コンテンツにスキップ

LLMエージェント

ADKでサポートPython v0.1.0Go v0.1.0Java v0.1.0

LlmAgent(しばしばAgentと略されます)はADKにおけるコアコンポーネントであり、アプリケーションの「思考」部分として機能します。大規模言語モデル(LLM)の能力を活用して、推論、自然言語の理解、意思決定、応答の生成、ツールとの対話を行います。

事前に定義された実行パスに従う決定論的なワークフローエージェントとは異なり、LlmAgentの振る舞いは非決定的です。LLMを使用して指示とコンテキストを解釈し、動的に処理方法を決定したり、どのツールを使用するか(もし使用する場合)、あるいは他のエージェントに制御を移すかを判断します。

効果的なLlmAgentを構築するには、そのアイデンティティを定義し、指示を通じてその振る舞いを明確にガイドし、必要なツールと能力を備えさせることが含まれます。

エージェントのアイデンティティと目的の定義

まず、エージェントが何であるか、そして何のためにあるのかを確立する必要があります。

  • name (必須): 全てのエージェントには一意の文字列識別子が必要です。このnameは内部操作、特にエージェント同士が互いを参照したりタスクを委任したりする必要があるマルチエージェントシステムにおいて非常に重要です。エージェントの機能を反映した説明的な名前(例: customer_support_router, billing_inquiry_agent)を選択してください。userのような予約名は避けてください。

  • description (任意、マルチエージェントで推奨): エージェントの能力に関する簡潔な要約を提供します。この説明は主に、他のLLMエージェントがこのエージェントにタスクをルーティングすべきかどうかを判断するために使用されます。同僚と区別できるように、具体的に記述してください(例: 「請求エージェント」ではなく、「現在の請求書に関する問い合わせを処理します」)。

  • model (必須): このエージェントの推論を支える基盤となるLLMを指定します。これは"gemini-2.0-flash"のような文字列識別子です。モデルの選択は、エージェントの能力、コスト、パフォーマンスに影響します。利用可能なオプションと考慮事項については、モデルページを参照してください。

# 例: 基本的なアイデンティティの定義
capital_agent = LlmAgent(
    model="gemini-2.0-flash",
    name="capital_agent",
    description="与えられた国の首都に関するユーザーの質問に答えます。"
    # instruction と tools は次に追加します
)
// Example: Defining the basic identity
agent, err := llmagent.New(llmagent.Config{
    Name:        "capital_agent",
    Model:       model,
    Description: "Answers user questions about the capital city of a given country.",
    // instruction and tools will be added next
})
// 例: 基本的なアイデンティティの定義
LlmAgent capitalAgent =
    LlmAgent.builder()
        .model("gemini-2.0-flash")
        .name("capital_agent")
        .description("与えられた国の首都に関するユーザーの質問に答えます。")
        // instruction と tools は次に追加します
        .build();

エージェントのガイド:指示 (instruction)

instructionパラメータは、LlmAgentの振る舞いを形成する上で、おそらく最も重要な要素です。これは、エージェントに以下のことを伝える文字列(または文字列を返す関数)です:

  • その中核となるタスクや目標。
  • その性格やペルソナ(例:「あなたは親切なアシスタントです」、「あなたは機知に富んだ海賊です」)。
  • その振る舞いに対する制約(例:「Xに関する質問にのみ答えてください」、「Yは決して明かさないでください」)。
  • toolsをどのように、いつ使用するか。各ツールの目的と、それが呼び出されるべき状況を説明し、ツール自体の説明を補足する必要があります。
  • 望ましい出力フォーマット(例:「JSONで応答してください」、「箇条書きリストで提供してください」)。

効果的な指示のためのヒント:

  • 明確かつ具体的に: 曖昧さを避けてください。望ましい行動と結果を明確に記述します。
  • マークダウンを使用: 複雑な指示の可読性を向上させるために、見出しやリストなどを使用します。
  • 例を提供する (Few-Shot): 複雑なタスクや特定の出力フォーマットの場合、指示の中に直接例を含めます。
  • ツールの使用をガイド: ツールをリストアップするだけでなく、エージェントがいつなぜそれらを使用すべきかを説明します。

状態 (State):

  • 指示は文字列テンプレートであり、{var}構文を使用して動的な値を指示に挿入できます。
  • {var}は、varという名前の状態変数の値を挿入するために使用されます。
  • {artifact.var}は、varという名前のアーティファクトのテキストコンテンツを挿入するために使用されます。
  • 状態変数またはアーティファクトが存在しない場合、エージェントはエラーを発生させます。エラーを無視したい場合は、変数名の末尾に?を付けて{var?}のようにすることができます。
