アツアツA2AツなGoogle ADKさわっていく🪭

せっかくなのでGoogle ADKでエージェントを実装していく
Agno AIパッケージで実装してたらタイミング悪くでGoogle Agent Development Kitが公開された。
ドキュメントとこの辺を参考にする。

はじめかた
各エージェントのコードの配置
.
├── agents # Contains individual agent samples
│ ├── agent1 # Specific agent directory
│ │ └── README.md # Agent-specific instructions
│ ├── agent2
│ │ └── README.md
│ ├── ...
│ └── README.md # Overview and categorization of agents
└── README.md # This file (Repository overview)
__init__.py
、agent.py
、.env
を各エージェントのフォルダに作成する
agents/
agent/
__init__.py
agent.py
.env
README.md

チュートリアル見てみる
Colab向け
CLI向け
ステップ1:最初のエージェント
エージェントの定義はよくある形。
エージェントの定義の部分
# Use one of the model constants defined earlier
AGENT_MODEL = MODEL_GEMINI_2_0_FLASH # Starting with Gemini
weather_agent = Agent(
name="weather_agent_v1",
model=AGENT_MODEL, # Can be a string for Gemini or a LiteLlm object
description="Provides weather information for specific cities.",
instruction="You are a helpful weather assistant. "
"When the user asks for the weather in a specific city, "
"use the 'get_weather' tool to find the information. "
"If the tool returns an error, inform the user politely. "
"If the tool is successful, present the weather report clearly.",
tools=[get_weather], # Pass the function directly
)
print(f"Agent '{weather_agent.name}' created using model '{AGENT_MODEL}'.")
セッションSessionService
と実行Runner
の管理設定がある。下記の実装ではセッションの会話履歴を全てメモリに埋め込むシンプルな実装。詳しくはステップ4。CLI向けにこの記述はない。
セッション管理の部分
# --- Session Management ---
# Key Concept: SessionService stores conversation history & state.
# InMemorySessionService is simple, non-persistent storage for this tutorial.
session_service = InMemorySessionService()
# Define constants for identifying the interaction context
APP_NAME = "weather_tutorial_app"
USER_ID = "user_1"
SESSION_ID = "session_001" # 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}'")
# --- Runner ---
# Key Concept: Runner orchestrates the agent execution loop.
runner = Runner(
agent=weather_agent, # The agent we want to run
app_name=APP_NAME, # Associates runs with our app
session_service=session_service # Uses our session manager
)
print(f"Runner created for agent '{runner.agent.name}'.")
非同期処理したいためasync
を使って改めて関数を定義する。(あとでawaitつけて関数を使って非同期処理に。詳しくは公式doc)
asyncで関数を定義
from google.genai import types # For creating message Content/Parts
async def call_agent_async(query: str, runner, user_id, session_id):
"""Sends a query to the agent and prints the final response."""
print(f"\n>>> User Query: {query}")
# Prepare the user's message in ADK format
content = types.Content(role='user', parts=[types.Part(text=query)])
final_response_text = "Agent did not produce a final response." # Default
# Key Concept: run_async executes the agent logic and yields Events.
# We iterate through events to find the final answer.
async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=content):
# You can uncomment the line below to see *all* events during execution
# print(f" [Event] Author: {event.author}, Type: {type(event).__name__}, Final: {event.is_final_response()}, Content: {event.content}")
# Key Concept: is_final_response() marks the concluding message for the turn.
if event.is_final_response():
if event.content and event.content.parts:
# Assuming text response in the first part
final_response_text = event.content.parts[0].text
elif event.actions and event.actions.escalate: # Handle potential errors/escalations
final_response_text = f"Agent escalated: {event.error_message or 'No specific message.'}"
# Add more checks here if needed (e.g., specific error codes)
break # Stop processing events once the final response is found
print(f"<<< Agent Response: {final_response_text}")
実行方法は以下。awaitをつけて関数を使うとrun_conversation内である関数の待機中に実行できる他の関数から進められていく。
会話を実行
# @title Run the Initial Conversation
# We need an async function to await our interaction helper
async def run_conversation():
await call_agent_async("What is the weather like in London?",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID)
await call_agent_async("How about Paris?",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID) # Expecting the tool's error message
await call_agent_async("Tell me the weather in New York",
runner=runner,
user_id=USER_ID,
session_id=SESSION_ID)
# Execute the conversation using await in an async context (like Colab/Jupyter)
await run_conversation()
# --- OR ---
# Uncomment the following lines if running as a standard Python script (.py file): import asyncio
if __name__ == "__main__":
try:
asyncio.run(run_conversation())
except Exception as e:
print(f"An error occurred: {e}")

