Agent Development Kitを触ってみる #3
投稿日: 2025/04/28
before_model_callbackなどを実装します

今回はADKのチュートリアルの3章以降をやっていきます。
Agent Development Kit
Build powerful multi-agent systems with Agent Development Kit
google.github.io

前回の記事はこちら。
tech.n-island.dev

複数のエージェントを使ってエージェントチームを構築します。
エージェントを複数作成してチームを構築する利点として、保守が容易になる、スケーラビリティの向上などが挙げられます。
ルートエージェントと2つのサブエージェントからなるエージェントチームを構築していきます。
本記事ではノートブックで実行することを前提に記述します。
各エージェントに持たせるツールを以下のようにそれぞれ定義します。
def get_weather(city: str) -> dict:
"""Retrieves the current weather report for a specified city.
Args:
city (str): The name of the city (e.g., "New York", "London", "Tokyo").
Returns:
dict: A dictionary containing the weather information.
Includes a 'status' key ('success' or 'error').
If 'success', includes a 'report' key with weather details.
If 'error', includes an 'error_message' key.
"""
# Best Practice: Log tool execution for easier debugging
print(f"--- Tool: get_weather called for city: {city} ---")
city_normalized = city.lower().replace(" ", "") # Basic input normalization
# Mock weather data for simplicity
mock_weather_db = {
"newyork": {"status": "success", "report": "The weather in New York is sunny with a temperature of 25°C."},
"london": {"status": "success", "report": "It's cloudy in London with a temperature of 15°C."},
"tokyo": {"status": "success", "report": "Tokyo is experiencing light rain and a temperature of 18°C."},
}
# Best Practice: Handle potential errors gracefully within the tool
if city_normalized in mock_weather_db:
return mock_weather_db[city_normalized]
else:
return {"status": "error", "error_message": f"Sorry, I don't have weather information for '{city}'."}
def say_hello(name: str = "there") -> str:
"""Provides a simple greeting, optionally addressing the user by name.
Args:
name (str, optional): The name of the person to greet. Defaults to "there".
Returns:
str: A friendly greeting message.
"""
print(f"--- Tool: say_hello called with name: {name} ---")
return f"Hello, {name}!"
def say_goodbye() -> str:
"""Provides a simple farewell message to conclude the conversation."""
print(f"--- Tool: say_goodbye called ---")
return "Goodbye! Have a great day."
print("Agent tools defined.")サブエージェントを定義します。定義の方法はこれまでと同様です。
今回は全てのエージェントをgemini/gemini-2.0-flashにします。あらかじめノートブックと同階層に.envを作成し、GEMINI_API_KEY={APIキー}を書き込んでください。
from dotenv import load_dotenv
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
load_dotenv()
# --- Greeting Agent ---
greeting_agent = None
try:
greeting_agent = Agent(
model=LiteLlm(model="gemini/gemini-2.0-flash"),
name="greeting_agent",
instruction="You are the Greeting Agent. Your ONLY task is to provide a friendly greeting to the user. "
"Use the 'say_hello' tool to generate the greeting. "
"If the user provides their name, make sure to pass it to the tool. "
"Do not engage in any other conversation or tasks.",
description="Handles simple greetings and hellos using the 'say_hello' tool.", # Crucial for delegation
tools=[say_hello],
)
print(f"✅ Agent '{greeting_agent.name}' created.")
except Exception as e:
print(f"❌ Could not create Greeting agent. Check API Key. Error: {e}")
# --- Farewell Agent ---
farewell_agent = None
try:
farewell_agent = Agent(
model=LiteLlm(model="gemini/gemini-2.0-flash"),
name="farewell_agent",
instruction="You are the Farewell Agent. Your ONLY task is to provide a polite goodbye message. "
"Use the 'say_goodbye' tool when the user indicates they are leaving or ending the conversation "
"(e.g., using words like 'bye', 'goodbye', 'thanks bye', 'see you'). "
"Do not perform any other actions.",
description="Handles simple farewells and goodbyes using the 'say_goodbye' tool.", # Crucial for delegation
tools=[say_goodbye],
)
print(f"✅ Agent '{farewell_agent.name}' created.")
except Exception as e:
print(f"❌ Could not create Farewell agent. Check API Key. Error: {e}")続いてルートエージェントを定義します。
エージェントを定義する方法は基本的には同じですが、Agentの引数にsub_agentsを渡して
root_agent = None
runner_root = None # Initialize runner
if greeting_agent and farewell_agent and 'get_weather' in globals():
weather_agent_team = Agent(
name="weather_agent_v2", # Give it a new version name
model=LiteLlm(model="gemini/gemini-2.0-flash"),
description="The main coordinator agent. Handles weather requests and delegates greetings/farewells to specialists.",
instruction="You are the main Weather Agent coordinating a team. Your primary responsibility is to provide weather information. "
"Use the 'get_weather' tool ONLY for specific weather requests (e.g., 'weather in London'). "
"You have specialized sub-agents: "
"1. 'greeting_agent': Handles simple greetings like 'Hi', 'Hello'. Delegate to it for these. "
"2. 'farewell_agent': Handles simple farewells like 'Bye', 'See you'. Delegate to it for these. "
"Analyze the user's query. If it's a greeting, delegate to 'greeting_agent'. If it's a farewell, delegate to 'farewell_agent'. "
"If it's a weather request, handle it yourself using 'get_weather'. "
"For anything else, respond appropriately or state you cannot handle it.",
tools=[get_weather],
# Key change: Link the sub-agents here!
sub_agents=[greeting_agent, farewell_agent]
)
print(f"✅ Root Agent '{weather_agent_team.name}' created with sub-agents: {[sa.name for sa in weather_agent_team.sub_agents]}")
else:
print("❌ Cannot create root agent because one or more sub-agents failed to initialize or 'get_weather' tool is missing.")
if not greeting_agent:
print(" - Greeting Agent is missing.")
if not farewell_agent:
print(" - Farewell Agent is missing.")
if 'get_weather' not in globals():
print(" - get_weather function is missing.")エージェントチームを実行するために、ヘルパー関数を以下のように定義します。
import asyncio
from google.genai import types
from google.adk.runners import Runner
async def call_agent_async(query: str, runner: Runner, user_id: str, session_id: str):
"""Sends a query to the agent and prints the final response."""
print(f"\n>>> User Query: {query}")
content = types.Content(role='user', parts=[types.Part(text=query)])
final_response_text = "Agent did not produce a final response."
async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=content):
if event.is_final_response():
if event.content and event.content.parts:
final_response_text = event.content.parts[0].text
elif event.actions and event.actions.escalate:
final_response_text = f"Agent escalated: {event.error_message or 'No specific message.'}"
break
print(f"<<< Agent Response: {final_response_text}")そのうえで、以下のコードでエージェントチームを実行します。
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
root_agent_var_name = 'root_agent'
if 'weather_agent_team' in globals():
root_agent_var_name = 'weather_agent_team'
elif 'root_agent' not in globals():
print("⚠️ Root agent ('root_agent' or 'weather_agent_team') not found. Cannot define run_team_conversation.")
root_agent = None
if root_agent_var_name in globals() and globals()[root_agent_var_name]:
async def run_team_conversation():
print("\n--- Testing Agent Team Delegation ---")
# InMemorySessionService is simple, non-persistent storage for this tutorial.
session_service = InMemorySessionService()
# Define constants for identifying the interaction context
APP_NAME = "weather_tutorial_agent_team"
USER_ID = "user_1_agent_team"
SESSION_ID = "session_001_agent_team" # Using a fixed ID for simplicity
# Create the specific session where the conversation will happen
session = session_service.create_session(
app_name=APP_NAME,
user_id=USER_ID,
session_id=SESSION_ID
)
print(f"Session created: App='{APP_NAME}', User='{USER_ID}', Session='{SESSION_ID}'")
# --- Get the actual root agent object ---
# Use the determined variable name
actual_root_agent = globals()[root_agent_var_name]
# Create a runner specific to this agent team test
runner_agent_team = Runner(
agent=actual_root_agent, # Use the root agent object
app_name=APP_NAME, # Use the specific app name
session_service=session_service # Use the specific session service
)
# Corrected print statement to show the actual root agent's name
print(f"Runner created for agent '{actual_root_agent.name}'.")
# Always interact via the root agent's runner, passing the correct IDs
await call_agent_async(query = "Hello there!",
runner=runner_agent_team,
user_id=USER_ID,
session_id=SESSION_ID)
await call_agent_async(query = "What is the weather in New York?",
runner=runner_agent_team,
user_id=USER_ID,
session_id=SESSION_ID)
await call_agent_async(query = "Thanks, bye!",
runner=runner_agent_team,
user_id=USER_ID,
session_id=SESSION_ID)
await run_team_conversation()
else:
print("\n⚠️ Skipping agent team conversation as the root agent was not successfully defined in the previous step.")結果は以下のようになりました。
Default value is not supported in function declaration schema for Google AI.という警告が出ていますが、これはGoogle AIが関数のデフォルト値に対応していないことが原因のようです。
--- Testing Agent Team Delegation ---
Session created: App='weather_tutorial_agent_team', User='user_1_agent_team', Session='session_001_agent_team'
Runner created for agent 'weather_agent_v2'.
>>> User Query: Hello there!
Default value is not supported in function declaration schema for Google AI.
Default value is not supported in function declaration schema for Google AI.
--- Tool: say_hello called with name: None ---
Default value is not supported in function declaration schema for Google AI.
<<< Agent Response: Hello, None!
>>> User Query: What is the weather in New York?
--- Tool: get_weather called for city: New York ---
<<< Agent Response: The weather in New York is sunny with a temperature of 25°C.
>>> User Query: Thanks, bye!
--- Tool: say_goodbye called ---
<<< Agent Response: Goodbye! Have a great day.以下の様にsay_hello関数からデフォルト値のある変数を削除することで、警告が出なくなりました。
def say_hello(name: str) -> str:
"""Provides a simple greeting, optionally addressing the user by name.
Args:
name (str, optional): The name of the person to greet. Defaults to "there".
Returns:
str: A friendly greeting message.
"""
if not name:
name = "there"
print(f"--- Tool: say_hello called with name: {name} ---")
return f"Hello, {name}!"このようにしてエージェントチームを実行した結果は以下のようになりました。
出力をみると、各クエリに対して正しくツールが使われており、サブエージェントが動作していることがわかります。
--- Testing Agent Team Delegation ---
Session created: App='weather_tutorial_agent_team', User='user_1_agent_team', Session='session_001_agent_team'
Runner created for agent 'weather_agent_v2'.
>>> User Query: Hello there!
--- Tool: say_hello called with name: there ---
<<< Agent Response: Hello, there!
>>> User Query: What is the weather in New York?
--- Tool: get_weather called for city: New York ---
<<< Agent Response: The weather in New York is sunny with a temperature of 25°C.
>>> User Query: Thanks, bye!
--- Tool: say_goodbye called ---
<<< Agent Response: Goodbye! Have a great day.ここまでで実装してきたエージェントは過去の会話やユーザーの設定を保持していません。
ADKでは、ツールやエージェントが読み書きできるStateを使うことができます。
新しいノートブックで進めていきます。
APIキーを読み込んでおきます。
from dotenv import load_dotenv
load_dotenv()これまでと同様にセッションを作りますが、stateを渡していることに注目してください。
from google.adk.sessions import InMemorySessionService
session_service_stateful = InMemorySessionService()
print("✅ New InMemorySessionService created for state demonstration.")
APP_NAME = "stateful_demo_app"
SESSION_ID_STATEFUL = "session_state_demo_001"
USER_ID_STATEFUL = "user_state_demo"
# 初期状態(気温単位)を設定
initial_state = {
"user_preference_temperature_unit": "Celsius"
}
# セッションの作成
session_stateful = session_service_stateful.create_session(
app_name=APP_NAME,
user_id=USER_ID_STATEFUL,
session_id=SESSION_ID_STATEFUL,
state=initial_state # <<< 初期状態を渡す
)
print(f"✅ Session '{SESSION_ID_STATEFUL}' created for user '{USER_ID_STATEFUL}'.")
# セッションの確認
retrieved_session = session_service_stateful.get_session(app_name=APP_NAME,
user_id=USER_ID_STATEFUL,
session_id = SESSION_ID_STATEFUL)
print("\n--- Initial Session State ---")
if retrieved_session:
print(retrieved_session.state)
else:
print("Error: Could not retrieve session.")天気の情報を取得するツールを定義します。
引数にToolContextを受け取っていることに着目してください。ツール関数の最後のパラメータとして定義されている場合、ADKが自動的に挿入するようです。
また、.state.get()によってデフォルト値を指定しつつ読み込むことができます。これにより、エラーを回避することが可能です。
from google.adk.tools.tool_context import ToolContext
def get_weather_stateful(city: str, tool_context: ToolContext) -> dict:
"""Retrieves weather, converts temp unit based on session state."""
print(f"--- Tool: get_weather_stateful called for {city} ---")
# --- Read preference from state ---
preferred_unit = tool_context.state.get("user_preference_temperature_unit", "Celsius") # デフォルト値: Celsius
print(f"--- Tool: Reading state 'user_preference_temperature_unit': {preferred_unit} ---")
city_normalized = city.lower().replace(" ", "")
# モックデータ
mock_weather_db = {
"newyork": {"temp_c": 25, "condition": "sunny"},
"london": {"temp_c": 15, "condition": "cloudy"},
"tokyo": {"temp_c": 18, "condition": "light rain"},
}
if city_normalized in mock_weather_db:
data = mock_weather_db[city_normalized]
temp_c = data["temp_c"]
condition = data["condition"]
# stateに従って温度単位を変換
if preferred_unit == "Fahrenheit":
temp_value = (temp_c * 9/5) + 32 # Calculate Fahrenheit
temp_unit = "°F"
else: # Default to Celsius
temp_value = temp_c
temp_unit = "°C"
report = f"The weather in {city.capitalize()} is {condition} with a temperature of {temp_value:.0f}{temp_unit}."
result = {"status": "success", "report": report}
print(f"--- Tool: Generated report in {preferred_unit}. Result: {result} ---")
# 最後に確認した都市をstateに保存
tool_context.state["last_city_checked_stateful"] = city
print(f"--- Tool: Updated state 'last_city_checked_stateful': {city} ---")
return result
else:
# Handle city not found
error_msg = f"Sorry, I don't have weather information for '{city}'."
print(f"--- Tool: City '{city}' not found. ---")
return {"status": "error", "error_message": error_msg}
print("✅ State-aware 'get_weather_stateful' tool defined.")
def say_hello(name: str = "") -> str:
"""Provides a simple greeting, optionally addressing the user by name.
Args:
name (str, optional): The name of the person to greet. Defaults to "there".
Returns:
str: A friendly greeting message.
"""
if not name:
name = "there"
print(f"--- Tool: say_hello called with name: {name} ---")
return f"Hello, {name}!"
def say_goodbye() -> str:
"""Provides a simple farewell message to conclude the conversation."""
print(f"--- Tool: say_goodbye called ---")
return "Goodbye! Have a great day."
print("Agent tools defined.")エージェントを定義します。
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.runners import Runner
greeting_agent = None
try:
greeting_agent = Agent(
model=LiteLlm("gemini/gemini-2.0-flash"),
name="greeting_agent",
instruction="You are the Greeting Agent. Your ONLY task is to provide a friendly greeting using the 'say_hello' tool. Do nothing else.",
description="Handles simple greetings and hellos using the 'say_hello' tool.",
tools=[say_hello],
)
print(f"✅ Agent '{greeting_agent.name}' redefined.")
except Exception as e:
print(f"❌ Could not redefine Greeting agent. Error: {e}")
farewell_agent = None
try:
farewell_agent = Agent(
model=LiteLlm("gemini/gemini-2.0-flash"),
name="farewell_agent",
instruction="You are the Farewell Agent. Your ONLY task is to provide a polite goodbye message using the 'say_goodbye' tool. Do not perform any other actions.",
description="Handles simple farewells and goodbyes using the 'say_goodbye' tool.",
tools=[say_goodbye],
)
print(f"✅ Agent '{farewell_agent.name}' redefined.")
except Exception as e:
print(f"❌ Could not redefine Farewell agent. Error: {e}")
root_agent_stateful = None
runner_root_stateful = None
if greeting_agent and farewell_agent and 'get_weather_stateful' in globals():
root_agent_stateful = Agent(
name="weather_agent_v4_stateful", # New version name
model=LiteLlm("gemini/gemini-2.0-flash"),
description="Main agent: Provides weather (state-aware unit), delegates greetings/farewells, saves report to state.",
instruction="You are the main Weather Agent. Your job is to provide weather using 'get_weather_stateful'. "
"The tool will format the temperature based on user preference stored in state. "
"Delegate simple greetings to 'greeting_agent' and farewells to 'farewell_agent'. "
"Handle only weather requests, greetings, and farewells.",
tools=[get_weather_stateful], # Use the state-aware tool
sub_agents=[greeting_agent, farewell_agent], # サブエージェント
output_key="last_weather_report" # <<< Auto-save agent's final weather response
)
print(f"✅ Root Agent '{root_agent_stateful.name}' created using stateful tool and output_key.")
runner_root_stateful = Runner(
agent=root_agent_stateful,
app_name=APP_NAME,
session_service=session_service_stateful # Use the NEW stateful session service
)
print(f"✅ Runner created for stateful root agent '{runner_root_stateful.agent.name}' using stateful session service.")
else:
print("❌ Cannot create stateful root agent. Prerequisites missing.")
if not greeting_agent: print(" - greeting_agent definition missing.")
if not farewell_agent: print(" - farewell_agent definition missing.")
if 'get_weather_stateful' not in globals(): print(" - get_weather_stateful tool missing.")あらかじめ前記のcall_agent_asyncを定義しておきます。
import asyncio
from google.genai import types
from google.adk.runners import Runner
async def call_agent_async(query: str, runner: Runner, user_id: str, session_id: str):
"""Sends a query to the agent and prints the final response."""
print(f"\n>>> User Query: {query}")
content = types.Content(role='user', parts=[types.Part(text=query)])
final_response_text = "Agent did not produce a final response."
async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=content):
if event.is_final_response():
if event.content and event.content.parts:
final_response_text = event.content.parts[0].text
elif event.actions and event.actions.escalate:
final_response_text = f"Agent escalated: {event.error_message or 'No specific message.'}"
break
print(f"<<< Agent Response: {final_response_text}")エージェントを実行する以下のプログラムを実行します。
import asyncio
if 'runner_root_stateful' in globals() and runner_root_stateful:
# The 'await' keywords INSIDE this function are necessary for async operations.
async def run_stateful_conversation():
print("\n--- Testing State: Temp Unit Conversion & output_key ---")
# 1. 天気の確認 (Uses initial state: Celsius)
print("--- Turn 1: Requesting weather in London (expect Celsius) ---")
await call_agent_async(query= "What's the weather in London?",
runner=runner_root_stateful,
user_id=USER_ID_STATEFUL,
session_id=SESSION_ID_STATEFUL
)
# 2. 手動でstateをFahrenheitに変更 - 直接ストレージを編集する
print("\n--- Manually Updating State: Setting unit to Fahrenheit ---")
try:
# 永続的なストレージを使用する場合、通常は直接ストレージを操作せずに、エージェントのアクションや特定のサービスAPIを介して状態を更新します。
# 内部ストレージにアクセスするのは、テスト目的でのみ行います。
stored_session = session_service_stateful.sessions[APP_NAME][USER_ID_STATEFUL][SESSION_ID_STATEFUL]
stored_session.state["user_preference_temperature_unit"] = "Fahrenheit"
# Optional: タイムスタンプを更新する
# import time
# stored_session.last_update_time = time.time()
print(f"--- Stored session state updated. Current 'user_preference_temperature_unit': {stored_session.state.get('user_preference_temperature_unit', 'Not Set')} ---") # Added .get for safety
except KeyError:
print(f"--- Error: Could not retrieve session '{SESSION_ID_STATEFUL}' from internal storage for user '{USER_ID_STATEFUL}' in app '{APP_NAME}' to update state. Check IDs and if session was created. ---")
except Exception as e:
print(f"--- Error updating internal session state: {e} ---")
# 3. 再び天気を確認する (Fahrenheit表記になっていることを期待)
# この操作はoutput_keyを介してlast_weather_reportを更新します。
print("\n--- Turn 2: Requesting weather in New York (expect Fahrenheit) ---")
await call_agent_async(query= "Tell me the weather in New York.",
runner=runner_root_stateful,
user_id=USER_ID_STATEFUL,
session_id=SESSION_ID_STATEFUL
)
# 4. 通常の委任を実行
# この操作はNYの天気情報でlast_weather_reportを上書きします。
print("\n--- Turn 3: Sending a greeting ---")
await call_agent_async(query= "Hi!",
runner=runner_root_stateful,
user_id=USER_ID_STATEFUL,
session_id=SESSION_ID_STATEFUL
)
# 実行
print("Attempting execution using 'await'...")
await run_stateful_conversation()
# 最終的なセッション状態を確認
print("\n--- Inspecting Final Session State ---")
final_session = session_service_stateful.get_session(app_name=APP_NAME,
user_id= USER_ID_STATEFUL,
session_id=SESSION_ID_STATEFUL)
if final_session:
# Use .get() for safer access to potentially missing keys
print(f"Final Preference: {final_session.state.get('user_preference_temperature_unit', 'Not Set')}")
print(f"Final Last Weather Report (from output_key): {final_session.state.get('last_weather_report', 'Not Set')}")
print(f"Final Last City Checked (by tool): {final_session.state.get('last_city_checked_stateful', 'Not Set')}")
# Print full state for detailed view
# print(f"Full State Dict: {final_session.state.as_dict()}") # Use as_dict() for clarity
else:
print("\n❌ Error: Could not retrieve final session state.")
else:
print("\n⚠️ Skipping state test conversation. Stateful root agent runner ('runner_root_stateful') is not available.")実行した結果は以下のようになりました。おおむね期待通りの出力となりました。stateの値を基に温度が表示され、最終的なstateの値も問題なさそうです。
Attempting execution using 'await'...
--- Testing State: Temp Unit Conversion & output_key ---
--- Turn 1: Requesting weather in London (expect Celsius) ---
>>> User Query: What's the weather in London?
--- Tool: get_weather_stateful called for London ---
--- Tool: Reading state 'user_preference_temperature_unit': Celsius ---
--- Tool: Generated report in Celsius. Result: {'status': 'success', 'report': 'The weather in London is cloudy with a temperature of 15°C.'} ---
--- Tool: Updated state 'last_city_checked_stateful': London ---
<<< Agent Response: The weather in London is cloudy with a temperature of 15°C.
--- Manually Updating State: Setting unit to Fahrenheit ---
--- Stored session state updated. Current 'user_preference_temperature_unit': Fahrenheit ---
--- Turn 2: Requesting weather in New York (expect Fahrenheit) ---
>>> User Query: Tell me the weather in New York.
--- Tool: get_weather_stateful called for New York ---
--- Tool: Reading state 'user_preference_temperature_unit': Fahrenheit ---
--- Tool: Generated report in Fahrenheit. Result: {'status': 'success', 'report': 'The weather in New york is sunny with a temperature of 77°F.'} ---
--- Tool: Updated state 'last_city_checked_stateful': New York ---
<<< Agent Response: The weather in New york is sunny with a temperature of 77°F.
--- Turn 3: Sending a greeting ---
>>> User Query: Hi!
Default value is not supported in function declaration schema for Google AI.
Default value is not supported in function declaration schema for Google AI.
--- Tool: say_hello called with name: there ---
<<< Agent Response: Hello, there!
--- Inspecting Final Session State ---
Final Preference: Fahrenheit
Final Last Weather Report (from output_key): Hello, there!
Final Last City Checked (by tool): New York今回は複数のエージェントを使ったエージェントチームを作成し、stateの使い方も学びました。次回はbefore_model_callbackの使い方などについて学びます。