LLMエージェント¶
LlmAgent(しばしばAgentと略されます)はADKにおけるコアコンポーネントであり、アプリケーションの「思考」部分として機能します。大規模言語モデル(LLM)の能力を活用して、推論、自然言語の理解、意思決定、応答の生成、ツールとの対話を行います。
事前に定義された実行パスに従う決定論的なワークフローエージェントとは異なり、LlmAgentの振る舞いは非決定的です。LLMを使用して指示とコンテキストを解釈し、動的に処理方法を決定したり、どのツールを使用するか(もし使用する場合)、あるいは他のエージェントに制御を移すかを判断します。
効果的なLlmAgentを構築するには、そのアイデンティティを定義し、指示を通じてその振る舞いを明確にガイドし、必要なツールと能力を備えさせることが含まれます。
エージェントのアイデンティティと目的の定義¶
まず、エージェントが何であるか、そして何のためにあるのかを確立する必要があります。
-
name(必須): 全てのエージェントには一意の文字列識別子が必要です。このnameは内部操作、特にエージェント同士が互いを参照したりタスクを委任したりする必要があるマルチエージェントシステムにおいて非常に重要です。エージェントの機能を反映した説明的な名前(例:customer_support_router,billing_inquiry_agent)を選択してください。userのような予約名は避けてください。 -
description(任意、マルチエージェントで推奨): エージェントの能力に関する簡潔な要約を提供します。この説明は主に、他のLLMエージェントがこのエージェントにタスクをルーティングすべきかどうかを判断するために使用されます。同僚と区別できるように、具体的に記述してください(例: 「請求エージェント」ではなく、「現在の請求書に関する問い合わせを処理します」)。 -
model(必須): このエージェントの推論を支える基盤となるLLMを指定します。これは"gemini-flash-latest"のような文字列識別子です。モデルの選択は、エージェントの能力、コスト、パフォーマンスに影響します。利用可能なオプションと考慮事項については、モデルページを参照してください。
エージェントのガイド:指示 (instruction)¶
instructionパラメータは、LlmAgentの振る舞いを形成する上で、おそらく最も重要な要素です。これは、エージェントに以下のことを伝える文字列(または文字列を返す関数)です:
- その中核となるタスクや目標。
- その性格やペルソナ(例:「あなたは親切なアシスタントです」、「あなたは機知に富んだ海賊です」)。
- その振る舞いに対する制約(例:「Xに関する質問にのみ答えてください」、「Yは決して明かさないでください」)。
toolsをどのように、いつ使用するか。各ツールの目的と、それが呼び出されるべき状況を説明し、ツール自体の説明を補足する必要があります。- 望ましい出力フォーマット(例:「JSONで応答してください」、「箇条書きリストで提供してください」)。
効果的な指示のためのヒント:
- 明確かつ具体的に: 曖昧さを避けてください。望ましい行動と結果を明確に記述します。
- マークダウンを使用: 複雑な指示の可読性を向上させるために、見出しやリストなどを使用します。
- 例を提供する (Few-Shot): 複雑なタスクや特定の出力フォーマットの場合、指示の中に直接例を含めます。
- ツールの使用をガイド: ツールをリストアップするだけでなく、エージェントがいつ、なぜそれらを使用すべきかを説明します。
状態 (State):
- 指示は文字列テンプレートであり、
{var}構文を使用して動的な値を指示に挿入できます。 {var}は、varという名前の状態変数の値を挿入するために使用されます。{artifact.var}は、varという名前のアーティファクトのテキストコンテンツを挿入するために使用されます。- 状態変数またはアーティファクトが存在しない場合、エージェントはエラーを発生させます。エラーを無視したい場合は、変数名の末尾に
?を付けて{var?}のようにすることができます。
# 例: 指示の追加
capital_agent = LlmAgent(
model="gemini-flash-latest",
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-flash-latest")
.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-flash-latest",
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-flash-latest")
.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_p、top_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,
)
]
)
)
データの構造化 (input_schema, output_schema, output_key)¶
LLM Agentとの構造化されたデータ交換を必要とするシナリオのために、ADKはスキーマ定義を使用して期待される入力と望ましい出力フォーマットを定義するメカニズムを提供します。
-
input_schema(任意): 期待される入力構造を表すスキーマを定義します。設定されている場合、このエージェントに渡されるユーザーメッセージの内容は、このスキーマに準拠したJSON文字列でなければなりません。あなたの指示は、ユーザーまたは先行するエージェントをそれに従ってガイドする必要があります。 -
output_schema(任意): 望ましい出力構造を表すスキーマを定義します。設定されている場合、エージェントの最終応答は、このスキーマに準拠したJSON文字列でなければなりません。
警告: output_schema と tools の併用
同じ LLM リクエストで output_schema と tools を併用できるのは、Gemini 3.0 を含む一部のモデルに限られます。
それ以外のモデルでは、ADK の function tools) を使った回避策は安定して動作しない可能性があります。
そのような場合は、出力フォーマットを別途処理する sub-agent の利用を検討してください。
output_key(任意): 文字列キーを提供します。設定されている場合、エージェントの最終応答のテキスト内容は、このキーの下でセッションの状態辞書に自動的に保存されます。これは、エージェント間やワークフローのステップ間で結果を渡すのに便利です。- Pythonでは、
session.state[output_key] = agent_response_textのようになります。 - Javaでは、
session.state().put(outputKey, agentResponseText)です。 - Golangでは、コールバックハンドラ内で
ctx.State().Set(output_key, agentResponseText)となります。
- Pythonでは、
入力および出力スキーマは、通常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(CAPITAL_OUTPUT) // JSON出力を強制
.outputKey("found_capital") // 結果を state.get("found_capital") に保存
// ここでは tools(getCapitalCity) を効果的に使用できない
.build();
コンテキストの管理 (include_contents)¶
エージェントが以前の会話履歴を受け取るかどうかを制御します。
include_contents(任意、デフォルト:'default'):contents(履歴)がLLMに送信されるかどうかを決定します。'default': エージェントは関連する会話履歴を受け取ります。'none': エージェントは以前のcontentsを受け取りません。現在の指示と現在のターンで提供された入力のみに基づいて動作します(ステートレスなタスクや特定のコンテキストを強制する場合に便利です)。
プランナー¶
planner (任意): BasePlannerインスタンスを割り当てて、実行前の複数ステップの推論と計画を有効にします。主なプランナーは2つあります:
-
BuiltInPlanner: モデルの組み込み計画機能(例: Geminiの思考機能)を活用します。詳細と例については、Gemini Thinkingを参照してください。ここで、
thinking_budgetパラメータは、応答を生成する際に使用する思考トークンの数をモデルにガイドします。include_thoughtsパラメータは、モデルが応答に生の思考や内部の推論プロセスを含めるべきかどうかを制御します。 -
PlanReActPlanner: このプランナーは、モデルに出力で特定の構造に従うように指示します:まず計画を立て、次に行動(ツールの呼び出しなど)を実行し、そのステップの推論を提供します。特に、組み込みの「思考」機能を持たないモデルに役立ちます。from google.adk import Agent from google.adk.planners import PlanReActPlanner my_agent = Agent( model="gemini-flash-latest", planner=PlanReActPlanner(), # ... ここにツールを記述 )エージェントの応答は、構造化されたフォーマットに従います:
[user]: ai news [google_search_agent]: /*PLANNING*/ 1. 人工知能に関する最新の更新とヘッドラインを得るために、「最新のAIニュース」でGoogle検索を実行する。 2. 検索結果からの情報を統合して、最近のAIニュースの要約を提供する。 /*ACTION*/ /*REASONING*/ 検索結果は、企業の動向、研究のブレークスルー、応用など、さまざまな側面をカバーする最近のAIニュースの包括的な概要を提供しています。ユーザーの要求に答えるのに十分な情報があります。 /*FINAL_ANSWER*/ 最近のAIニュースの要約はこちらです: ....
コード実行¶
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())
// 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.
import { LlmAgent, FunctionTool, InMemoryRunner, isFinalResponse } from '@google/adk';
import { createUserContent, Schema, Type } from '@google/genai';
import type { Part } from '@google/genai';
import { z } from 'zod';
// --- 1. Define Constants ---
const APP_NAME = "capital_app_ts";
const USER_ID = "test_user_789";
const SESSION_ID_TOOL_AGENT = "session_tool_agent_ts";
const SESSION_ID_SCHEMA_AGENT = "session_schema_agent_ts";
const MODEL_NAME = "gemini-2.5-flash"; // Using flash for speed
// --- 2. Define Schemas ---
// A. Schema for the Tool's parameters (using Zod)
const CountryInput = z.object({
country: z.string().describe('The country to get the capital for.'),
});
// B. Output schema ONLY for the second agent (using ADK's Schema type)
const CapitalInfoOutputSchema: Schema = {
type: Type.OBJECT,
description: "Schema for capital city information.",
properties: {
capital: {
type: Type.STRING,
description: "The capital city of the country."
},
population_estimate: {
type: Type.STRING,
description: "An estimated population of the capital city."
},
},
required: ["capital", "population_estimate"],
};
// --- 3. Define the Tool (Only for the first agent) ---
async function getCapitalCity(params: z.infer<typeof CountryInput>): Promise<{ result: string }> {
console.log(`\n-- Tool Call: getCapitalCity(country='${params.country}') --`);
const capitals: Record<string, string> = {
'united states': 'Washington, D.C.',
'canada': 'Ottawa',
'france': 'Paris',
'japan': 'Tokyo',
};
const result = capitals[params.country.toLowerCase()] ??
`Sorry, I couldn't find the capital for ${params.country}.`;
console.log(`-- Tool Result: '${result}' --`);
return { result: result }; // Tools must return an object
}
// --- 4. Configure Agents ---
// Agent 1: Uses a tool and outputKey
const getCapitalCityTool = new FunctionTool({
name: 'get_capital_city',
description: 'Retrieves the capital city for a given country',
parameters: CountryInput,
execute: getCapitalCity,
});
const capitalAgentWithTool = new 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 with a JSON object with the key 'capital' and the value as the capital city.
`,
tools: [getCapitalCityTool],
outputKey: "capital_tool_result", // Store final text response
});
// Agent 2: Uses outputSchema (NO tools possible)
const structuredInfoAgentSchema = new LlmAgent({
model: MODEL_NAME,
name: 'structured_info_agent_schema',
description: 'Provides capital and estimated population in a specific JSON format.',
instruction: `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.stringify(CapitalInfoOutputSchema, null, 2)}
Use your knowledge to determine the capital and estimate the population. Do not use any tools.
`,
// *** NO tools parameter here - using outputSchema prevents tool use ***
outputSchema: CapitalInfoOutputSchema,
outputKey: "structured_info_result",
});
// --- 5. Define Agent Interaction Logic ---
async function callAgentAndPrint(
runner: InMemoryRunner,
agent: LlmAgent,
sessionId: string,
queryJson: string
) {
console.log(`\n>>> Calling Agent: '${agent.name}' | Query: ${queryJson}`);
const message = createUserContent(queryJson);
let finalResponseContent = "No final response received.";
for await (const event of runner.runAsync({ userId: USER_ID, sessionId: sessionId, newMessage: message })) {
if (isFinalResponse(event) && event.content?.parts?.length) {
finalResponseContent = event.content.parts.map((part: Part) => part.text ?? '').join('');
}
}
console.log(`<<< Agent '${agent.name}' Response: ${finalResponseContent}`);
// Check the session state
const currentSession = await runner.sessionService.getSession({ appName: APP_NAME, userId: USER_ID, sessionId: sessionId });
if (!currentSession) {
console.log(`--- Session not found: ${sessionId} ---`);
return;
}
const storedOutput = currentSession.state[agent.outputKey!];
console.log(`--- Session State ['${agent.outputKey}']: `);
try {
// Attempt to parse and pretty print if it's JSON
const parsedOutput = JSON.parse(storedOutput as string);
console.log(JSON.stringify(parsedOutput, null, 2));
} catch (e) {
// Otherwise, print as a string
console.log(storedOutput);
}
console.log("-".repeat(30));
}
// --- 6. Run Interactions ---
async function main() {
// Set up runners for each agent
const capitalRunner = new InMemoryRunner({ appName: APP_NAME, agent: capitalAgentWithTool });
const structuredRunner = new InMemoryRunner({ appName: APP_NAME, agent: structuredInfoAgentSchema });
// Create sessions
console.log("--- Creating Sessions ---");
await capitalRunner.sessionService.createSession({ appName: APP_NAME, userId: USER_ID, sessionId: SESSION_ID_TOOL_AGENT });
await structuredRunner.sessionService.createSession({ appName: APP_NAME, userId: USER_ID, sessionId: SESSION_ID_SCHEMA_AGENT });
console.log("\n--- Testing Agent with Tool ---");
await callAgentAndPrint(capitalRunner, capitalAgentWithTool, SESSION_ID_TOOL_AGENT, '{"country": "France"}');
await callAgentAndPrint(capitalRunner, capitalAgentWithTool, SESSION_ID_TOOL_AGENT, '{"country": "Canada"}');
console.log("\n\n--- Testing Agent with Output Schema (No Tool Use) ---");
await callAgentAndPrint(structuredRunner, structuredInfoAgentSchema, SESSION_ID_SCHEMA_AGENT, '{"country": "France"}');
await callAgentAndPrint(structuredRunner, structuredInfoAgentSchema, SESSION_ID_SCHEMA_AGENT, '{"country": "Japan"}');
}
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_callback、after_model_callbackなどを使用して、実行ポイント(モデル呼び出しの前後、ツール呼び出しの前後)をインターセプトします。コールバックを参照してください。 - マルチエージェント制御: 計画(
planner)、エージェント転送の制御(disallow_transfer_to_parent,disallow_transfer_to_peers)、およびシステム全体の指示(global_instruction)を含む、エージェント対話の高度な戦略。マルチエージェントを参照してください。
Kotlin の例の補足¶
英語原文に追加された Kotlin の LlmAgent 例を以下にも掲載します。
基本的な識別情報¶
val capitalAgent =
LlmAgent(
name = "capital_agent",
model = Gemini(name = "gemini-flash-latest"),
description = "Answers user questions about the capital city of a given country.",
)
指示文の構成¶
val instructedAgent =
LlmAgent(
name = "capital_agent",
model = Gemini(name = "gemini-flash-latest"),
instruction =
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 `getCapitalCity` 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."
""".trimIndent(),
),
)
ツール定義¶
class CapitalService {
@Tool(description = "Retrieves the capital city for a given country.")
fun getCapitalCity(
@Param("The country to get capital for.") country: String,
): String {
val capitals = mapOf("france" to "Paris", "japan" to "Tokyo", "canada" to "Ottawa")
return capitals[country.lowercase()] ?: "Sorry, I don't know the capital of $country."
}
}
ツールの使用¶
// Note: generatedTools() is generated by KSP for classes containing @Tool annotated functions.
// In a real project, you would need to set up the ADK KSP processor.
// val agentWithTools = LlmAgent(
// name = "capital_agent",
// model = Gemini(name = "gemini-flash-latest"),
// tools = capitalService.generatedTools()
// )
生成設定¶
val agentWithConfig =
LlmAgent(
name = "capital_agent",
model = Gemini(name = "gemini-flash-latest"),
generateContentConfig =
GenerateContentConfig(
temperature = 0.2f,
maxOutputTokens = 250,
),
)
完全な例¶
val finalAgent =
LlmAgent(
name = "capital_agent",
model = Gemini(name = "gemini-flash-latest"),
description = "Answers user questions about the capital city of a given country.",
instruction =
Instruction(
"You are an agent that provides the capital city of a country...",
),
// tools = capitalService.generatedTools() // Assuming tools are added
)
val sessionService = InMemorySessionService()
val runner = InMemoryRunner(finalAgent, "capital_app", sessionService)
val userMessage = Content(parts = listOf(Part(text = "What is the capital of France?")))
// Use runAsync to get a Flow of events
runner.runAsync(
userId = "user123",
sessionId = "session456",
newMessage = userMessage,
).collect {
event ->
if (event.isFinalResponse) {
val finalResponse = event.content?.parts?.firstOrNull()?.text
println(finalResponse)
}
}
コード実行例の補足¶
英語原文に含まれているコード実行例を以下にも掲載します。
Python¶
# 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.
import asyncio
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.code_executors import BuiltInCodeExecutor
from google.genai import types
AGENT_NAME = "calculator_agent"
APP_NAME = "calculator"
USER_ID = "user1234"
SESSION_ID = "session_code_exec_async"
GEMINI_MODEL = "gemini-2.0-flash"
# Agent Definition
code_agent = LlmAgent(
name=AGENT_NAME,
model=GEMINI_MODEL,
code_executor=BuiltInCodeExecutor(),
instruction="""You are a calculator agent.
When given a mathematical expression, write and execute Python code to calculate the result.
Return only the final numerical result as plain text, without markdown or code blocks.
""",
description="Executes Python code to perform calculations.",
)
# Session and Runner
session_service = InMemorySessionService()
session = asyncio.run(session_service.create_session(
app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID
))
runner = Runner(agent=code_agent, app_name=APP_NAME,
session_service=session_service)
# Agent Interaction (Async)
async def call_agent_async(query):
content = types.Content(role="user", parts=[types.Part(text=query)])
print(f"\n--- Running Query: {query} ---")
final_response_text = "No final text response captured."
try:
# Use run_async
async for event in runner.run_async(
user_id=USER_ID, session_id=SESSION_ID, new_message=content
):
print(f"Event ID: {event.id}, Author: {event.author}")
# --- Check for specific parts FIRST ---
has_specific_part = False
if event.content and event.content.parts:
for part in event.content.parts: # Iterate through all parts
if part.executable_code:
# Access the actual code string via .code
print(
f" Debug: Agent generated code:\n```python\n{part.executable_code.code}\n```"
)
has_specific_part = True
elif part.code_execution_result:
# Access outcome and output correctly
print(
f" Debug: Code Execution Result: {part.code_execution_result.outcome} - Output:\n{part.code_execution_result.output}"
)
has_specific_part = True
# Also print any text parts found in any event for debugging
elif part.text and not part.text.isspace():
print(f" Text: '{part.text.strip()}'")
# Do not set has_specific_part=True here, as we want the final response logic below
# --- Check for final response AFTER specific parts ---
# Only consider it final if it doesn't have the specific code parts we just handled
if not has_specific_part and event.is_final_response():
if (
event.content
and event.content.parts
and event.content.parts[0].text
):
final_response_text = event.content.parts[0].text.strip()
print(f"==> Final Agent Response: {final_response_text}")
else:
print(
"==> Final Agent Response: [No text content in final event]")
except Exception as e:
print(f"ERROR during agent run: {e}")
print("-" * 30)
# Main async function to run the examples
async def main():
await call_agent_async("Calculate the value of (5 + 7) * 3")
await call_agent_async("What is 10 factorial?")
# Execute the main async function
try:
asyncio.run(main())
except RuntimeError as e:
# Handle specific error when running asyncio.run in an already running loop (like Jupyter/Colab)
if "cannot be called from a running event loop" in str(e):
print("\nRunning in an existing event loop (like Colab/Jupyter).")
print("Please run `await main()` in a notebook cell instead.")
# If in an interactive environment like a notebook, you might need to run:
# await main()
else:
raise e # Re-raise other runtime errors
Java¶
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.adk.tools.BuiltInCodeExecutionTool;
import com.google.common.collect.ImmutableList;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
public class CodeExecutionAgentApp {
private static final String AGENT_NAME = "calculator_agent";
private static final String APP_NAME = "calculator";
private static final String USER_ID = "user1234";
private static final String SESSION_ID = "session_code_exec_sync";
private static final String GEMINI_MODEL = "gemini-2.0-flash";
/**
* Calls the agent with a query and prints the interaction events and final response.
*
* @param runner The runner instance for the agent.
* @param query The query to send to the agent.
*/
public static void callAgent(Runner runner, String query) {
Content content =
Content.builder().role("user").parts(ImmutableList.of(Part.fromText(query))).build();
InMemorySessionService sessionService = (InMemorySessionService) runner.sessionService();
Session session =
sessionService
.createSession(APP_NAME, USER_ID, /* state= */ null, SESSION_ID)
.blockingGet();
System.out.println("\n--- Running Query: " + query + " ---");
final String[] finalResponseText = {"No final text response captured."};
try {
runner
.runAsync(session.userId(), session.id(), content)
.forEach(
event -> {
System.out.println("Event ID: " + event.id() + ", Author: " + event.author());
boolean hasSpecificPart = false;
if (event.content().isPresent() && event.content().get().parts().isPresent()) {
for (Part part : event.content().get().parts().get()) {
if (part.executableCode().isPresent()) {
System.out.println(
" Debug: Agent generated code:\n```python\n"
+ part.executableCode().get().code()
+ "\n```");
hasSpecificPart = true;
} else if (part.codeExecutionResult().isPresent()) {
System.out.println(
" Debug: Code Execution Result: "
+ part.codeExecutionResult().get().outcome()
+ " - Output:\n"
+ part.codeExecutionResult().get().output());
hasSpecificPart = true;
} else if (part.text().isPresent() && !part.text().get().trim().isEmpty()) {
System.out.println(" Text: '" + part.text().get().trim() + "'");
}
}
}
if (!hasSpecificPart && event.finalResponse()) {
if (event.content().isPresent()
&& event.content().get().parts().isPresent()
&& !event.content().get().parts().get().isEmpty()
&& event.content().get().parts().get().get(0).text().isPresent()) {
finalResponseText[0] =
event.content().get().parts().get().get(0).text().get().trim();
System.out.println("==> Final Agent Response: " + finalResponseText[0]);
} else {
System.out.println(
"==> Final Agent Response: [No text content in final event]");
}
}
});
} catch (Exception e) {
System.err.println("ERROR during agent run: " + e.getMessage());
e.printStackTrace();
}
System.out.println("------------------------------");
}
public static void main(String[] args) {
BuiltInCodeExecutionTool codeExecutionTool = new BuiltInCodeExecutionTool();
BaseAgent codeAgent =
LlmAgent.builder()
.name(AGENT_NAME)
.model(GEMINI_MODEL)
.tools(ImmutableList.of(codeExecutionTool))
.instruction(
"""
You are a calculator agent.
When given a mathematical expression, write and execute Python code to calculate the result.
Return only the final numerical result as plain text, without markdown or code blocks.
""")
.description("Executes Python code to perform calculations.")
.build();
InMemorySessionService sessionService = new InMemorySessionService();
Runner runner = new Runner(codeAgent, APP_NAME, null, sessionService);
callAgent(runner, "Calculate the value of (5 + 7) * 3");
callAgent(runner, "What is 10 factorial?");
}
}