# 例: 指示の追加
capital_agent = LlmAgent(
    model="gemini-2.0-flash",
    name="capital_agent",
    description="与えられた国の首都に関するユーザーの質問に答えます。",
    instruction="""あなたは国の首都を提供するエージェントです。
ユーザーが国の首都を尋ねたら:
1. ユーザーのクエリから国名を特定します。
2. `get_capital_city` ツールを使用して首都を見つけます。
3. ユーザーに首都を明確に述べて応答します。
クエリ例: 「{country}の首都は何ですか?」
応答例: 「フランスの首都はパリです。」
""",
    # tools は次に追加します
)
    // Example: Adding instructions
    agent, err := llmagent.New(llmagent.Config{
        Name:        "capital_agent",
        Model:       model,
        Description: "Answers user questions about the capital city of a given country.",
        Instruction: `You are an agent that provides the capital city of a country.
When a user asks for the capital of a country:
1. Identify the country name from the user's query.
2. Use the 'get_capital_city' tool to find the capital.
3. Respond clearly to the user, stating the capital city.
Example Query: "What's the capital of {country}?"
Example Response: "The capital of France is Paris."`,
        // tools will be added next
    })
// 例: 指示の追加
LlmAgent capitalAgent =
    LlmAgent.builder()
        .model("gemini-2.0-flash")
        .name("capital_agent")
        .description("与えられた国の首都に関するユーザーの質問に答えます。")
        .instruction(
            """
            あなたは国の首都を提供するエージェントです。
            ユーザーが国の首都を尋ねたら:
            1. ユーザーのクエリから国名を特定します。
            2. `get_capital_city` ツールを使用して首都を見つけます。
            3. ユーザーに首都を明確に述べて応答します。
            クエリ例: 「{country}の首都は何ですか?」
            応答例: 「フランスの首都はパリです。」
            """)
        // tools は次に追加します
        .build();

(注:システムのすべてのエージェントに適用される指示については、ルートエージェントのglobal_instructionの使用を検討してください。詳細はマルチエージェントセクションで説明します。)

エージェントの装備:ツール (tools)

ツールは、LlmAgentにLLMに組み込まれた知識や推論能力を超える機能を与えます。ツールにより、エージェントは外部世界と対話したり、計算を実行したり、リアルタイムデータを取得したり、特定のアクションを実行したりできます。

  • tools (任意): エージェントが使用できるツールのリストを提供します。リストの各項目は、次のいずれかです:
    • ネイティブ関数またはメソッド(FunctionToolとしてラップ)。Python ADKはネイティブ関数を自動的にFunctionToolにラップしますが、JavaではFunctionTool.create(...)を使用してメソッドを明示的にラップする必要があります。
    • BaseToolを継承するクラスのインスタンス。
    • 別のエージェントのインスタンス(AgentTool、エージェント間の委任を可能にする - マルチエージェント参照)。

LLMは、関数/ツール名、説明(docstringやdescriptionフィールドから)、およびパラメータスキーマを使用して、会話と指示に基づいてどのツールを呼び出すかを決定します。

# ツール関数を定義
def get_capital_city(country: str) -> str:
  """指定された国の首都を取得します。"""
  # 実際のロジックに置き換えてください (例: API呼び出し, データベース検索)
  capitals = {"france": "Paris", "japan": "Tokyo", "canada": "Ottawa"}
  return capitals.get(country.lower(), f"申し訳ありませんが、{country}の首都はわかりません。")

# エージェントにツールを追加
capital_agent = LlmAgent(
    model="gemini-2.0-flash",
    name="capital_agent",
    description="与えられた国の首都に関するユーザーの質問に答えます。",
    instruction="""あなたは国の首都を提供するエージェントです... (前の指示テキスト)""",
    tools=[get_capital_city] # 関数を直接提供
)
// Define a tool function
type getCapitalCityArgs struct {
    Country string `json:"country" jsonschema:"The country to get the capital of."`
}
getCapitalCity := func(ctx tool.Context, args getCapitalCityArgs) (map[string]any, error) {
    // Replace with actual logic (e.g., API call, database lookup)
    capitals := map[string]string{"france": "Paris", "japan": "Tokyo", "canada": "Ottawa"}
    capital, ok := capitals[strings.ToLower(args.Country)]
    if !ok {
        return nil, fmt.Errorf("Sorry, I don't know the capital of %s.", args.Country)
    }
    return map[string]any{"result": capital}, nil
}

// Add the tool to the agent
capitalTool, err := functiontool.New(
    functiontool.Config{
        Name:        "get_capital_city",
        Description: "Retrieves the capital city for a given country.",
    },
    getCapitalCity,
)
if err != nil {
    log.Fatal(err)
}
agent, err := llmagent.New(llmagent.Config{
    Name:        "capital_agent",
    Model:       model,
    Description: "Answers user questions about the capital city of a given country.",
    Instruction: "You are an agent that provides the capital city of a country... (previous instruction text)",
    Tools:       []tool.Tool{capitalTool},
})
// ツール関数を定義
// 指定された国の首都を取得します。
public static Map<String, Object> getCapitalCity(
        @Schema(name = "country", description = "首都を取得する国")
        String country) {
  // 実際のロジックに置き換えてください (例: API呼び出し, データベース検索)
  Map<String, String> countryCapitals = new HashMap<>();
  countryCapitals.put("canada", "Ottawa");
  countryCapitals.put("france", "Paris");
  countryCapitals.put("japan", "Tokyo");

  String result =
          countryCapitals.getOrDefault(
                  country.toLowerCase(), "申し訳ありませんが、" + country + "の首都は見つかりませんでした。");
  return Map.of("result", result); // ツールはMapを返す必要があります
}