ステップ2:LiteLLMでマルチモデル化する[オプション]
LiteLLMパッケージを噛ませて、モデル名を指定するだけでLLMプロバイダー横断でモデルを切り替えることができる。MODEL_GPT_4O
にあらかじめモデル名を定義している前提。
from google.adk.models.lite_llm import LiteLlm
weather_agent_gpt = Agent(
name="weather_agent_gpt",
# Key change: Wrap the LiteLLM model identifier
model=LiteLlm(model=MODEL_GPT_4O),
description="Provides weather information (using GPT-4o).",
instruction="You are a helpful weather assistant powered by GPT-4o. "
"Use the 'get_weather' tool for city weather requests. "
"Clearly present successful reports or polite error messages based on the tool's output status.",
tools=[get_weather], # Re-use the same tool
)

ステップ3:エージェントチームの構築 - 挨拶と送別会の委任
引数sub_agents
でタスクを割り振るエージェントを指定することができる。
Agentを宣言している部分
weather_agent_team = Agent(
name="weather_agent_v2", # Give it a new version name
model=root_agent_model,
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], # Root agent still needs the weather tool for its core task
# Key change: Link the sub-agents here!
sub_agents=[greeting_agent, farewell_agent]
)

ステップ4: セッション状態によるメモリとパーソナライゼーションの追加
状態管理:エージェントによる判断・操作の結果でタスクがどういう状態か
まず、session_service_stateful = InMemorySessionService()
のようにセッションサービスのインスタンスを定義する。Agent
インスタンスの宣言時に引数output_key
を指定しておき、Runner
インスタンスの引数session_service
にセッションサービスを渡しておけば、エージェントの出力が決まったセッションのキーに収められていく。
新規のセッション・状態を初期化
# Import necessary session components
from google.adk.sessions import InMemorySessionService
# Create a NEW session service instance for this state demonstration
session_service_stateful = InMemorySessionService()
print("✅ New InMemorySessionService created for state demonstration.")
# Define a NEW session ID for this part of the tutorial
SESSION_ID_STATEFUL = "session_state_demo_001"
USER_ID_STATEFUL = "user_state_demo"
# Define initial state data - user prefers Celsius initially
initial_state = {
"user_preference_temperature_unit": "Celsius"
}
# Create the session, providing the initial state
session_stateful = session_service_stateful.create_session(
app_name=APP_NAME, # Use the consistent app name
user_id=USER_ID_STATEFUL,
session_id=SESSION_ID_STATEFUL,
state=initial_state # <<< Initialize state during creation
)
print(f"✅ Session '{SESSION_ID_STATEFUL}' created for user '{USER_ID_STATEFUL}'.")
# Verify the initial state was set correctly
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.")
状態を取得・更新するツール関数を作っておくといいらしい。例えば、「ユーザーが望む温度の単位」がすでに状態として入っており、それを読み取ることでLLMは出力するレポートにその単位を反映させることができる。また、「最後に調べた都市」を更新することで後の処理に流用でき、LLMの出力に依存しない統一的な表現で後続のプロンプトを生成することができる。ToolContextはツール関数の最後のパラメータとして定義されている場合、ADK によってToolContextの状態へ自動的に挿入されるとのこと。
状態を確認するツールの実装例
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") # Default to Celsius
print(f"--- Tool: Reading state 'user_preference_temperature_unit': {preferred_unit} ---")
city_normalized = city.lower().replace(" ", "")
# Mock weather data (always stored in Celsius internally)
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"]
# Format temperature based on state preference
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} ---")
# Example of writing back to state (optional for this tool)
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.")
チーム型のエージェントを定義する。状態管理を利用することで出力の揺れを抑えることができそう。root_agent_stateful
を見るとAgent関数でoutput_key="last_weather_report"
を指定することで、最終出力をセッションに収めることができる。runner_root_stateful
で、さっき初期化しておいたセッションのインスタンスを指定して実行開始。
サブエージェントを含めたルートエージェントを状態を更新する形で実装
# Ensure necessary imports: Agent, LiteLlm, Runner
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm
from google.adk.runners import Runner
# Ensure tools 'say_hello', 'say_goodbye' are defined (from Step 3)
# Ensure model constants MODEL_GPT_4O, MODEL_GEMINI_2_0_FLASH etc. are defined
# --- Redefine Greeting Agent (from Step 3) ---
greeting_agent = None
try:
greeting_agent = Agent(
model=MODEL_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}")
# --- Redefine Farewell Agent (from Step 3) ---
farewell_agent = None
try:
farewell_agent = Agent(
model=MODEL_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}")
# --- Define the Updated Root Agent ---
root_agent_stateful = None
runner_root_stateful = None # Initialize runner
# Check prerequisites before creating the root agent
if greeting_agent and farewell_agent and 'get_weather_stateful' in globals():
root_agent_model = MODEL_GEMINI_2_0_FLASH # Choose orchestration model
root_agent_stateful = Agent(
name="weather_agent_v4_stateful", # New version name
model=root_agent_model,
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], # Include sub-agents
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.")
# --- Create Runner for this Root Agent & NEW Session Service ---
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.")

