Skip to content

回调的设计模式和最佳实践

回调提供了强大的智能体生命周期钩子。以下是常见的设计模式,说明如何在 ADK 中有效地利用回调,随后是实施的最佳实践。

设计模式

这些模式展示了使用回调增强或控制智能体行为的典型方式:

1. 防护机制与策略执行

  • 模式: 在请求到达 LLM 或工具之前拦截请求以执行规则。
  • 如何实现: 使用 before_model_callback 检查 LlmRequest 提示,或使用 before_tool_callback 检查工具参数。如果检测到策略违规(如禁用话题、脏话),返回预定义响应(LlmResponsedict/ Map)以阻止操作,并可选地更新 context.state 记录违规。
  • 示例: before_model_callback 检查 llm_request.contents 是否包含敏感关键词,如果发现则返回标准的"无法处理此请求" LlmResponse,从而阻止 LLM 调用。

2. 动态状态管理

  • 模式: 在回调中读取和写入会话状态,使智能体行为具有上下文感知能力并在步骤间传递数据。
  • 如何实现: 访问callback_context.statetool_context.state。修改 (state['key'] = value) 会自动通过后续的Event.actions.state_deltaSessionService跟踪并持久化。
  • 示例: after_tool_callback将工具结果中的transaction_id保存到tool_context.state['last_transaction_id']。后续的before_agent_callback可能读取state['user_tier']来自定义智能体的问候语。

3. 日志记录和监控

  • 模式: 在特定生命周期点添加详细日志以便观察和调试。
  • 如何实现: 实现回调 (例如,before_agent_callbackafter_tool_callbackafter_model_callback) 打印或发送包含智能体名称、工具名称、调用 ID 以及来自上下文或参数的相关数据的结构化日志。
  • 示例: 记录如INFO: [Invocation: e-123] Before Tool: search_api - Args: {'query': 'ADK'}的消息。

4. 缓存

  • 模式: 通过缓存结果避免重复的 LLM 调用或工具执行。
  • 如何实现:before_model_callbackbefore_tool_callback 中,根据请求/参数生成缓存键。检查 context.state(或外部缓存)是否有该键。如果有,直接返回缓存的 LlmResponse 或结果,跳过实际操作。如果没有,则允许操作继续,并在对应的 after_ 回调(after_model_callbackafter_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中的Contentbefore_model_callback中的LlmResponsebefore_tool_callback中的dict)。框架将此返回值解释为该步骤的结果,跳过正常执行。
  • 示例: before_tool_callback检查tool_context.state['api_quota_exceeded']。如果为True,它返回{'error': 'API quota exceeded'},防止实际的工具函数运行。

7. 工具特定操作 (认证与摘要控制)

  • 模式: 处理工具生命周期特定的操作,主要是认证和工具结果 LLM 摘要控制。
  • 如何实现: 在工具回调 (before_tool_callbackafter_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_PREFIXState.USER_PREFIXState.TEMP_PREFIX),特别是在持久化 SessionService 实现下。
  • 考虑幂等性: 如果回调涉及外部副作用(如递增外部计数器),尽量设计为幂等(同一输入多次运行安全),以应对框架或应用的重试。
  • 充分测试: 用 mock context 对象对回调函数做单元测试。做集成测试确保回调在完整智能体流程中表现正确。
  • 确保清晰: 用有意义的函数名和清晰的 docstring 说明回调用途、运行时机和副作用(尤其是状态修改)。
  • 用对上下文类型: 始终使用提供的具体上下文类型(智能体/模型用 CallbackContext,工具用 ToolContext),以确保能访问正确的方法和属性。

通过应用这些模式和最佳实践,你可以有效地使用回调在 ADK 中创建更健壮、可观察和自定义的智能体行为。