// エージェントにツールを追加
FunctionTool capitalTool = FunctionTool.create(experiment.getClass(), "getCapitalCity");
LlmAgent capitalAgent =
    LlmAgent.builder()
        .model("gemini-2.0-flash")
        .name("capital_agent")
        .description("与えられた国の首都に関するユーザーの質問に答えます。")
        .instruction("あなたは国の首都を提供するエージェントです... (前の指示テキスト)")
        .tools(capitalTool) // FunctionToolとしてラップされた関数を提供
        .build();

ツールについての詳細は、ツールセクションをご覧ください。

高度な設定と制御

主要なパラメータに加えて、LlmAgentはより詳細な制御のためのいくつかのオプションを提供します。

LLM生成の設定 (generate_content_config)

generate_content_configを使用して、基盤となるLLMが応答を生成する方法を調整できます。

  • generate_content_config (任意): google.genai.types.GenerateContentConfigのインスタンスを渡して、temperature(ランダム性)、max_output_tokens(応答の長さ)、top_ptop_k、およびセーフティセッティングなどのパラメータを制御します。
from google.genai import types

agent = LlmAgent(
    # ... その他のパラメータ
    generate_content_config=types.GenerateContentConfig(
        temperature=0.2, # より決定論的な出力
        max_output_tokens=250,
        safety_settings=[
            types.SafetySetting(
                category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
                threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
            )
        ]
    )
)
import "google.golang.org/genai"

temperature := float32(0.2)
agent, err := llmagent.New(llmagent.Config{
    Name:  "gen_config_agent",
    Model: model,
    GenerateContentConfig: &genai.GenerateContentConfig{
        Temperature:     &temperature,
        MaxOutputTokens: 250,
    },
})
import com.google.genai.types.GenerateContentConfig;

LlmAgent agent =
    LlmAgent.builder()
        // ... その他のパラメータ
        .generateContentConfig(GenerateContentConfig.builder()
            .temperature(0.2F) // より決定論的な出力
            .maxOutputTokens(250)
            .build())
        .build();

データの構造化 (input_schema, output_schema, output_key)

LLM Agentとの構造化されたデータ交換を必要とするシナリオのために、ADKはスキーマ定義を使用して期待される入力と望ましい出力フォーマットを定義するメカニズムを提供します。

  • input_schema (任意): 期待される入力構造を表すスキーマを定義します。設定されている場合、このエージェントに渡されるユーザーメッセージの内容は、このスキーマに準拠したJSON文字列でなければなりません。あなたの指示は、ユーザーまたは先行するエージェントをそれに従ってガイドする必要があります。

  • output_schema (任意): 望ましい出力構造を表すスキーマを定義します。設定されている場合、エージェントの最終応答は、このスキーマに準拠したJSON文字列でなければなりません。

  • output_key (任意): 文字列キーを提供します。設定されている場合、エージェントの最終応答のテキスト内容は、このキーの下でセッションの状態辞書に自動的に保存されます。これは、エージェント間やワークフローのステップ間で結果を渡すのに便利です。

    • Pythonでは、session.state[output_key] = agent_response_textのようになります。
    • Javaでは、session.state().put(outputKey, agentResponseText)です。
    • Golangでは、コールバックハンドラ内でctx.State().Set(output_key, agentResponseText)となります。

入力および出力スキーマは、通常PydanticのBaseModelです。

from pydantic import BaseModel, Field

class CapitalOutput(BaseModel):
    capital: str = Field(description="その国の首都。")

structured_capital_agent = LlmAgent(
    # ... name, model, description
    instruction="""あなたは首都情報エージェントです。国が与えられたら、首都を含むJSONオブジェクトのみで応答してください。フォーマット: {"capital": "capital_name"}""",
    output_schema=CapitalOutput, # JSON出力を強制
    output_key="found_capital"  # 結果を state['found_capital'] に保存
    # ここでは tools=[get_capital_city] を効果的に使用できない
)

入力および出力スキーマは、google.genai.types.Schemaオブジェクトです。

capitalOutput := &genai.Schema{
    Type:        genai.TypeObject,
    Description: "Schema for capital city information.",
    Properties: map[string]*genai.Schema{
        "capital": {
            Type:        genai.TypeString,
            Description: "The capital city of the country.",
        },
    },
}

agent, err := llmagent.New(llmagent.Config{
    Name:         "structured_capital_agent",
    Model:        model,
    Description:  "Provides capital information in a structured format.",
    Instruction:  `You are a Capital Information Agent. Given a country, respond ONLY with a JSON object containing the capital. Format: {"capital": "capital_name"}`,
    OutputSchema: capitalOutput,
    OutputKey:    "found_capital",
    // Cannot use the capitalTool tool effectively here
})

入力および出力スキーマは、google.genai.types.Schemaオブジェクトです。

private static final Schema CAPITAL_OUTPUT =
    Schema.builder()
        .type("OBJECT")
        .description("首都情報のためのスキーマ。")
        .properties(
            Map.of(
                "capital",
                Schema.builder()
                    .type("STRING")
                    .description("その国の首都。")
                    .build()))
        .build();

