Koog と Docker を使用したレシピ AI エージェントの構築

こんにちは、私は Docker のプリンシパル ソリューション アーキテクトである Philippe Charriere です。私は新しいツールをテストして、それが実際のワークフローにどのように適合するかを確認するのが好きです。最近、JetBrains の Koog フレームワークが Docker Model Runner で実行できるかどうかを確認しようとしましたが、簡単なテストとして始まったものが、予想よりもはるかに興味深いものになりました。この新しいブログ投稿では、人気のある Docker AI ツールを使用して、ラタトゥイユのレシピに特化した小さな Koog エージェントを作成する方法を探ります (免責事項: 私はフランス人です)。以下を使用します。

前提条件: Kotlin プロジェクトの初期化

  • IntelliJ IDEA Community Editionを使用してKotlinプロジェクトを初期化します。
  • プロジェクト構成にはOpenJDK 23 とGradle Kotlin DSLを使用しています。

ステップ 1: Gradle の構成

これが私のビルド構成です:build.gradle.kts

plugins {
    kotlin("jvm") version "2.1.21"
    application
}

group = "kitchen.ratatouille"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(kotlin("test"))
    implementation("ai.koog:koog-agents:0.3.0")
    implementation("org.slf4j:slf4j-simple:2.0.9")

}

application {
    mainClass.set("kitchen.ratatouille.MainKt")
}

tasks.test {
    useJUnitPlatform()
}

tasks.jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    manifest {
        attributes("Main-Class" to "kitchen.ratatouille.MainKt")
    }
    from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
}

kotlin {
    jvmToolchain(23)
}

ステップ 2: Docker Compose プロジェクト構成

Docker Compose の新しい「エージェント」機能を使用すると、Docker Compose サービスで使用されるモデルを定義できます。

以下の内容で、Hugging Face の hf.co/menlo/lucy-128k-gguf:q4_k_m モデルを「Koog エージェント」に使用することを定義します。

models:
  app_model:
    model: hf.co/menlo/lucy-128k-gguf:q4_k_m

そして、koog-appサービスとapp_modelモデルとKoogエージェントの間の「リンク」をサービスレベルで次のように作成します。

models:
      app_model:
        endpoint_var: MODEL_RUNNER_BASE_URL
        model_var: MODEL_RUNNER_CHAT_MODEL

Docker Compose は、MODEL_RUNNER_BASE_URL と MODEL_RUNNER_CHAT_MODEL の環境変数を koog-app サービスに自動的に挿入し、Koog エージェントがモデルに接続できるようにします。

koog-app コンテナーで対話モードに入った場合、環境変数がコマンドで適切に定義されていることを確認できます。

env | grep '^MODEL_RUNNER'

そして、次のようなものが得られます。

MODEL_RUNNER_BASE_URL=http://model-runner.docker.internal/engines/v1/
MODEL_RUNNER_CHAT_MODEL=hf.co/menlo/lucy-128k-gguf:q4_k_m

複数のモデルを定義することは完全に可能です。

完全なcompose.yamlファイルは次のようになります。

services:

  koog-app:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      SYSTEM_PROMPT: You are a helpful cooking assistant.
      AGENT_INPUT: How to cook a ratatouille?
    models:
      app_model:
        endpoint_var: MODEL_RUNNER_BASE_URL
        model_var: MODEL_RUNNER_CHAT_MODEL

models:
  app_model:
    model: hf.co/menlo/lucy-128k-gguf:q4_k_m

ステップ 3: Dockerfile

次に、KoogアプリケーションのDockerイメージをビルドするためのDockerfileが必要です。Dockerfile は、最終的なイメージ サイズを最適化するためにマルチステージ ビルドを使用するため、アプリケーションのビルド (ビルド) と実行 (ランタイム) の 2 つの部分/ステージに分かれています。Dockerfile の内容は次のとおりです。

# Stage 1: Build
FROM eclipse-temurin:23-jdk-noble AS build

WORKDIR /app

COPY gradlew .
COPY gradle/ gradle/
COPY build.gradle.kts .
COPY settings.gradle.kts .

RUN chmod +x ./gradlew

COPY src/ src/

# Build
RUN ./gradlew clean build

# Stage 2: Runtime
FROM eclipse-temurin:23-jre-noble AS runtime

