回调的设计模式和最佳实践¶
回调提供了强大的智能体生命周期钩子。以下是常见的设计模式,说明如何在 ADK 中有效地利用回调,随后是实施的最佳实践。
设计模式¶
这些模式展示了使用回调增强或控制智能体行为的典型方式:
1. 护栏和政策执行¶
模式概述: 在请求到达 LLM 或工具之前拦截它们以执行规则。
实施:
- 使用
before_model_callback检查LlmRequest提示 - 使用
before_tool_callback检查工具参数 - 如果检测到政策违规(例如,禁止的主题、亵渎):
- 返回预定义的响应(
LlmResponse或dict/Map)以阻止操作 - 可选地更新
context.state以记录违规
示例用例:
before_model_callback 检查 llm_request.contents 中的敏感关键词,如果找到则返回标准的"Cannot process this request" 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_回调将新结果存储在使用该键的缓存中
示例用例:
before_tool_callback 用于 get_stock_price(symbol) 检查 state[f"cache:stock:{symbol}"]。如果存在,返回缓存的价格;否则,允许 API 调用,after_tool_callback 将结果保存到状态键。
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 将"User language preference: Spanish"附加到 llm_request.config.system_instruction。
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. 制品处理¶
模式概述: 在智能体生命周期期间保存或加载与会话相关的文件或大型数据块。
实施:
- 保存: 使用
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.APP_PREFIX、State.USER_PREFIX、State.TEMP_PREFIX)以提高清晰度,特别是对于持久的SessionService实现
可靠性¶
考虑幂等性: 如果回调执行具有外部副作用的操作(例如,递增外部计数器),如果可能,将其设计为幂等(使用相同输入多次运行是安全的),以处理框架或应用程序中的潜在重试。
测试和文档¶
彻底测试:
- 使用模拟上下文对象对回调函数进行单元测试
- 执行集成测试以确保回调在完整智能体流程中正确运行
确保清晰:
- 为回调函数使用描述性名称
- 添加清晰的文档字符串,解释其目的、何时运行以及任何副作用(特别是状态修改)
使用正确的上下文类型:
始终使用提供的特定上下文类型(智能体/模型的 CallbackContext,工具的 ToolContext)以确保访问适当的方法和属性。
通过应用这些模式和最佳实践,你可以有效地使用回调在 ADK 中创建更健壮、可观察和自定义的智能体行为。