LlmAgent structuredCapitalAgent =
    LlmAgent.builder()
        // ... name, model, description
        .instruction(
                "あなたは首都情報エージェントです。国が与えられたら、首都を含むJSONオブジェクトのみで応答してください。フォーマット: {\"capital\": \"capital_name\"}")
        .outputSchema(capitalOutput) // JSON出力を強制
        .outputKey("found_capital") // 結果を state.get("found_capital") に保存
        // ここでは tools(getCapitalCity) を効果的に使用できない
        .build();

コンテキストの管理 (include_contents)

エージェントが以前の会話履歴を受け取るかどうかを制御します。

  • include_contents (任意、デフォルト: 'default'): contents(履歴)がLLMに送信されるかどうかを決定します。
    • 'default': エージェントは関連する会話履歴を受け取ります。
    • 'none': エージェントは以前のcontentsを受け取りません。現在の指示と現在のターンで提供された入力のみに基づいて動作します(ステートレスなタスクや特定のコンテキストを強制する場合に便利です)。
stateless_agent = LlmAgent(
    # ... その他のパラメータ
    include_contents='none'
)
import "google.golang.org/adk/agent/llmagent"

agent, err := llmagent.New(llmagent.Config{
    Name:            "stateless_agent",
    Model:           model,
    IncludeContents: llmagent.IncludeContentsNone,
})
import com.google.adk.agents.LlmAgent.IncludeContents;

LlmAgent statelessAgent =
    LlmAgent.builder()
        // ... その他のパラメータ
        .includeContents(IncludeContents.NONE)
        .build();

プランナー

ADKでサポートPython v0.1.0

planner (任意): BasePlannerインスタンスを割り当てて、実行前の複数ステップの推論と計画を有効にします。主なプランナーは2つあります:

  • BuiltInPlanner: モデルの組み込み計画機能(例: Geminiの思考機能)を活用します。詳細と例については、Gemini Thinkingを参照してください。

    ここで、thinking_budgetパラメータは、応答を生成する際に使用する思考トークンの数をモデルにガイドします。include_thoughtsパラメータは、モデルが応答に生の思考や内部の推論プロセスを含めるべきかどうかを制御します。

    from google.adk import Agent
    from google.adk.planners import BuiltInPlanner
    from google.genai import types
    
    my_agent = Agent(
        model="gemini-2.5-flash",
        planner=BuiltInPlanner(
            thinking_config=types.ThinkingConfig(
                include_thoughts=True,
                thinking_budget=1024,
            )
        ),
        # ... ここにツールを記述
    )
    
  • PlanReActPlanner: このプランナーは、モデルに出力で特定の構造に従うように指示します:まず計画を立て、次に行動(ツールの呼び出しなど)を実行し、そのステップの推論を提供します。特に、組み込みの「思考」機能を持たないモデルに役立ちます

    from google.adk import Agent
    from google.adk.planners import PlanReActPlanner
    
    my_agent = Agent(
        model="gemini-2.0-flash",
        planner=PlanReActPlanner(),
        # ... ここにツールを記述
    )
    

    エージェントの応答は、構造化されたフォーマットに従います:

    [user]: ai news
    [google_search_agent]: /*PLANNING*/
    1. 人工知能に関する最新の更新とヘッドラインを得るために、「最新のAIニュース」でGoogle検索を実行する。
    2. 検索結果からの情報を統合して、最近のAIニュースの要約を提供する。
    
    /*ACTION*/
    /*REASONING*/
    検索結果は、企業の動向、研究のブレークスルー、応用など、さまざまな側面をカバーする最近のAIニュースの包括的な概要を提供しています。ユーザーの要求に答えるのに十分な情報があります。
    
    /*FINAL_ANSWER*/
    最近のAIニュースの要約はこちらです:
    ....
    

コード実行

ADKでサポートPython v0.1.0
  • code_executor (任意): BaseCodeExecutorインスタンスを提供して、エージェントがLLMの応答で見つかったコードブロックを実行できるようにします。(ツール/組み込みツール参照)。

組み込みプランナーの使用例:

# (コメント省略、原文コードと同じ)
...

まとめ:例

コード

これが完全な基本的なcapital_agentです:

# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# --- Full example code demonstrating LlmAgent with Tools vs. Output Schema ---
import json # Needed for pretty printing dicts
import asyncio 

from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
from pydantic import BaseModel, Field

# --- 1. Define Constants ---
APP_NAME = "agent_comparison_app"
USER_ID = "test_user_456"
SESSION_ID_TOOL_AGENT = "session_tool_agent_xyz"
SESSION_ID_SCHEMA_AGENT = "session_schema_agent_xyz"
MODEL_NAME = "gemini-2.0-flash"

# --- 2. Define Schemas ---

# Input schema used by both agents
class CountryInput(BaseModel):
    country: str = Field(description="The country to get information about.")

# Output schema ONLY for the second agent
class CapitalInfoOutput(BaseModel):
    capital: str = Field(description="The capital city of the country.")
    # Note: Population is illustrative; the LLM will infer or estimate this
    # as it cannot use tools when output_schema is set.
    population_estimate: str = Field(description="An estimated population of the capital city.")