WORKDIR /app

COPY --from=build /app/build/libs/ratatouille-1.0-SNAPSHOT.jar app.jar
CMD ["java", "-jar", "app.jar"]

ステップ 4: Kotlin側:

Docker Model Runnerへの接続

さて、Docker Model Runner を使用できるようにするためのアプリケーションのソース コードを src/main/kotlin/Main.kt ファイルに示します。Docker Model Runner によって公開される API は OpenAI API と互換性があるため、Koog の OpenAI クライアントを使用してモデルと対話します。

package kitchen.ratatouille

import ai.koog.prompt.executor.clients.openai.OpenAIClientSettings
import ai.koog.prompt.executor.clients.openai.OpenAILLMClient

suspend fun main() {

    val apiKey = "nothing"
    val customEndpoint = System.getenv("MODEL_RUNNER_BASE_URL").removeSuffix("/")
    val model = System.getenv("MODEL_RUNNER_CHAT_MODEL")

    val client = OpenAILLMClient(
        apiKey=apiKey,
        settings = OpenAIClientSettings(customEndpoint)
    )
}

最初のクーグエージェント

Koog を使用したエージェントの作成は、以下のコードでわかるように比較的簡単です。必要なもの:

  • 以前に作成した OpenAI クライアントを使用してモデルへのリクエストを実行する SingleLLMPromptExecutor です。
  • 使用するモデルを定義するLLModel。
  • モデルをカプセル化するAIAgentと、リクエストを実行するプロンプトエグゼキューター。

プロンプトに関しては、SYSTEM_PROMPT 環境変数を使用してエージェントのシステム プロンプトを定義し、AGENT_INPUT を使用してエージェントの入力 (「ユーザー メッセージ」) を定義します。これらの変数は、以前に compose.yaml ファイルで定義されていました。

environment:
      SYSTEM_PROMPT: You are a helpful cooking assistant.
      AGENT_INPUT: How to cook a ratatouille?

src/main/kotlin/Main.kt ファイル内の Koog エージェントの完全なコードは次のとおりです。

package kitchen.ratatouille

import ai.koog.agents.core.agent.AIAgent
import ai.koog.prompt.executor.clients.openai.OpenAIClientSettings
import ai.koog.prompt.executor.clients.openai.OpenAILLMClient
import ai.koog.prompt.executor.llms.SingleLLMPromptExecutor
import ai.koog.prompt.llm.LLMCapability
import ai.koog.prompt.llm.LLMProvider
import ai.koog.prompt.llm.LLModel

suspend fun main() {

    val apiKey = "nothing"
    val customEndpoint = System.getenv("MODEL_RUNNER_BASE_URL").removeSuffix("/")
    val model = System.getenv("MODEL_RUNNER_CHAT_MODEL")

    val client = OpenAILLMClient(
        apiKey=apiKey,
        settings = OpenAIClientSettings(customEndpoint)
    )

    val promptExecutor = SingleLLMPromptExecutor(client)

    val llmModel = LLModel(
        provider = LLMProvider.OpenAI,
        id = model,
        capabilities = listOf(LLMCapability.Completion)
    )

    val agent = AIAgent(
        executor = promptExecutor,
        systemPrompt = System.getenv("SYSTEM_PROMPT"),
        llmModel = llmModel,
        temperature = 0.0
    )

    val recipe = agent.run(System.getenv("AGENT_INPUT"))

    println("Recipe:\n $recipe")

}

プロジェクトの実行

あとは、次のコマンドでプロジェクトを起動するだけです。

docker compose up --build --no-log-prefix

その後、マシンによっては、ビルドと完了の時間が多かれ少なかれ長くなります。それでも私が Lucy 128k を選んだのは、GPU がなくても小さな構成で実行できるためです。このモデルには、サイズが小さいにもかかわらず「関数呼び出し」の検出に非常に優れているという利点もあります(ただし、並列ツール呼び出しはサポートしていません)。そして、最終的にコンソールで次のようなものが表示されるはずです。

Recipe:
 Sure! Here's a step-by-step guide to cooking a classic ratatouille:

---

