回调类型¶
框架提供不同类型的回调,它们在智能体执行的各个阶段触发。了解每种回调何时触发以及接收什么上下文是有效使用它们的关键。
智能体生命周期回调¶
这些回调适用于任何继承自BaseAgent
的智能体 (包括LlmAgent
、SequentialAgent
、ParallelAgent
、LoopAgent
等)。
智能体前置回调¶
何时触发: 在智能体的_run_async_impl
(或_run_live_impl
) 方法执行之前立即调用。它在创建智能体的InvocationContext
之后但在其核心逻辑开始之前运行。
用途: 非常适合设置仅对此特定智能体运行所需的资源或状态,在执行开始前对会话状态 (callback_context.state) 执行验证检查,记录智能体活动的入口点,或者在核心逻辑使用之前可能修改调用上下文。
代码
from google.adk.agents import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.runners import Runner
from typing import Optional
from google.genai import types
from google.adk.sessions import InMemorySessionService
GEMINI_2_FLASH="gemini-2.0-flash"
# --- Define the Callback Function ---
def simple_before_agent_logger(callback_context: CallbackContext) -> Optional[types.Content]:
"""Logs entry into an agent and checks a condition."""
agent_name = callback_context.agent_name
invocation_id = callback_context.invocation_id
print(f"[Callback] Entering agent: {agent_name} (Invocation: {invocation_id})")
# Example: Check a condition in state
if callback_context.state.get("skip_agent", False):
print(f"[Callback] Condition met: Skipping agent {agent_name}.")
# Return Content to skip the agent's run
return types.Content(parts=[types.Part(text=f"Agent {agent_name} was skipped by callback.")])
else:
print(f"[Callback] Condition not met: Proceeding with agent {agent_name}.")
# Return None to allow the agent's run to execute
return None
# Create LlmAgent and Assign Callback
my_llm_agent = LlmAgent(
name="SimpleLlmAgent",
model=GEMINI_2_FLASH,
instruction="You are a simple agent. Just say 'Hello!'",
description="An LLM agent demonstrating before_agent_callback",
before_agent_callback=simple_before_agent_logger
)
APP_NAME = "guardrail_app"
USER_ID = "user_1"
SESSION_ID = "session_001"
# Session and Runner
session_service = InMemorySessionService()
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=my_llm_agent, app_name=APP_NAME, session_service=session_service)
# Agent Interaction
def call_agent(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
call_agent("callback example")
智能体后置回调¶
何时触发: 在智能体的_run_async_impl
(或_run_live_impl
) 方法成功完成之后立即调用。如果由于before_agent_callback
返回内容或在智能体运行期间设置了end_invocation
而跳过了智能体,则不运行。
用途: 适用于清理任务、执行后验证、记录智能体活动的完成、修改最终状态或增强/替换智能体的最终输出。
代码
from google.adk.agents import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.runners import Runner
from typing import Optional
from google.genai import types
from google.adk.sessions import InMemorySessionService
GEMINI_2_FLASH="gemini-2.0-flash"
# --- Define the Callback Function ---
def simple_after_agent_logger(callback_context: CallbackContext) -> Optional[types.Content]:
"""Logs exit from an agent and optionally appends a message."""
agent_name = callback_context.agent_name
invocation_id = callback_context.invocation_id
print(f"[Callback] Exiting agent: {agent_name} (Invocation: {invocation_id})")
# Example: Check state potentially modified during the agent's run
final_status = callback_context.state.get("agent_run_status", "Completed Normally")
print(f"[Callback] Agent run status from state: {final_status}")
# Example: Optionally return Content to append a message
if callback_context.state.get("add_concluding_note", False):
print(f"[Callback] Adding concluding note for agent {agent_name}.")
# Return Content to append after the agent's own output
return types.Content(parts=[types.Part(text=f"Concluding note added by after_agent_callback.")])
else:
print(f"[Callback] No concluding note added for agent {agent_name}.")
# Return None - no additional message appended
return None
my_llm_agent = LlmAgent(
name="SimpleLlmAgentWithAfter",
model=GEMINI_2_FLASH,
instruction="You are a simple agent. Just say 'Processing complete!'",
description="An LLM agent demonstrating after_agent_callback",
after_agent_callback=simple_after_agent_logger # Assign the function here
)
APP_NAME = "guardrail_app"
USER_ID = "user_1"
SESSION_ID = "session_001"
# Session and Runner
session_service = InMemorySessionService()
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=my_llm_agent, app_name=APP_NAME, session_service=session_service)
# Agent Interaction
def call_agent(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
call_agent("callback example")
LLM 交互回调¶
这些回调专用于LlmAgent
,提供了围绕与大型语言模型交互的钩子。
模型前置回调¶
何时触发: 在LlmAgent
流程中向 LLM 发送generate_content_async
(或等效) 请求之前调用。
用途: 允许检查和修改发送给 LLM 的请求。用例包括添加动态指令、基于状态注入少量示例、修改模型配置、实现防护机制 (如亵渎过滤器) 或实现请求级缓存。
返回值效果:
如果回调返回None
,LLM 继续其正常工作流程。如果回调返回LlmResponse
对象,则跳过对 LLM 的调用。返回的LlmResponse
直接被使用,就像它来自模型一样。这对实现防护机制或缓存非常强大。
代码
from google.adk.agents import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.models import LlmResponse, LlmRequest
from google.adk.runners import Runner
from typing import Optional
from google.genai import types
from google.adk.sessions import InMemorySessionService
GEMINI_2_FLASH="gemini-2.0-flash"
# --- Define the Callback Function ---
def simple_before_model_modifier(
callback_context: CallbackContext, llm_request: LlmRequest
) -> Optional[LlmResponse]:
"""Inspects/modifies the LLM request or skips the call."""
agent_name = callback_context.agent_name
print(f"[Callback] Before model call for agent: {agent_name}")
# Inspect the last user message in the request contents
last_user_message = ""
if llm_request.contents and llm_request.contents[-1].role == 'user':
if llm_request.contents[-1].parts:
last_user_message = llm_request.contents[-1].parts[0].text
print(f"[Callback] Inspecting last user message: '{last_user_message}'")
# --- Modification Example ---
# Add a prefix to the system instruction
original_instruction = llm_request.config.system_instruction or types.Content(role="system", parts=[])
prefix = "[Modified by Callback] "
# Ensure system_instruction is Content and parts list exists
if not isinstance(original_instruction, types.Content):
# Handle case where it might be a string (though config expects Content)
original_instruction = types.Content(role="system", parts=[types.Part(text=str(original_instruction))])
if not original_instruction.parts:
original_instruction.parts.append(types.Part(text="")) # Add an empty part if none exist
# Modify the text of the first part
modified_text = prefix + (original_instruction.parts[0].text or "")
original_instruction.parts[0].text = modified_text
llm_request.config.system_instruction = original_instruction
print(f"[Callback] Modified system instruction to: '{modified_text}'")
# --- Skip Example ---
# Check if the last user message contains "BLOCK"
if "BLOCK" in last_user_message.upper():
print("[Callback] 'BLOCK' keyword found. Skipping LLM call.")
# Return an LlmResponse to skip the actual LLM call
return LlmResponse(
content=types.Content(
role="model",
parts=[types.Part(text="LLM call was blocked by before_model_callback.")],
)
)
else:
print("[Callback] Proceeding with LLM call.")
# Return None to allow the (modified) request to go to the LLM
return None
# Create LlmAgent and Assign Callback
my_llm_agent = LlmAgent(
name="ModelCallbackAgent",
model=GEMINI_2_FLASH,
instruction="You are a helpful assistant.", # Base instruction
description="An LLM agent demonstrating before_model_callback",
before_model_callback=simple_before_model_modifier # Assign the function here
)
APP_NAME = "guardrail_app"
USER_ID = "user_1"
SESSION_ID = "session_001"
# Session and Runner
session_service = InMemorySessionService()
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=my_llm_agent, app_name=APP_NAME, session_service=session_service)
# Agent Interaction
def call_agent(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
call_agent("callback example")
模型后置回调¶
何时触发: 在从 LLM 接收到响应 (LlmResponse
) 之后,在调用智能体进一步处理之前调用。
用途: 允许检查或修改原始 LLM 响应。用例包括:
- 记录模型输出,
- 重新格式化响应,
- 审查模型生成的敏感信息,
- 从 LLM 响应中解析结构化数据并将其存储在
callback_context.state
中 - 或处理特定错误代码。
代码
from google.adk.agents import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.runners import Runner
from typing import Optional
from google.genai import types
from google.adk.sessions import InMemorySessionService
from google.adk.models import LlmResponse
GEMINI_2_FLASH="gemini-2.0-flash"
# --- Define the Callback Function ---
def simple_after_model_modifier(
callback_context: CallbackContext, llm_response: LlmResponse
) -> Optional[LlmResponse]:
"""Inspects/modifies the LLM response after it's received."""
agent_name = callback_context.agent_name
print(f"[Callback] After model call for agent: {agent_name}")
# --- Inspection ---
original_text = ""
if llm_response.content and llm_response.content.parts:
# Assuming simple text response for this example
if llm_response.content.parts[0].text:
original_text = llm_response.content.parts[0].text
print(f"[Callback] Inspected original response text: '{original_text[:100]}...'") # Log snippet
elif llm_response.content.parts[0].function_call:
print(f"[Callback] Inspected response: Contains function call '{llm_response.content.parts[0].function_call.name}'. No text modification.")
return None # Don't modify tool calls in this example
else:
print("[Callback] Inspected response: No text content found.")
return None
elif llm_response.error_message:
print(f"[Callback] Inspected response: Contains error '{llm_response.error_message}'. No modification.")
return None
else:
print("[Callback] Inspected response: Empty LlmResponse.")
return None # Nothing to modify
# --- Modification Example ---
# Replace "joke" with "funny story" (case-insensitive)
search_term = "joke"
replace_term = "funny story"
if search_term in original_text.lower():
print(f"[Callback] Found '{search_term}'. Modifying response.")
modified_text = original_text.replace(search_term, replace_term)
modified_text = modified_text.replace(search_term.capitalize(), replace_term.capitalize()) # Handle capitalization
# Create a NEW LlmResponse with the modified content
# Deep copy parts to avoid modifying original if other callbacks exist
modified_parts = [copy.deepcopy(part) for part in llm_response.content.parts]
modified_parts[0].text = modified_text # Update the text in the copied part
new_response = LlmResponse(
content=types.Content(role="model", parts=modified_parts),
# Copy other relevant fields if necessary, e.g., grounding_metadata
grounding_metadata=llm_response.grounding_metadata
)
print(f"[Callback] Returning modified response.")
return new_response # Return the modified response
else:
print(f"[Callback] '{search_term}' not found. Passing original response through.")
# Return None to use the original llm_response
return None
# Create LlmAgent and Assign Callback
my_llm_agent = LlmAgent(
name="AfterModelCallbackAgent",
model=GEMINI_2_FLASH,
instruction="You are a helpful assistant.",
description="An LLM agent demonstrating after_model_callback",
after_model_callback=simple_after_model_modifier # Assign the function here
)
APP_NAME = "guardrail_app"
USER_ID = "user_1"
SESSION_ID = "session_001"
# Session and Runner
session_service = InMemorySessionService()
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=my_llm_agent, app_name=APP_NAME, session_service=session_service)
# Agent Interaction
def call_agent(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
call_agent("callback example")
工具执行回调¶
这些回调也专用于LlmAgent
,在 LLM 可能请求的工具 (包括FunctionTool
、AgentTool
等) 的执行前后触发。
工具前置回调¶
何时触发: 在调用特定工具的run_async
方法之前,在 LLM 为其生成函数调用之后调用。
用途: 允许检查和修改工具参数,在执行前执行授权检查,记录工具使用尝试,或实现工具级缓存。
返回值效果:
- 如果回调返回
None
,工具的run_async
方法将使用 (可能已修改的)args
执行。 - 如果返回字典,则跳过工具的
run_async
方法。返回的字典直接用作工具调用的结果。这对缓存或覆盖工具行为很有用。
代码
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from typing import Optional
from google.genai import types
from google.adk.sessions import InMemorySessionService
from google.adk.tools import FunctionTool
from google.adk.tools.tool_context import ToolContext
from google.adk.tools.base_tool import BaseTool
from typing import Dict, Any
GEMINI_2_FLASH="gemini-2.0-flash"
def get_capital_city(country: str) -> str:
"""Retrieves the capital city of a given country."""
print(f"--- Tool 'get_capital_city' executing with country: {country} ---")
country_capitals = {
"united states": "Washington, D.C.",
"canada": "Ottawa",
"france": "Paris",
"germany": "Berlin",
}
return country_capitals.get(country.lower(), f"Capital not found for {country}")
capital_tool = FunctionTool(func=get_capital_city)
def simple_before_tool_modifier(
tool: BaseTool, args: Dict[str, Any], tool_context: ToolContext
) -> Optional[Dict]:
"""Inspects/modifies tool args or skips the tool call."""
agent_name = tool_context.agent_name
tool_name = tool.name
print(f"[Callback] Before tool call for tool '{tool_name}' in agent '{agent_name}'")
print(f"[Callback] Original args: {args}")
if tool_name == 'get_capital_city' and args.get('country', '').lower() == 'canada':
print("[Callback] Detected 'Canada'. Modifying args to 'France'.")
args['country'] = 'France'
print(f"[Callback] Modified args: {args}")
return None
# If the tool is 'get_capital_city' and country is 'BLOCK'
if tool_name == 'get_capital_city' and args.get('country', '').upper() == 'BLOCK':
print("[Callback] Detected 'BLOCK'. Skipping tool execution.")
return {"result": "Tool execution was blocked by before_tool_callback."}
print("[Callback] Proceeding with original or previously modified args.")
return None
my_llm_agent = LlmAgent(
name="ToolCallbackAgent",
model=GEMINI_2_FLASH,
instruction="You are an agent that can find capital cities. Use the get_capital_city tool.",
description="An LLM agent demonstrating before_tool_callback",
tools=[capital_tool],
before_tool_callback=simple_before_tool_modifier
)
APP_NAME = "guardrail_app"
USER_ID = "user_1"
SESSION_ID = "session_001"
# Session and Runner
session_service = InMemorySessionService()
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=my_llm_agent, app_name=APP_NAME, session_service=session_service)
# Agent Interaction
def call_agent(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
call_agent("callback example")
工具后置回调¶
何时触发: 在工具的run_async
方法成功完成后立即调用。
用途: 允许在将工具结果发送回 LLM(可能在摘要后) 之前对其进行检查和修改。适用于记录工具结果、后处理或格式化结果,或将结果的特定部分保存到会话状态。
返回值效果:
- 如果回调返回
None
,则使用原始tool_response
。 - 如果返回新字典,则替换原始
tool_response
。这允许修改或过滤 LLM 看到的结果。
代码
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
from typing import Optional
from google.genai import types
from google.adk.sessions import InMemorySessionService
from google.adk.tools import FunctionTool
from google.adk.tools.tool_context import ToolContext
from google.adk.tools.base_tool import BaseTool
from typing import Dict, Any
from copy import copy
GEMINI_2_FLASH="gemini-2.0-flash"
# --- Define a Simple Tool Function (Same as before) ---
def get_capital_city(country: str) -> str:
"""Retrieves the capital city of a given country."""
print(f"--- Tool 'get_capital_city' executing with country: {country} ---")
country_capitals = {
"united states": "Washington, D.C.",
"canada": "Ottawa",
"france": "Paris",
"germany": "Berlin",
}
return {"result": country_capitals.get(country.lower(), f"Capital not found for {country}")}
# --- Wrap the function into a Tool ---
capital_tool = FunctionTool(func=get_capital_city)
# --- Define the Callback Function ---
def simple_after_tool_modifier(
tool: BaseTool, args: Dict[str, Any], tool_context: ToolContext, tool_response: Dict
) -> Optional[Dict]:
"""Inspects/modifies the tool result after execution."""
agent_name = tool_context.agent_name
tool_name = tool.name
print(f"[Callback] After tool call for tool '{tool_name}' in agent '{agent_name}'")
print(f"[Callback] Args used: {args}")
print(f"[Callback] Original tool_response: {tool_response}")
# Default structure for function tool results is {"result": <return_value>}
original_result_value = tool_response.get("result", "")
# original_result_value = tool_response
# --- Modification Example ---
# If the tool was 'get_capital_city' and result is 'Washington, D.C.'
if tool_name == 'get_capital_city' and original_result_value == "Washington, D.C.":
print("[Callback] Detected 'Washington, D.C.'. Modifying tool response.")
# IMPORTANT: Create a new dictionary or modify a copy
modified_response = copy.deepcopy(tool_response)
modified_response["result"] = f"{original_result_value} (Note: This is the capital of the USA)."
modified_response["note_added_by_callback"] = True # Add extra info if needed
print(f"[Callback] Modified tool_response: {modified_response}")
return modified_response # Return the modified dictionary
print("[Callback] Passing original tool response through.")
# Return None to use the original tool_response
return None
# Create LlmAgent and Assign Callback
my_llm_agent = LlmAgent(
name="AfterToolCallbackAgent",
model=GEMINI_2_FLASH,
instruction="You are an agent that finds capital cities using the get_capital_city tool. Report the result clearly.",
description="An LLM agent demonstrating after_tool_callback",
tools=[capital_tool], # Add the tool
after_tool_callback=simple_after_tool_modifier # Assign the callback
)
APP_NAME = "guardrail_app"
USER_ID = "user_1"
SESSION_ID = "session_001"
# Session and Runner
session_service = InMemorySessionService()
session = session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=my_llm_agent, app_name=APP_NAME, session_service=session_service)
# Agent Interaction
def call_agent(query):
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
for event in events:
if event.is_final_response():
final_response = event.content.parts[0].text
print("Agent Response: ", final_response)
call_agent("callback example")