# --- 3. Define the Tool (Only for the first agent) ---
def get_capital_city(country: str) -> str:
    """Retrieves the capital city of a given country."""
    print(f"\n-- Tool Call: get_capital_city(country='{country}') --")
    country_capitals = {
        "united states": "Washington, D.C.",
        "canada": "Ottawa",
        "france": "Paris",
        "japan": "Tokyo",
    }
    result = country_capitals.get(country.lower(), f"Sorry, I couldn't find the capital for {country}.")
    print(f"-- Tool Result: '{result}' --")
    return result

# --- 4. Configure Agents ---

# Agent 1: Uses a tool and output_key
capital_agent_with_tool = LlmAgent(
    model=MODEL_NAME,
    name="capital_agent_tool",
    description="Retrieves the capital city using a specific tool.",
    instruction="""You are a helpful agent that provides the capital city of a country using a tool.
The user will provide the country name in a JSON format like {"country": "country_name"}.
1. Extract the country name.
2. Use the `get_capital_city` tool to find the capital.
3. Respond clearly to the user, stating the capital city found by the tool.
""",
    tools=[get_capital_city],
    input_schema=CountryInput,
    output_key="capital_tool_result", # Store final text response
)

# Agent 2: Uses output_schema (NO tools possible)
structured_info_agent_schema = LlmAgent(
    model=MODEL_NAME,
    name="structured_info_agent_schema",
    description="Provides capital and estimated population in a specific JSON format.",
    instruction=f"""You are an agent that provides country information.
The user will provide the country name in a JSON format like {{"country": "country_name"}}.
Respond ONLY with a JSON object matching this exact schema:
{json.dumps(CapitalInfoOutput.model_json_schema(), indent=2)}
Use your knowledge to determine the capital and estimate the population. Do not use any tools.
""",
    # *** NO tools parameter here - using output_schema prevents tool use ***
    input_schema=CountryInput,
    output_schema=CapitalInfoOutput, # Enforce JSON output structure
    output_key="structured_info_result", # Store final JSON response
)

# --- 5. Set up Session Management and Runners ---
session_service = InMemorySessionService()

# Create a runner for EACH agent
capital_runner = Runner(
    agent=capital_agent_with_tool,
    app_name=APP_NAME,
    session_service=session_service
)
structured_runner = Runner(
    agent=structured_info_agent_schema,
    app_name=APP_NAME,
    session_service=session_service
)

# --- 6. Define Agent Interaction Logic ---
async def call_agent_and_print(
    runner_instance: Runner,
    agent_instance: LlmAgent,
    session_id: str,
    query_json: str
):
    """Sends a query to the specified agent/runner and prints results."""
    print(f"\n>>> Calling Agent: '{agent_instance.name}' | Query: {query_json}")

    user_content = types.Content(role='user', parts=[types.Part(text=query_json)])

    final_response_content = "No final response received."
    async for event in runner_instance.run_async(user_id=USER_ID, session_id=session_id, new_message=user_content):
        # print(f"Event: {event.type}, Author: {event.author}") # Uncomment for detailed logging
        if event.is_final_response() and event.content and event.content.parts:
            # For output_schema, the content is the JSON string itself
            final_response_content = event.content.parts[0].text

    print(f"<<< Agent '{agent_instance.name}' Response: {final_response_content}")

    current_session = await session_service.get_session(app_name=APP_NAME,
                                                  user_id=USER_ID,
                                                  session_id=session_id)
    stored_output = current_session.state.get(agent_instance.output_key)

    # Pretty print if the stored output looks like JSON (likely from output_schema)
    print(f"--- Session State ['{agent_instance.output_key}']: ", end="")
    try:
        # Attempt to parse and pretty print if it's JSON
        parsed_output = json.loads(stored_output)
        print(json.dumps(parsed_output, indent=2))
    except (json.JSONDecodeError, TypeError):
         # Otherwise, print as string
        print(stored_output)
    print("-" * 30)


# --- 7. Run Interactions ---
async def main():
    # Create separate sessions for clarity, though not strictly necessary if context is managed
    print("--- Creating Sessions ---")
    await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID_TOOL_AGENT)
    await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID_SCHEMA_AGENT)

    print("--- Testing Agent with Tool ---")
    await call_agent_and_print(capital_runner, capital_agent_with_tool, SESSION_ID_TOOL_AGENT, '{"country": "France"}')
    await call_agent_and_print(capital_runner, capital_agent_with_tool, SESSION_ID_TOOL_AGENT, '{"country": "Canada"}')

    print("\n\n--- Testing Agent with Output Schema (No Tool Use) ---")
    await call_agent_and_print(structured_runner, structured_info_agent_schema, SESSION_ID_SCHEMA_AGENT, '{"country": "France"}')
    await call_agent_and_print(structured_runner, structured_info_agent_schema, SESSION_ID_SCHEMA_AGENT, '{"country": "Japan"}')

# --- Run the Agent ---
# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
if __name__ == "__main__":
    asyncio.run(main())    