### **Ingredients**  
- 2 boneless chicken thighs or 1-2 lbs rabbit (chicken is common, but rabbit is traditional)  
- 1 small onion (diced)  
- 2 garlic cloves (minced)  
- 1 cup tomatoes (diced)  
- 1 zucchini (sliced)  
- 1 yellow squash or eggplant (sliced)  
- 1 bell pepper (sliced)  
- 2 medium potatoes (chopped)  
- 1 red onion (minced)  
- 2 tbsp olive oil  
- 1 tbsp thyme (or rosemary)  
- Salt and pepper (to taste)  
- Optional: 1/4 cup wine (white or red) to deglaze the pan  

---

### **Steps**  
1. **Prep the Ingredients**  
   - Dice the onion, garlic, tomatoes, zucchini, squash, bell pepper, potatoes.  
   - Sauté the chicken in olive oil until browned (about 10–15 minutes).  
   - Add the onion and garlic, sauté for 2–3 minutes.  

2. **Add Vegetables & Flavor**  
   - Pour in the tomatoes, zucchini, squash, bell pepper, red onion, and potatoes.  
   - Add thyme, salt, pepper, and wine (if using). Stir to combine.  
   - Add about 1 cup water or stock to fill the pot, if needed.  

3. **Slow Cook**  
   - Place the pot in a large pot of simmering water (or use a Dutch oven) and cook on low heat (around 200°F/90°C) for about 30–40 minutes, or until the chicken is tender.  
   - Alternatively, use a stovetop pot with a lid to cook the meat and vegetables together, simmering until the meat is cooked through.  

4. **Finish & Serve**  
   - Remove the pot from heat and let it rest for 10–15 minutes to allow flavors to meld.  
   - Stir in fresh herbs (like rosemary or parsley) if desired.  
   - Serve warm with crusty bread or on the plate as is.  

---

### **Tips**  
- **Meat Variations**: Use duck or other meats if you don't have chicken.  
- **Vegetables**: Feel free to swap out any vegetables (e.g., mushrooms, leeks).  
- **Liquid**: If the mixture is too dry, add a splash of water or stock.  
- **Serving**: Ratatouille is often eaten with bread, so enjoy it with a side of crusty bread or a simple salad.  

Enjoy your meal! 

ご覧のとおり、Koog と Docker Model Runner を使用してエージェントを作成するのは非常に簡単です。 

しかし、問題があります、私はフランス人で、ルーシー128kが提案したラタトゥイユのレシピは私にはあまり合いません:ラタトゥイユにはウサギ、鶏肉、アヒルが入っていません!!.しかし、それを修正する方法を見てみましょう。

Docker MCP ゲートウェイを使用して Koog エージェントにスーパーパワーを追加しましょう

私が今やりたいことは、最初にアプリケーションでラタトゥイユの材料に関する情報を検索し、次に Koog エージェントにこの情報を使用してレシピを改善してもらうことです。このために、 Docker MCP Hubで利用可能なDuckDuckGo MCPサーバーを使用したいと思います。そして、私の生活を楽にするために、 Docker MCPゲートウェイ を使用してこのMCPサーバーにアクセスします。

Docker Compose での Docker MCP ゲートウェイの構成

Docker MCP ゲートウェイを使用するには、まずcompose.ymlファイルを変更してゲートウェイ構成を追加します。

compose.yaml ファイルでのゲートウェイの構成

compose.yamlファイルでゲートウェイに追加した構成は次のとおりです。

 mcp-gateway:
    image: docker/mcp-gateway:latest
    command:
      - --port=8811
      - --transport=sse
      - --servers=duckduckgo
      - --verbose
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

この構成では、ポート 8811 でリッスンし、sse (Server-Sent Events) トランスポートを使用して MCP サーバーと通信する mcp ゲートウェイ サービスが作成されます。

重要:

  • –servers=duckduckgo を使用すると、使用可能な MCP サーバーをフィルタリングして、DuckDuckGo サーバーのみを使用できます。
  • MCP ゲートウェイは、Docker MCP Hub から利用可能な MCP サーバーを自動的にプルします。

MCP Gateway は、 次の場所にあるオープンソース プロジェクトです。 

次に、ゲートウェイ URL を指す MCP_HOST 環境変数と mcp-gateway サービスへの依存関係を追加して、ゲートウェイと通信できるように koog-app サービスを変更します。

environment:
      MCP_HOST: http://mcp-gateway:8811/sse
    depends_on:
      - mcp-gateway      