ステップ5: 安全性の追加 - ガードレールの入力before_model_callback
エージェントの動作を制限するため、プロンプトに特定のキーワードがある場合に止めることができるコールバック機能があるとのこと。下記が使用例。
使用例 | 説明 |
---|---|
入力バリデーション/フィルタリング | ユーザー入力が基準を満たしているか、または許可されないコンテンツ(個人情報やキーワードなど)が含まれているかをチェックします。 |
ガードレール | 有害なリクエスト、オフトピックなリクエスト、ポリシー違反のリクエストがLLMによって処理されないようにします。 |
動的プロンプト修正 | 送信直前にLLMリクエストコンテキストにタイムリーな情報(セッションの状態など)を追加します。 |
まずはBLOCK
という単語があれば実行させない関数を定義
`before_model_callback`ガードレールを定義
# Ensure necessary imports are available
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.genai import types # For creating response content
from typing import Optional
def block_keyword_guardrail(
callback_context: CallbackContext, llm_request: LlmRequest
) -> Optional[LlmResponse]:
"""
Inspects the latest user message for 'BLOCK'. If found, blocks the LLM call
and returns a predefined LlmResponse. Otherwise, returns None to proceed.
"""
agent_name = callback_context.agent_name # Get the name of the agent whose model call is being intercepted
print(f"--- Callback: block_keyword_guardrail running for agent: {agent_name} ---")
# Extract the text from the latest user message in the request history
last_user_message_text = ""
if llm_request.contents:
# Find the most recent message with role 'user'
for content in reversed(llm_request.contents):
if content.role == 'user' and content.parts:
# Assuming text is in the first part for simplicity
if content.parts[0].text:
last_user_message_text = content.parts[0].text
break # Found the last user message text
print(f"--- Callback: Inspecting last user message: '{last_user_message_text[:100]}...' ---") # Log first 100 chars
# --- Guardrail Logic ---
keyword_to_block = "BLOCK"
if keyword_to_block in last_user_message_text.upper(): # Case-insensitive check
print(f"--- Callback: Found '{keyword_to_block}'. Blocking LLM call! ---")
# Optionally, set a flag in state to record the block event
callback_context.state["guardrail_block_keyword_triggered"] = True
print(f"--- Callback: Set state 'guardrail_block_keyword_triggered': True ---")
# Construct and return an LlmResponse to stop the flow and send this back instead
return LlmResponse(
content=types.Content(
role="model", # Mimic a response from the agent's perspective
parts=[types.Part(text=f"I cannot process this request because it contains the blocked keyword '{keyword_to_block}'.")],
)
# Note: You could also set an error_message field here if needed
)
else:
# Keyword not found, allow the request to proceed to the LLM
print(f"--- Callback: Keyword not found. Allowing LLM call for {agent_name}. ---")
return None # Returning None signals ADK to continue normally
print("✅ block_keyword_guardrail function defined.")
ルートエージェントの定義するとき、before_model_callback=block_keyword_guardrail
として引数に含める。このケースだとユーザーがBLOCKという単語を含めると実行が終了することになる。
`before_model_callback`を含めたかたちにルートエージェントを更新
# --- Redefine Sub-Agents (Ensures they exist in this context) ---
greeting_agent = None
try:
# Use a defined model constant
greeting_agent = Agent(
model=MODEL_GEMINI_2_0_FLASH,
name="greeting_agent", # Keep original name for consistency
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"✅ Sub-Agent '{greeting_agent.name}' redefined.")
except Exception as e:
print(f"❌ Could not redefine Greeting agent. Check Model/API Key ({greeting_agent.model}). Error: {e}")
farewell_agent = None
try:
# Use a defined model constant
farewell_agent = Agent(
model=MODEL_GEMINI_2_0_FLASH,
name="farewell_agent", # Keep original name
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"✅ Sub-Agent '{farewell_agent.name}' redefined.")
except Exception as e:
print(f"❌ Could not redefine Farewell agent. Check Model/API Key ({farewell_agent.model}). Error: {e}")
# --- Define the Root Agent with the Callback ---
root_agent_model_guardrail = None
runner_root_model_guardrail = None
# Check all components before proceeding
if greeting_agent and farewell_agent and 'get_weather_stateful' in globals() and 'block_keyword_guardrail' in globals():
# Use a defined model constant
root_agent_model = MODEL_GEMINI_2_0_FLASH
root_agent_model_guardrail = Agent(
name="weather_agent_v5_model_guardrail", # New version name for clarity
model=root_agent_model,
description="Main agent: Handles weather, delegates greetings/farewells, includes input keyword guardrail.",
instruction="You are the main Weather Agent. Provide weather using 'get_weather_stateful'. "
"Delegate simple greetings to 'greeting_agent' and farewells to 'farewell_agent'. "
"Handle only weather requests, greetings, and farewells.",
tools=[get_weather],
sub_agents=[greeting_agent, farewell_agent], # Reference the redefined sub-agents
output_key="last_weather_report", # Keep output_key from Step 4
before_model_callback=block_keyword_guardrail # <<< Assign the guardrail callback
)
print(f"✅ Root Agent '{root_agent_model_guardrail.name}' created with before_model_callback.")
# --- Create Runner for this Agent, Using SAME Stateful Session Service ---
# Ensure session_service_stateful exists from Step 4
if 'session_service_stateful' in globals():
runner_root_model_guardrail = Runner(
agent=root_agent_model_guardrail,
app_name=APP_NAME, # Use consistent APP_NAME
session_service=session_service_stateful # <<< Use the service from Step 4
)
print(f"✅ Runner created for guardrail agent '{runner_root_model_guardrail.agent.name}', using stateful session service.")
else:
print("❌ Cannot create runner. 'session_service_stateful' from Step 4 is missing.")
else:
print("❌ Cannot create root agent with model guardrail. One or more prerequisites are missing or failed initialization:")
if not greeting_agent: print(" - Greeting Agent")
if not farewell_agent: print(" - Farewell Agent")
if 'get_weather_stateful' not in globals(): print(" - 'get_weather_stateful' tool")
if 'block_keyword_guardrail' not in globals(): print(" - 'block_keyword_guardrail' callback")