package main

import (
    "context"
    "encoding/json"
    "errors"
    "fmt"
    "log"
    "strings"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/functiontool"

    "google.golang.org/genai"
)

// --- Main Runnable Example ---

const (
    modelName = "gemini-2.0-flash"
    appName   = "agent_comparison_app"
    userID    = "test_user_456"
)

type getCapitalCityArgs struct {
    Country string `json:"country" jsonschema:"The country to get the capital of."`
}

// getCapitalCity retrieves the capital city of a given country.
func getCapitalCity(ctx tool.Context, args getCapitalCityArgs) (map[string]any, error) {
    fmt.Printf("\n-- Tool Call: getCapitalCity(country='%s') --\n", args.Country)
    capitals := map[string]string{
        "united states": "Washington, D.C.",
        "canada":        "Ottawa",
        "france":        "Paris",
        "japan":         "Tokyo",
    }
    capital, ok := capitals[strings.ToLower(args.Country)]
    if !ok {
        result := fmt.Sprintf("Sorry, I couldn't find the capital for %s.", args.Country)
        fmt.Printf("-- Tool Result: '%s' --\n", result)
        return nil, errors.New(result)
    }
    fmt.Printf("-- Tool Result: '%s' --\n", capital)
    return map[string]any{"result": capital}, nil
}

// callAgent is a helper function to execute an agent with a given prompt and handle its output.
func callAgent(ctx context.Context, a agent.Agent, outputKey string, prompt string) {
    fmt.Printf("\n>>> Calling Agent: '%s' | Query: %s\n", a.Name(), prompt)
    // Create an in-memory session service to manage agent state.
    sessionService := session.InMemoryService()

    // Create a new session for the agent interaction.
    sessionCreateResponse, err := sessionService.Create(ctx, &session.CreateRequest{
        AppName: appName,
        UserID:  userID,
    })
    if err != nil {
        log.Fatalf("Failed to create the session service: %v", err)
    }

    session := sessionCreateResponse.Session

    // Configure the runner with the application name, agent, and session service.
    config := runner.Config{
        AppName:        appName,
        Agent:          a,
        SessionService: sessionService,
    }

    // Create a new runner instance.
    r, err := runner.New(config)
    if err != nil {
        log.Fatalf("Failed to create the runner: %v", err)
    }

    // Prepare the user's message to send to the agent.
    sessionID := session.ID()
    userMsg := &genai.Content{
        Parts: []*genai.Part{
            genai.NewPartFromText(prompt),
        },
        Role: string(genai.RoleUser),
    }

    // Run the agent and process the streaming events.
    for event, err := range r.Run(ctx, userID, sessionID, userMsg, agent.RunConfig{
        StreamingMode: agent.StreamingModeSSE,
    }) {
        if err != nil {
            fmt.Printf("\nAGENT_ERROR: %v\n", err)
        } else if event.Partial {
            // Print partial responses as they are received.
            for _, p := range event.Content.Parts {
                fmt.Print(p.Text)
            }
        }
    }

    // After the run, check if there's an expected output key in the session state.
    if outputKey != "" {
        storedOutput, error := session.State().Get(outputKey)
        if error == nil {
            // Pretty-print the stored output if it's a JSON string.
            fmt.Printf("\n--- Session State ['%s']: ", outputKey)
            storedString, isString := storedOutput.(string)
            if isString {
                var prettyJSON map[string]interface{}
                if err := json.Unmarshal([]byte(storedString), &prettyJSON); err == nil {
                    indentedJSON, err := json.MarshalIndent(prettyJSON, "", "  ")
                    if err == nil {
                        fmt.Println(string(indentedJSON))
                    } else {
                        fmt.Println(storedString)
                    }
                } else {
                    fmt.Println(storedString)
                }
            } else {
                fmt.Println(storedOutput)
            }
            fmt.Println(strings.Repeat("-", 30))
        }
    }
}

