回调的设计模式和最佳实践¶
回调提供了强大的智能体生命周期钩子。以下是常见的设计模式,说明如何在 ADK 中有效地利用回调,随后是实施的最佳实践。
设计模式¶
这些模式展示了使用回调增强或控制智能体行为的典型方式:
1. 防护机制与策略执行¶
- 模式: 在请求到达 LLM 或工具之前拦截请求以执行规则。
- 如何实现: 使用
before_model_callback
检查LlmRequest
提示,或使用before_tool_callback
检查工具参数。如果检测到策略违规(如禁用话题、脏话),返回预定义响应(LlmResponse
或dict
/Map
)以阻止操作,并可选地更新context.state
记录违规。 - 示例:
before_model_callback
检查llm_request.contents
是否包含敏感关键词,如果发现则返回标准的"无法处理此请求"LlmResponse
,从而阻止 LLM 调用。
2. 动态状态管理¶
- 模式: 在回调中读取和写入会话状态,使智能体行为具有上下文感知能力并在步骤间传递数据。
- 如何实现: 访问
callback_context.state
或tool_context.state
。修改 (state['key'] = value
) 会自动通过后续的Event.actions.state_delta
被SessionService
跟踪并持久化。 - 示例:
after_tool_callback
将工具结果中的transaction_id
保存到tool_context.state['last_transaction_id']
。后续的before_agent_callback
可能读取state['user_tier']
来自定义智能体的问候语。
3. 日志记录和监控¶
- 模式: 在特定生命周期点添加详细日志以便观察和调试。
- 如何实现: 实现回调 (例如,
before_agent_callback
、after_tool_callback
、after_model_callback
) 打印或发送包含智能体名称、工具名称、调用 ID 以及来自上下文或参数的相关数据的结构化日志。 - 示例: 记录如
INFO: [Invocation: e-123] Before Tool: search_api - Args: {'query': 'ADK'}
的消息。
4. 缓存¶
- 模式: 通过缓存结果避免重复的 LLM 调用或工具执行。
- 如何实现: 在
before_model_callback
或before_tool_callback
中,根据请求/参数生成缓存键。检查context.state
(或外部缓存)是否有该键。如果有,直接返回缓存的LlmResponse
或结果,跳过实际操作。如果没有,则允许操作继续,并在对应的after_
回调(after_model_callback
、after_tool_callback
)中使用该键将新结果存入缓存。 - 示例:
get_stock_price(symbol)
的before_tool_callback
检查state[f"cache:stock:{symbol}"]
。如果存在,返回缓存价格;否则允许 API 调用,after_tool_callback
将结果保存到 state 键。
5. 请求/响应修改¶
- 模式: 在数据发送到 LLM/工具前或收到后进行修改。
- 如何实现:
before_model_callback
:修改llm_request
(如根据state
添加系统指令)。after_model_callback
:修改返回的LlmResponse
(如格式化文本、过滤内容)。before_tool_callback
:修改工具args
字典(或 Java 中的 Map)。after_tool_callback
:修改tool_response
字典(或 Java 中的 Map)。
- 示例: 如果
context.state['lang'] == 'es'
,before_model_callback
会在llm_request.config.system_instruction
末尾追加"User language preference: Spanish"。
6. 步骤的条件跳过¶
- 模式: 根据特定条件阻止标准操作 (智能体运行、LLM 调用、工具执行)。
- 如何实现: 从
before_
回调返回一个值 (before_agent_callback
中的Content
,before_model_callback
中的LlmResponse
,before_tool_callback
中的dict
)。框架将此返回值解释为该步骤的结果,跳过正常执行。 - 示例:
before_tool_callback
检查tool_context.state['api_quota_exceeded']
。如果为True
,它返回{'error': 'API quota exceeded'}
,防止实际的工具函数运行。
7. 工具特定操作 (认证与摘要控制)¶
- 模式: 处理工具生命周期特定的操作,主要是认证和工具结果 LLM 摘要控制。
- 如何实现: 在工具回调 (
before_tool_callback
、after_tool_callback
) 中使用ToolContext
。- 认证: 如果需要凭据但未找到 (例如,通过
tool_context.get_auth_response
或状态检查),在before_tool_callback
中调用tool_context.request_credential(auth_config)
。这会启动认证流程。 - 摘要: 如果工具的原始字典输出应该直接传回给 LLM 或潜在地直接显示,绕过默认的 LLM 摘要步骤,设置
tool_context.actions.skip_summarization = True
。
- 认证: 如果需要凭据但未找到 (例如,通过
- 示例: 安全 API 的
before_tool_callback
检查状态中的认证令牌;如果缺失,它调用request_credential
。返回结构化 JSON 的工具的after_tool_callback
可能设置skip_summarization = True
。
8. 制品(Artifacts)处理¶
- 模式: 在智能体生命周期内保存或加载与会话相关的文件或大数据块。
- 如何实现: 使用
callback_context.save_artifact
/await tool_context.save_artifact
存储数据(如生成的报告、日志、中间数据)。使用load_artifact
检索先前存储的制品。更改通过Event.actions.artifact_delta
跟踪。 - 示例: "generate_report" 工具的
after_tool_callback
使用await tool_context.save_artifact("report.pdf", report_part)
保存输出文件。before_agent_callback
可能使用callback_context.load_artifact("agent_config.json")
加载配置制品。
回调的最佳实践¶
- 保持专注: 让每个回调只做一件事(如只做日志、只做校验),避免写成大而全的回调。
- 注意性能: 回调在智能体处理循环中同步执行。避免长时间运行或阻塞操作(如网络请求、重计算)。如需异步处理请谨慎设计,避免引入复杂性。
- 优雅处理错误: 在回调函数中使用
try...except/ catch
。适当记录错误,并决定是否中止智能体调用或尝试恢复。不要让回调错误导致整个进程崩溃。 - 谨慎管理状态:
- 明确地读取和写入
context.state
。更改会在当前调用中立即可见,并在事件处理结束时持久化。 - 使用具体的 state 键而不是大范围修改结构,避免意外副作用。
- 建议使用 state 前缀(
State.APP_PREFIX
、State.USER_PREFIX
、State.TEMP_PREFIX
),特别是在持久化SessionService
实现下。
- 明确地读取和写入
- 考虑幂等性: 如果回调涉及外部副作用(如递增外部计数器),尽量设计为幂等(同一输入多次运行安全),以应对框架或应用的重试。
- 充分测试: 用 mock context 对象对回调函数做单元测试。做集成测试确保回调在完整智能体流程中表现正确。
- 确保清晰: 用有意义的函数名和清晰的 docstring 说明回调用途、运行时机和副作用(尤其是状态修改)。
- 用对上下文类型: 始终使用提供的具体上下文类型(智能体/模型用
CallbackContext
,工具用ToolContext
),以确保能访问正确的方法和属性。
通过应用这些模式和最佳实践,你可以有效地使用回调在 ADK 中创建更健壮、可观察和自定义的智能体行为。