ステップ6: 安全性の追加 - ツール引数ガードレール(before_tool_callback)
あぶないプロンプトを弾くほかに、エージェントがツールに入力する引数も制限することができるらしい。
機能 | 説明 |
---|---|
引数の検証 | LLM によって提供された引数が有効であるか、許容範囲内であるか、または予期される形式に準拠しているかどうかを確認します。 |
リソース保護 | コストがかかったり、制限されたデータにアクセスしたり、望ましくない副作用 (特定のパラメータに対する API 呼び出しをブロックするなど) を引き起こす可能性のある入力でツールが呼び出されないようにします。 |
動的な引数の変更 | ツールを実行する前に、セッション状態またはその他のコンテキスト情報に基づいて引数を調整します。 |
まずはコールバック関数定義する。
`before_tool_callback`ガードレールを定義する
# Ensure necessary imports are available
from google.adk.tools.base_tool import BaseTool
from google.adk.tools.tool_context import ToolContext
from typing import Optional, Dict, Any # For type hints
def block_paris_tool_guardrail(
tool: BaseTool, args: Dict[str, Any], tool_context: ToolContext
) -> Optional[Dict]:
"""
Checks if 'get_weather_stateful' is called for 'Paris'.
If so, blocks the tool execution and returns a specific error dictionary.
Otherwise, allows the tool call to proceed by returning None.
"""
tool_name = tool.name
agent_name = tool_context.agent_name # Agent attempting the tool call
print(f"--- Callback: block_paris_tool_guardrail running for tool '{tool_name}' in agent '{agent_name}' ---")
print(f"--- Callback: Inspecting args: {args} ---")
# --- Guardrail Logic ---
target_tool_name = "get_weather_stateful" # Match the function name used by FunctionTool
blocked_city = "paris"
# Check if it's the correct tool and the city argument matches the blocked city
if tool_name == target_tool_name:
city_argument = args.get("city", "") # Safely get the 'city' argument
if city_argument and city_argument.lower() == blocked_city:
print(f"--- Callback: Detected blocked city '{city_argument}'. Blocking tool execution! ---")
# Optionally update state
tool_context.state["guardrail_tool_block_triggered"] = True
print(f"--- Callback: Set state 'guardrail_tool_block_triggered': True ---")
# Return a dictionary matching the tool's expected output format for errors
# This dictionary becomes the tool's result, skipping the actual tool run.
return {
"status": "error",
"error_message": f"Policy restriction: Weather checks for '{city_argument.capitalize()}' are currently disabled by a tool guardrail."
}
else:
print(f"--- Callback: City '{city_argument}' is allowed for tool '{tool_name}'. ---")
else:
print(f"--- Callback: Tool '{tool_name}' is not the target tool. Allowing. ---")
# If the checks above didn't return a dictionary, allow the tool to execute
print(f"--- Callback: Allowing tool '{tool_name}' to proceed. ---")
return None # Returning None allows the actual tool function to run
print("✅ block_paris_tool_guardrail function defined.")
ルートエージェントに引数before_tool_callback
でコールバック関数を渡せばツール関数の引数に対するガードレールを実装できる。
ルートエージェントを定義しなおす
# --- Ensure Prerequisites are Defined ---
# (Include or ensure execution of definitions for: Agent, LiteLlm, Runner, ToolContext,
# MODEL constants, say_hello, say_goodbye, greeting_agent, farewell_agent,
# get_weather_stateful, block_keyword_guardrail, block_paris_tool_guardrail)
# --- Redefine Sub-Agents (Ensures they exist in this context) ---
greeting_agent = None
try:
# Use a defined model constant
greeting_agent = Agent(
model=MODEL_GEMINI_2_0_FLASH,
name="greeting_agent", # Keep original name for consistency
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"✅ Sub-Agent '{greeting_agent.name}' redefined.")
except Exception as e:
print(f"❌ Could not redefine Greeting agent. Check Model/API Key ({greeting_agent.model}). Error: {e}")
farewell_agent = None
try:
# Use a defined model constant
farewell_agent = Agent(
model=MODEL_GEMINI_2_0_FLASH,
name="farewell_agent", # Keep original name
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"✅ Sub-Agent '{farewell_agent.name}' redefined.")
except Exception as e:
print(f"❌ Could not redefine Farewell agent. Check Model/API Key ({farewell_agent.model}). Error: {e}")
# --- Define the Root Agent with Both Callbacks ---
root_agent_tool_guardrail = None
runner_root_tool_guardrail = None
if ('greeting_agent' in globals() and greeting_agent and
'farewell_agent' in globals() and farewell_agent and
'get_weather_stateful' in globals() and
'block_keyword_guardrail' in globals() and
'block_paris_tool_guardrail' in globals()):
root_agent_model = MODEL_GEMINI_2_0_FLASH
root_agent_tool_guardrail = Agent(
name="weather_agent_v6_tool_guardrail", # New version name
model=root_agent_model,
description="Main agent: Handles weather, delegates, includes input AND tool guardrails.",
instruction="You are the main Weather Agent. Provide weather using 'get_weather_stateful'. "
"Delegate greetings to 'greeting_agent' and farewells to 'farewell_agent'. "
"Handle only weather, greetings, and farewells.",
tools=[get_weather_stateful],
sub_agents=[greeting_agent, farewell_agent],
output_key="last_weather_report",
before_model_callback=block_keyword_guardrail, # Keep model guardrail
before_tool_callback=block_paris_tool_guardrail # <<< Add tool guardrail
)
print(f"✅ Root Agent '{root_agent_tool_guardrail.name}' created with BOTH callbacks.")
# --- Create Runner, Using SAME Stateful Session Service ---
if 'session_service_stateful' in globals():
runner_root_tool_guardrail = Runner(
agent=root_agent_tool_guardrail,
app_name=APP_NAME,
session_service=session_service_stateful # <<< Use the service from Step 4/5
)
print(f"✅ Runner created for tool guardrail agent '{runner_root_tool_guardrail.agent.name}', using stateful session service.")
else:
print("❌ Cannot create runner. 'session_service_stateful' from Step 4/5 is missing.")
else:
print("❌ Cannot create root agent with tool guardrail. Prerequisites missing.")