func main() {
    ctx := context.Background()

    model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
    if err != nil {
        log.Fatalf("Failed to create model: %v", err)
    }

    capitalTool, err := functiontool.New(
        functiontool.Config{
            Name:        "get_capital_city",
            Description: "Retrieves the capital city for a given country.",
        },
        getCapitalCity,
    )
    if err != nil {
        log.Fatalf("Failed to create function tool: %v", err)
    }

    countryInputSchema := &genai.Schema{
        Type:        genai.TypeObject,
        Description: "Input for specifying a country.",
        Properties: map[string]*genai.Schema{
            "country": {
                Type:        genai.TypeString,
                Description: "The country to get information about.",
            },
        },
        Required: []string{"country"},
    }

    capitalAgentWithTool, err := llmagent.New(llmagent.Config{
        Name:        "capital_agent_tool",
        Model:       model,
        Description: "Retrieves the capital city using a specific tool.",
        Instruction: `You are a helpful agent that provides the capital city of a country using a tool.
The user will provide the country name in a JSON format like {"country": "country_name"}.
1. Extract the country name.
2. Use the 'get_capital_city' tool to find the capital.
3. Respond clearly to the user, stating the capital city found by the tool.`,
        Tools:       []tool.Tool{capitalTool},
        InputSchema: countryInputSchema,
        OutputKey:   "capital_tool_result",
    })
    if err != nil {
        log.Fatalf("Failed to create capital agent with tool: %v", err)
    }

    capitalInfoOutputSchema := &genai.Schema{
        Type:        genai.TypeObject,
        Description: "Schema for capital city information.",
        Properties: map[string]*genai.Schema{
            "capital": {
                Type:        genai.TypeString,
                Description: "The capital city of the country.",
            },
            "population_estimate": {
                Type:        genai.TypeString,
                Description: "An estimated population of the capital city.",
            },
        },
        Required: []string{"capital", "population_estimate"},
    }
    schemaJSON, _ := json.Marshal(capitalInfoOutputSchema)
    structuredInfoAgentSchema, err := llmagent.New(llmagent.Config{
        Name:        "structured_info_agent_schema",
        Model:       model,
        Description: "Provides capital and estimated population in a specific JSON format.",
        Instruction: fmt.Sprintf(`You are an agent that provides country information.
The user will provide the country name in a JSON format like {"country": "country_name"}.
Respond ONLY with a JSON object matching this exact schema:
%s
Use your knowledge to determine the capital and estimate the population. Do not use any tools.`, string(schemaJSON)),
        InputSchema:  countryInputSchema,
        OutputSchema: capitalInfoOutputSchema,
        OutputKey:    "structured_info_result",
    })
    if err != nil {
        log.Fatalf("Failed to create structured info agent: %v", err)
    }

    fmt.Println("--- Testing Agent with Tool ---")
    callAgent(ctx, capitalAgentWithTool, "capital_tool_result", `{"country": "France"}`)
    callAgent(ctx, capitalAgentWithTool, "capital_tool_result", `{"country": "Canada"}`)

    fmt.Println("\n\n--- Testing Agent with Output Schema (No Tool Use) ---")
    callAgent(ctx, structuredInfoAgentSchema, "structured_info_result", `{"country": "France"}`)
    callAgent(ctx, structuredInfoAgentSchema, "structured_info_result", `{"country": "Japan"}`)
}
// --- Full example code demonstrating LlmAgent with Tools vs. Output Schema ---