また、システムプロンプトとユーザーメッセージも変更します。

environment:
      SYSTEM_PROMPT: |
        You are a helpful cooking assistant.
        Your job is to understand the user prompt and decide if you need to use tools to run external commands.
      AGENT_INPUT: |
        Search for the ingredients to cook a ratatouille, max result 1
        Then, from these found ingredients, generate a yummy ratatouille recipe
        Do it only once

MCP ゲートウェイの構成と koog-app サービスに加えられた変更を含む完全なcompose.yml ファイルを次に示します。

services:

  koog-app:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      SYSTEM_PROMPT: |
        You are a helpful cooking assistant.
        Your job is to understand the user prompt and decide if you need to use tools to run external commands.
      AGENT_INPUT: |
        Search for the ingredients to cook a ratatouille, max result 1
        Then, from these found ingredients, generate a yummy ratatouille recipe
        Do it only once
      MCP_HOST: http://mcp-gateway:8811/sse
    depends_on:
      - mcp-gateway
    models:
      app_model:
        # NOTE: populate the environment variables with the model runner endpoint and model name
        endpoint_var: MODEL_RUNNER_BASE_URL
        model_var: MODEL_RUNNER_CHAT_MODEL

  mcp-gateway:
    image: docker/mcp-gateway:latest
    command:
      - --port=8811
      - --transport=sse
      - --servers=duckduckgo
      - --verbose
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

models:
  app_model:
    model: hf.co/menlo/lucy-128k-gguf:q4_k_m 

次に、MCP ゲートウェイを使用するように Kotlin コードを変更し、ラタトゥイユの材料を検索しましょう。

MCP ゲートウェイを使用するための Kotlin コードの変更

変更は非常に簡単です。必要なのは、次のことだけです。

  • ゲートウェイ URL を使用して MCP トランスポート (SseClientTransport) を定義します: val transport = McpToolRegistryProvider.defaultSseTransport(System.getenv("MCP_HOST"))
  • ゲートウェイを使用してMCPツールレジストリを作成します: val toolRegistry = McpToolRegistryProvider.fromTransport(transport = transport, name = "sse-client", version = "1。0。0")
  • 最後に、ツールレジストリをKoogエージェントコンストラクターに追加します:toolRegistry = toolRegistry

非常に重要なこと: LLM モデルの "関数呼び出し" 機能を使用するため、capabilities = listOf(LLMCapability.Completion, LLMCapability.Tools) を追加しました (ツールは MCP サーバーによって定義および提供されます)。

MCP ゲートウェイを使用するように変更された Koog エージェントの完全なコードを src/main/kotlin/Main.kt ファイルで示します。

package kitchen.ratatouille

import ai.koog.agents.core.agent.AIAgent
import ai.koog.agents.mcp.McpToolRegistryProvider
import ai.koog.prompt.executor.clients.openai.OpenAIClientSettings
import ai.koog.prompt.executor.clients.openai.OpenAILLMClient
import ai.koog.prompt.executor.llms.SingleLLMPromptExecutor
import ai.koog.prompt.llm.LLMCapability
import ai.koog.prompt.llm.LLMProvider
import ai.koog.prompt.llm.LLModel

suspend fun main() {

    val transport = McpToolRegistryProvider.defaultSseTransport(System.getenv("MCP_HOST"))
    // Create a tool registry with tools from the MCP server
    val toolRegistry = McpToolRegistryProvider.fromTransport(
        transport = transport,
        name = "sse-client",
        version = "1.0.0"
    )
    println(toolRegistry.tools)

    val apiKey = "nothing"
    val customEndpoint = System.getenv("MODEL_RUNNER_BASE_URL").removeSuffix("/")
    val model = System.getenv("MODEL_RUNNER_CHAT_MODEL")

    val client = OpenAILLMClient(
        apiKey=apiKey,
        settings = OpenAIClientSettings(customEndpoint)
    )

    val promptExecutor = SingleLLMPromptExecutor(client)

    val llmModel = LLModel(
        provider = LLMProvider.OpenAI,
        id = model,
        capabilities = listOf(LLMCapability.Completion, LLMCapability.Tools)
    )

    val agent = AIAgent(
        executor = promptExecutor,
        systemPrompt = System.getenv("SYSTEM_PROMPT"),
        llmModel = llmModel,
        temperature = 0.0,
        toolRegistry = toolRegistry
    )

    val recipe = agent.run(System.getenv("AGENT_INPUT"))

    println("Recipe:\n $recipe")

}