実行してみる
リポジトリをクローンしたらexamples/python/tutorial/agent_team/adk-tutorial
に移動してadk web
を実行する。
adk web
で実行すると、anget.py
を納めたフォルダごとにエージェントを選んでテスト実行ができる。

同じようなエージェントのクラスがある?
引数おなじなのに名前が違う以下の2つ。
from google.adk.agents import LlmAgent
from google.adk.agents import Agent
どっちでも呼べる、、一緒??開発時にブレてたんかな。
The
LlmAgent
(often aliased simply asAgent
) is a core component in ADK,
ー llm-agents.md
その通りで、パッケージの実装みにいくとLlmAgent
のエイリアスとしてAgent
があるだけだった
475 Agent: TypeAlias = LlmAgent

Cloud Run にデプロイしたい。
ファイル構造は?
FastAPIをフレームワークとしてCloud Runでデプロイできる。FastAPIとして動かすためにfrom google.adk.cli.fast_api import get_fast_api_app
が用意されている。デプロイ時の引数にIAPを含めると組織内の人だけアクセスできる仕様に。
your-project-directory/
├── capital_agent/
│ ├── __init__.py
│ └── agent.py # Your agent code (see "Agent sample" tab)
├── main.py # FastAPI application entry point
├── requirements.txt # Python dependencies
├── .env
└── Dockerfile # Container build instructions
こちらが参考になりました。
デプロイ方法
iapでアクセス制限をできるだけ手軽につけたかったけど、ベータ版gcloud beta run deploy
にはある--iap
コマンドが本番版のgcloud run deploy
になかた。ずっと使ってるGitHub Actionsのgoogle-github-actions/auth@v2
が通らないのでこちら
gcloud beta run deploy adk-agent \
--region $GOOGLE_CLOUD_LOCATION \
--project $GOOGLE_CLOUD_PROJECT \
--image asia-northeast1-docker.pkg.dev/$GOOGLE_CLOUD_PROJECT/samples/adk-python \
--iap \
--set-env-vars="GOOGLE_CLOUD_PROJECT=$GOOGLE_CLOUD_PROJECT,GOOGLE_CLOUD_LOCATION=$GOOGLE_CLOUD_LOCATION,GOOGLE_GENAI_USE_VERTEXAI=$GOOGLE_GENAI_USE_VERTEXAI"
こちらが参考になりました
ちなみにiap
は組織ポリシーは地域?かなんかで設定して、サービスのポリシーはデプロイ後に組織のドメインを指定しました。