import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations;
import com.google.adk.tools.FunctionTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import com.google.genai.types.Schema;
import io.reactivex.rxjava3.core.Flowable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class LlmAgentExample {

  // --- 1. Define Constants ---
  private static final String MODEL_NAME = "gemini-2.0-flash";
  private static final String APP_NAME = "capital_agent_tool";
  private static final String USER_ID = "test_user_456";
  private static final String SESSION_ID_TOOL_AGENT = "session_tool_agent_xyz";
  private static final String SESSION_ID_SCHEMA_AGENT = "session_schema_agent_xyz";

  // --- 2. Define Schemas ---

  // Input schema used by both agents
  private static final Schema COUNTRY_INPUT_SCHEMA =
      Schema.builder()
          .type("OBJECT")
          .description("Input for specifying a country.")
          .properties(
              Map.of(
                  "country",
                  Schema.builder()
                      .type("STRING")
                      .description("The country to get information about.")
                      .build()))
          .required(List.of("country"))
          .build();

  // Output schema ONLY for the second agent
  private static final Schema CAPITAL_INFO_OUTPUT_SCHEMA =
      Schema.builder()
          .type("OBJECT")
          .description("Schema for capital city information.")
          .properties(
              Map.of(
                  "capital",
                  Schema.builder()
                      .type("STRING")
                      .description("The capital city of the country.")
                      .build(),
                  "population_estimate",
                  Schema.builder()
                      .type("STRING")
                      .description("An estimated population of the capital city.")
                      .build()))
          .required(List.of("capital", "population_estimate"))
          .build();

  // --- 3. Define the Tool (Only for the first agent) ---
  // Retrieves the capital city of a given country.
  public static Map<String, Object> getCapitalCity(
      @Annotations.Schema(name = "country", description = "The country to get capital for")
      String country) {
    System.out.printf("%n-- Tool Call: getCapitalCity(country='%s') --%n", country);
    Map<String, String> countryCapitals = new HashMap<>();
    countryCapitals.put("united states", "Washington, D.C.");
    countryCapitals.put("canada", "Ottawa");
    countryCapitals.put("france", "Paris");
    countryCapitals.put("japan", "Tokyo");

    String result =
        countryCapitals.getOrDefault(
            country.toLowerCase(), "Sorry, I couldn't find the capital for " + country + ".");
    System.out.printf("-- Tool Result: '%s' --%n", result);
    return Map.of("result", result); // Tools must return a Map
  }

  public static void main(String[] args){
    LlmAgentExample agentExample = new LlmAgentExample();
    FunctionTool capitalTool = FunctionTool.create(agentExample.getClass(), "getCapitalCity");

    // --- 4. Configure Agents ---

    // Agent 1: Uses a tool and output_key
    LlmAgent capitalAgentWithTool =
        LlmAgent.builder()
            .model(MODEL_NAME)
            .name("capital_agent_tool")
            .description("Retrieves the capital city using a specific tool.")
            .instruction(
              """
              You are a helpful agent that provides the capital city of a country using a tool.
              1. Extract the country name.
              2. Use the `get_capital_city` tool to find the capital.
              3. Respond clearly to the user, stating the capital city found by the tool.
              """)
            .tools(capitalTool)
            .inputSchema(COUNTRY_INPUT_SCHEMA)
            .outputKey("capital_tool_result") // Store final text response
            .build();

    // Agent 2: Uses an output schema
    LlmAgent structuredInfoAgentSchema =
        LlmAgent.builder()
            .model(MODEL_NAME)
            .name("structured_info_agent_schema")
            .description("Provides capital and estimated population in a specific JSON format.")
            .instruction(
                String.format("""
                You are an agent that provides country information.
                Respond ONLY with a JSON object matching this exact schema: %s
                Use your knowledge to determine the capital and estimate the population. Do not use any tools.
                """, CAPITAL_INFO_OUTPUT_SCHEMA.toJson()))
            // *** NO tools parameter here - using output_schema prevents tool use ***
            .inputSchema(COUNTRY_INPUT_SCHEMA)
            .outputSchema(CAPITAL_INFO_OUTPUT_SCHEMA) // Enforce JSON output structure
            .outputKey("structured_info_result") // Store final JSON response
            .build();

    // --- 5. Set up Session Management and Runners ---
    InMemorySessionService sessionService = new InMemorySessionService();

    sessionService.createSession(APP_NAME, USER_ID, null, SESSION_ID_TOOL_AGENT).blockingGet();
    sessionService.createSession(APP_NAME, USER_ID, null, SESSION_ID_SCHEMA_AGENT).blockingGet();

    Runner capitalRunner = new Runner(capitalAgentWithTool, APP_NAME, null, sessionService);
    Runner structuredRunner = new Runner(structuredInfoAgentSchema, APP_NAME, null, sessionService);

    // --- 6. Run Interactions ---
    System.out.println("--- Testing Agent with Tool ---");
    agentExample.callAgentAndPrint(
        capitalRunner, capitalAgentWithTool, SESSION_ID_TOOL_AGENT, "{\"country\": \"France\"}");
    agentExample.callAgentAndPrint(
        capitalRunner, capitalAgentWithTool, SESSION_ID_TOOL_AGENT, "{\"country\": \"Canada\"}");

    System.out.println("\n\n--- Testing Agent with Output Schema (No Tool Use) ---");
    agentExample.callAgentAndPrint(
        structuredRunner,
        structuredInfoAgentSchema,
        SESSION_ID_SCHEMA_AGENT,
        "{\"country\": \"France\"}");
    agentExample.callAgentAndPrint(
        structuredRunner,
        structuredInfoAgentSchema,
        SESSION_ID_SCHEMA_AGENT,
        "{\"country\": \"Japan\"}");
  }

  // --- 7. Define Agent Interaction Logic ---
  public void callAgentAndPrint(Runner runner, LlmAgent agent, String sessionId, String queryJson) {
    System.out.printf(
        "%n>>> Calling Agent: '%s' | Session: '%s' | Query: %s%n",
        agent.name(), sessionId, queryJson);

    Content userContent = Content.fromParts(Part.fromText(queryJson));
    final String[] finalResponseContent = {"No final response received."};
    Flowable<Event> eventStream = runner.runAsync(USER_ID, sessionId, userContent);

    // Stream event response
    eventStream.blockingForEach(event -> {
          if (event.finalResponse() && event.content().isPresent()) {
            event
                .content()
                .get()
                .parts()
                .flatMap(parts -> parts.isEmpty() ? Optional.empty() : Optional.of(parts.get(0)))
                .flatMap(Part::text)
                .ifPresent(text -> finalResponseContent[0] = text);
          }
        });

    System.out.printf("<<< Agent '%s' Response: %s%n", agent.name(), finalResponseContent[0]);

    // Retrieve the session again to get the updated state
    Session updatedSession =
        runner
            .sessionService()
            .getSession(APP_NAME, USER_ID, sessionId, Optional.empty())
            .blockingGet();

    if (updatedSession != null && agent.outputKey().isPresent()) {
      // Print to verify if the stored output looks like JSON (likely from output_schema)
      System.out.printf("--- Session State ['%s']: ", agent.outputKey().get());
      }
  }
}

(この例は基本的な概念を示しています。より複雑なエージェントは、スキーマ、コンテキスト制御、計画などを組み込むことがあります。)

関連概念(後続のトピック)

このページではLlmAgentの基本的な設定について説明しましたが、いくつかの関連概念はより高度な制御を提供し、他の場所で詳しく説明されています:

  • コールバック: before_model_callbackafter_model_callbackなどを使用して、実行ポイント(モデル呼び出しの前後、ツール呼び出しの前後)をインターセプトします。コールバックを参照してください。
  • マルチエージェント制御: 計画(planner)、エージェント転送の制御(disallow_transfer_to_parent, disallow_transfer_to_peers)、およびシステム全体の指示(global_instruction)を含む、エージェント対話の高度な戦略。マルチエージェントを参照してください。