MCP ゲートウェイを使用したプロジェクトの起動

次のコマンドでプロジェクトを再度起動しましょう。

docker compose up --build --no-log-prefix

しばらくすると、新しいラタトゥイユのレシピが手に入るはずですが、LLMはレシピを改善するためにDuckDuckGo MCPサーバー(MCPゲートウェイ経由)によって実行された検索結果に依存しています。LLM は最初に MCP サーバーにクエリを実行してラタトゥイユの材料を取得し、次にレシピを生成するため、応答時間は少し長くなります。そして、DuckDuckGo MCPサーバーはリンクを検索し、それらのリンクの内容を取得します(実際、DuckDuckGo MCPサーバーは検索とfetch_contentという 2 ツールを公開しています)。

以下は、改良されたより「本格的な」ラタトゥイユのレシピで得られるものの例です。

Recipe:
 Here's a **complete and easy-to-follow version** of **Ratatouille**, based on the recipe you provided, with tips and variations to suit your preferences:

---

###  **What Is Ratatouille?**  
A classic French vegetable stew, traditionally made with eggplant, tomatoes, zucchini, bell peppers, onions, and mushrooms. It's often seasoned with herbs like parsley, thyme, or basil and paired with crusty bread or a light sauce.

---

###  **Ingredients** (for 4 servings):  
- **1/2 cup olive oil** (divided)  
- **2 tbsp olive oil** (for the skillet)  
- **3 cloves garlic**, minced  
- **1 eggplant**, cubed  
- **2 zucchinis**, sliced  
- **2 large tomatoes**, chopped  
- **2 cups fresh mushrooms**, sliced  
- **1 large onion**, sliced  
- **1 green or red bell pepper**, sliced  
- **1/2 tsp dried parsley**  
- **Salt to taste**  
- **1/2 cup grated Parmesan cheese** (or pecorino, as you mentioned)  

---

###  **How to Make Ratatouille**  
**Preheat oven** to 350°F (175°C).  

1. **Prepare the dish**: Coat a 1½-quart casserole dish with 1 tbsp olive oil.  
2. **Cook the base**: In a skillet, sauté garlic until fragrant (about 1–2 minutes). Add eggplant, parsley, and salt; cook for 10 minutes until tender.  
3. **Layer the vegetables**: Spread the eggplant mixture in the dish, then add zucchini, tomatoes, mushrooms, onion, and bell pepper. Top with Parmesan.  
4. **Bake**: Cover and bake for 45 minutes. Check for tenderness; adjust time if needed.  

**Cook's Note**:  
- Add mushrooms (optional) or omit for a traditional flavor.  
- Use fresh herbs like thyme or basil if preferred.  
- Substitute zucchini with yellow squash or yellow bell pepper for color.  

---

###  **How to Serve**  
- **Main dish**: Serve with crusty French bread or rice.  
- **Side**: Pair with grilled chicken or fish.  
- **Guilt-free twist**: Add black olives or a sprinkle of basil/others for a lighter version.  

---

結論

このブログ投稿は、Dockerが構築している最新のコンテナ化されたAIエコシステムを完璧に示しています。Docker Model RunnerAgentic ComposeDocker MCP Gateway、Koogフレームワーク(もちろん他のフレームワークを使用することもできます)を組み合わせることで、「インテリジェント」なエージェントを非常に簡単に作成することができました。

  • Docker Model Runner を使用すると、AI モデルをローカルで使用できるようになりました。
  • Agentic Compose は、必要な環境変数を自動的に挿入することで、アプリケーションへのモデルの統合を簡素化しました。
  • Docker MCP ゲートウェイは、私たちの小さなエージェントを外界と対話できるシステムに変えました。
  • Koog フレームワークにより、これらのコンポーネントを Kotlin で調整できるようになりました。

近いうちに、MCP ゲートウェイと、Koog だけでなく、独自の MCP サーバーでそれを使用する方法について詳しく説明します。そして、Koog と Docker Model Runner で探索を続けています。このプロジェクトのソースコード全体はこちらから入手できます 

さらに詳しく

投稿カテゴリ

関連記事