自作ツール関数のアノテーション
シンプルな関数にしてくれ的なエラー返ってくる。
-
def func(args) -> None
はダメらしい。戻り値としてNone
以外で何らかの型を返す必要がある。 -
datetime
は引数として使えない
ValueError: Failed to parse the parameter return_value: None of function {自作ツール関数名} for automatic function calling. Automatic function calling works best with simpler function signature schema, consider manually parse your function declaration for function {自作ツール関数名}.

ガードレール関数
ガードレール関数は一つしか受けつてない?とりあえずリストで渡したらエラーに。
pydantic_core._pydantic_core.ValidationError: 1 validation error for LlmAgent
before_tool_callback
Input should be callable [type=callable_type, input_value=[<bound method CloudRunGu...object at 0x14dca3980>>], input_type=list]

.env
vertex aiを使う場合は環境変数がちょっと違う
GOOGLE_GENAI_USE_VERTEXAI=TRUE
GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
GOOGLE_CLOUD_LOCATION=LOCATION

LiteLLM
litellmでgeminiをラップしてツール関数に使おうとしたらvertex ai経由だとubuntu
上で使えない模様?
Google AI Studioはモデル名の指定が他プロバイダと異なる、、結局動かんかったケド。
# Define model constants for cleaner code
MODEL_GEMINI_PRO = "gemini-1.5-pro"
MODEL_GPT_4O = "openai/gpt-4o"
MODEL_CLAUDE_SONNET = "anthropic/claude-3-sonnet-20240229"
LiteLlmのバージョン落としてもだめ。同じエラー。
ADKのAgentでOCR用のエージェント作ってagent_toolで使おうとしたけど、ファイル一個ずつ指定することになったり(不安定)、base64エンコード文字列をプロンプトで渡すことになったり(コンテキスト長すぎる)と意味ないことに。
結局geminiのsdk(from google import genai
)で書き直した。
そもそもAgent関数でモデル指定する場合に、gemini系はLiteLlmでラッパーするとダメみたい。

get_fast_api_app の使い方
Cloud Run のデプロイする場合に、main.py
で使うget_fast_api_app
関数がわかりゃん。パッケージの実装コードを中心に調べてみた👀
参考
-
過去のスクラップ:Cloud Run にデプロイしたい。
-
https://github.com/google/adk-python/blob/main/src/google/adk/cli/fast_api.py#L195
主要な機能
-
セッション管理:↗︎ソース
-
session_service_uri
パラメータを使用して、セッションの保存方法を設定できます - 空の文字列を指定すると
InMemorySessionService
が使用されます(一時的なセッション) - データベース URL を指定すると
DatabaseSessionService
が使用されます(永続的なセッション) -
agentengine://
で始まる URL を指定するとVertexAiSessionService
が使用されます
-
-
成果物ストレージ:↗︎ソース
-
artifact_service_uri
パラメータで成果物の保存場所を指定できます - Google Cloud Storage の場合は
gs://
で始まる URI を指定します - 指定しない場合は
InMemoryArtifactService
が使用されます
-
-
ウェブインターフェース:[↗︎ソース]
-
web
パラメータをTrue
に設定すると、ウェブインターフェースが提供されます - これにより、ブラウザベースの対話型インターフェースが利用可能になります
-
-
CORS 設定:↗︎ソース
-
allow_origins
パラメータで Cross-Origin Resource Sharing を設定できます - API を異なるドメインから呼び出せるようにするために使用します
-
-
トレーシング:↗︎ソース
-
trace_to_cloud
パラメータをTrue
に設定すると、Google Cloud へのトレースが有効になります - デバッグや監視に役立ちます
-
詳しい解説
`get_fast_api_app` 関数の詳細解説
google.adk.cli.fast_api
モジュールの get_fast_api_app
関数は、Google ADK (Agent Development Kit) を使用してエージェントを FastAPI ウェブサーバーとして提供するための重要な関数です。以下にその詳細な使い方と機能を解説します。
基本的な機能
def get_fast_api_app(
*,
agents_dir: str, # エージェントのディレクトリパス
session_service_uri: str = "", # セッションデータベースの URL
artifact_service_uri: Optional[str] = None, # 成果物ストレージの URI
allow_origins: Optional[list[str]] = None, # CORS 許可オリジン
web: bool, # ウェブインターフェースを提供するかどうか
trace_to_cloud: bool = False, # クラウドへのトレースを有効にするかどうか
lifespan: Optional[Lifespan[FastAPI]] = None, # FastAPI のライフスパン
) -> FastAPI:
パラメータの詳細
-
agents_dir
- エージェントのディレクトリパスを指定します
- このディレクトリ内の各サブディレクトリがエージェントとして認識されます
-
session_service_uri
- セッション管理のためのデータベース URL を指定します
- 空文字列の場合:
InMemorySessionService
が使用される(一時的なセッション) - データベース URL の場合:
DatabaseSessionService
が使用される(永続的なセッション) -
agentengine://
で始まる URL の場合:VertexAiSessionService
が使用される - 例:
sqlite:///./sessions.db
(SQLite を使用する場合)
-
artifact_service_uri
- エージェントが生成する成果物の保存場所を指定します
-
gs://
で始まる URI の場合: Google Cloud Storage が使用される - 指定しない場合:
InMemoryArtifactService
が使用される(一時的な保存)
-
allow_origins
- CORS (Cross-Origin Resource Sharing) の設定を行います
- 異なるドメインからの API 呼び出しを許可するために使用します
- 例:
["http://localhost", "http://localhost:8080", "*"]
-
web
-
True
の場合: ブラウザベースの対話型インターフェースが提供されます -
False
の場合: API エンドポイントのみが提供されます - 注意:
web=True
に設定すると、カスタムエンドポイントにアクセスできない問題が報告されています(Issue #51)
-
-
trace_to_cloud
-
True
の場合: Google Cloud へのトレースが有効になります - デバッグや監視に役立ちます
- 環境変数
GOOGLE_CLOUD_PROJECT
が設定されている必要があります
-
-
lifespan
- FastAPI アプリケーションのライフサイクル管理を行います
- カスタムの起動・終了処理を設定できます
主要な機能
-
セッション管理
- ユーザーとエージェントの対話履歴を保存・管理します
- データベースを使用した永続的なセッション管理が可能です
- Cloud SQL や AlloyDB などの高度なデータベースサービスとの連携も可能です
-
成果物ストレージ
- エージェントが生成するファイルや画像などの成果物を保存します
- Google Cloud Storage との連携が組み込まれています
-
API エンドポイント
- エージェントとの対話のための RESTful API を自動的に設定します
- セッション管理、エージェント実行、評価機能などの API が含まれます
-
ウェブインターフェース
- ブラウザベースの対話型インターフェースを提供します
- エージェントのテストやデモに便利です
-
トレーシング
- エージェントの実行過程を追跡・記録します
- デバッグや監視に役立ちます
発展的な使い方
-
データベース設定の拡張
- 将来的に
session_db_kwargs
パラメータが追加される予定です(Issue #1287) - 接続プールのサイズやタイムアウトなどの詳細な設定が可能になります
- Cloud SQL や AlloyDB などの高度なデータベースサービスとの連携が強化されます
- 将来的に
-
カスタムエンドポイントの追加
- 返される FastAPI アプリケーションに独自のエンドポイントを追加できます
- 例:
app = get_fast_api_app(...) @app.get("/custom") async def custom_endpoint(): return {"message": "Custom endpoint"}
-
評価機能の活用
- エージェントの評価セットを作成・管理するための機能が含まれています
- エージェントのパフォーマンスをテストするのに役立ちます
実際の使用例
from google.adk.cli.fast_api import get_fast_api_app
import os
import uvicorn
# エージェントディレクトリの設定
AGENT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "agents")
# FastAPI アプリケーションの取得
app = get_fast_api_app(
agents_dir=AGENT_DIR,
session_db_url="sqlite:///./sessions.db", # SQLite を使用する場合
allow_origins=["http://localhost", "http://localhost:8080", "*"], # CORS 設定
web=True, # ウェブインターフェースを有効化
)
# サーバーの起動
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
この関数は、Google ADK を使用してエージェントを API として提供する際の標準的な方法であり、特に Cloud Run などのサービスにデプロイする場合に便利です。

get_fast_api_app
関数でVertexAiSessionService
使いたいゾ❗️
背景
AIエージェントをADKで作成・Cloud Runでデプロイする場合、会話歴(セッション)を簡単・標準・低費用で管理するにはAgent Engineが良さそう。正確にはAgent Engineインスタンスのセッション管理機能だけ使う。
セッションはInMemorySessionService、DatabaseSessionService、VertexAiSessionServiceから選ぶことができ、GCPでデプロイするならVertexAiSessionServiceを使うことで費用を抑えることができそうです。
Google Agent Engineにデプロイされ、計算時間に応じた従量課金となります。
ー Agent Development KitのセッションがDBに作成するスキーマを調べてみた
疑問
get_fast_api_app
関数の引数session_service_uri
でセッションの保存先を指定できる。agentengine://
で始まるURLを渡せばいいっぽい。どうやってこのURLを取得できる?
調べてみた
わきゃらんかった、、下記によるとpythonのagent_engines
クラスからAgent Engineインスタンスの作成はできる。
import vertexai
from vertexai import agent_engines
# Create an agent engine instance
agent_engine = agent_engines.create()
ただ、APIのURLを見ると下記な感じ。agentengine://
ではじまってない。
https://LOCATION-aiplatform.googleapis.com/v1beta1/projects/PROJECT_ID/locations/LOCATION/reasoningEngines/AGENT_ENGINE_ID/sessions
ー 直接 API 呼び出しを使用してセッションを管理する
*LOCATION
, PROJECT_ID
, AGENT_ENGINE_ID
は環境依存。
agentengine://
を検索しても何も出てこないので、Agent Engine側でも未実装?かもしれない。一旦、エージェント自体をデプロイするCloud Runのインスタンス上でSQLサーバーを起動させるといいかも?

Cloud Runで複数のGCSバケットをマウントする際の落とし穴と解決策
Cloud Runでアプリケーションをデプロイする際、複数のGoogle Cloud Storage (GCS) バケットにアクセスしたいケースはよくありますよね。特に、アプリケーションのデータやログ、設定などを別々のバケットに保存したい場合に便利です。
gcloud run deploy
コマンドを使ってバケットをマウントする際、少しハマりやすいポイントがあるので、その解決策を共有します。
はじめに: よくある間違い
複数のGCSバケットを一度にマウントしようとして、以下のようなコマンドを試す方がいらっしゃいます。これは一見正しそうに見えますが、残念ながら意図通りに動作しません。
❌ 間違ったコマンド例 (旧)
gcloud beta run deploy my-service \
--region us-central1 \
--source . \
--add-volume=name=bucket1-vol,type=cloud-storage,bucket=my-bucket-1,name=bucket2-vol,type=cloud-storage,bucket=my-bucket-2 \
--add-volume-mount=volume=bucket1-vol,mount-path=/mnt/bucket1,volume=bucket2-vol,mount-path=/mnt/bucket2
このコマンドを実行すると、一部のバケットしかマウントされなかったり、マウントパスがおかしくなったりすることがあります。原因は、--add-volume
や --add-volume-mount
パラメータが、1つの引数で複数の定義を受け付けないためです。カンマで区切って複数書くと、最初の定義以降が無視されたり、正しく解釈されなかったりします。
実際にこの方法を試した際、私もバケット名の間違い(例: test-takashou-ai-agent
とすべきところを test-takashou-ai-agents
と入力していたなど、スペルミスもよくあります!)と相まって、デプロイが失敗する経験をしました。
解決策: パラメータを個別に指定する
複数のバケットをマウントする場合、--add-volume
と --add-volume-mount
はそれぞれのバケットに対して個別に指定する必要があります。
✅ 正しいコマンド例 (新)
gcloud beta run deploy my-service \
--region us-central1 \
--source . \
\
--add-volume=name=bucket1-vol,type=cloud-storage,bucket=my-bucket-1 \
--add-volume-mount=volume=bucket1-vol,mount-path=/mnt/bucket1 \
\
--add-volume=name=bucket2-vol,type=cloud-storage,bucket=my-bucket-2 \
--add-volume-mount=volume=bucket2-vol,mount-path=/mnt/bucket2
この方法でコマンドを実行すれば、各バケットが意図した通りにマウントされ、アプリケーションからアクセスできるようになります。
ポイント:
-
1ボリューム1パラメータ:
name
,type
,bucket
を含む--add-volume
と、volume
,mount-path
を含む--add-volume-mount
は、それぞれ1つのバケットにつき1組ずつ指定します。 -
バケット名の正確性: コマンドで指定する
bucket
名と、実際のGCSバケット名が完全に一致しているか、もう一度確認しましょう。(私の場合はs
の付け忘れが原因でした!) -
サービスアカウントの権限: Cloud Runサービスが使用するサービスアカウントに、マウントするGCSバケットへの適切なアクセス権限(例:
Storage オブジェクト閲覧者
)が付与されていることを確認してください。

当たり前:Cloud Run とバケットのリージョン違いで激遅レスポンスに
背景
Cloud Runにマウントしたバケットにadkのセッション用のDBを配置したところ、web ui上のレスポンスが激遅に。
原因
Cloud Runのデプロイするリージョンとバケットのリージョンを揃えたところ激速に。よくあるリージョンを揃え忘れるやつが原因そう。
修正前
- 起動:10秒
- チャット:4秒
修正後
- 起動:3秒
- チャット:2秒

load_artifacts
関数じゃないとだめ?
artifactの読み込みは画像の受け渡しができなくて困っている
get_fast_api_app関数はartifactのuriを指定できる
artifactへの保存の例
artifactから取得の例
インストラクションから指定({artifact.<保存した名前>
)とか試したけどなーんか動かない。同じようなIssueがこちらに。散々試した結果、下記のように読み込んだload_artifacts_tool
をAgent
関数のtools
関数に渡せばいいとのこと。(同志、、)
from google.adk.tools.load_artifacts_tool import load_artifacts_tool

ループエージェントが止まるン
adkでLoopAgent
のサブエージェントはLoopAgent
を含めて、exit_loop
関数いれちゃうと、親のエージェントのループまで止めちゃうっぽい。
issue
例exit_loop