Skip to content

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

回调机制为智能体生命周期提供了强大的钩子功能。本文将展示在ADK中高效运用回调的常见设计模式,并附上实现时的最佳实践指南。

设计模式

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

1. 防护栏与策略执行

  • 模式:在请求到达大模型或工具前进行拦截以执行规则
  • 实现:使用before_model_callback检查LlmRequest提示词,或通过before_tool_callback检查工具参数(args)。若检测到策略违规(如禁用话题、不当用语),返回预定义响应(LlmResponsedict)以阻断操作,并可选更新context.state记录违规
  • 示例before_model_callback检查llm_request.contents中的敏感关键词,若发现则返回标准"Cannot process this request"LlmResponse,阻止大模型调用

2. 动态状态管理

  • 模式:在回调中读写会话状态,使智能体行为具备上下文感知能力并在步骤间传递数据
  • 实现:访问callback_context.statetool_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_callbackafter_tool_callbackafter_model_callback)打印或发送结构化日志,包含智能体名称、工具名称、调用ID及上下文/参数中的相关数据
  • 示例:记录类似INFO: [Invocation: e-123] Before Tool: search_api - Args: {'query': 'ADK'}的日志消息

4. 缓存机制

  • 模式:通过缓存结果避免冗余的大模型调用或工具执行
  • 实现:在before_model_callbackbefore_tool_callback中基于请求/参数生成缓存键,检查context.state(或外部缓存)。若命中则直接返回缓存的LlmResponse或结果dict;若未命中则允许操作执行,并在对应after_回调(after_model_callbackafter_tool_callback)中将新结果存入缓存
  • 示例before_tool_callback检查state[f"cache:stock:{symbol}"]中的get_stock_price(symbol)。若存在则返回缓存价格,否则允许API调用并由after_tool_callback将结果保存至状态键

5. 请求/响应修改

  • 模式:在数据发送至大模型/工具前或接收后立即进行修改
  • 实现
    • before_model_callback:修改llm_request(如基于state添加系统指令)
    • after_model_callback:修改返回的LlmResponse(如格式化文本、过滤内容)
    • before_tool_callback:修改工具args字典
    • after_tool_callback:修改tool_response字典
  • 示例:若context.state['lang'] == 'es'存在,before_model_callback会在llm_request.config.system_instruction后追加"User language preference: Spanish"

6. 条件式步骤跳过

  • 模式:基于特定条件阻止标准操作(智能体运行、大模型调用、工具执行)
  • 实现:从before_回调(ContentLlmResponsedict)返回值。框架会将该值视为步骤结果,跳过正常执行流程
  • 示例before_tool_callback检查tool_context.state['api_quota_exceeded']。若True则返回{'error': 'API quota exceeded'},阻止实际工具函数运行

7. 工具专属操作(鉴权与摘要控制)

  • 模式:处理工具生命周期中的专属操作,主要包括鉴权和控制大模型对工具结果的摘要生成
  • 实现:在工具回调(before_tool_callbackafter_tool_callback)中使用ToolContext
    • 鉴权:在before_tool_callback中调用tool_context.request_credential(auth_config)(当需要凭证但未找到时,通过tool_context.get_auth_response或状态检查),触发认证流程
    • 摘要控制:设置tool_context.actions.skip_summarization = True可将工具原始字典输出直接传回大模型或显示,绕过默认的摘要生成步骤
  • 示例:安全API的before_tool_callback检查状态中的认证令牌,若缺失则调用request_credential;返回结构化JSON的工具可能设置skip_summarization = True

8. 产物处理

  • 模式:在智能体生命周期中保存或加载会话相关文件/大数据块
  • 实现:使用callback_context.save_artifact/tool_context.save_artifact存储数据(如生成报告、日志、中间数据),通过load_artifact检索已存产物。变更通过Event.actions.artifact_delta追踪
  • 示例:"generate_report"工具的after_tool_callback使用tool_context.save_artifact("report.pdf", report_part)保存输出文件;before_agent_callback可能通过callback_context.load_artifact("agent_config.json")加载配置产物

回调最佳实践

  • 功能聚焦:每个回调应专注单一明确目的(如仅日志记录、仅验证)。避免构建多功能混杂的回调
  • 性能考量:回调在智能体处理循环中同步执行。避免长时间运行或阻塞操作(网络调用、重计算)。必要时卸载任务,但需注意会增加复杂度
  • 优雅容错:在回调函数中使用try...except块。合理记录错误并决定应中止智能体调用还是尝试恢复。避免回调错误导致整个进程崩溃
  • 谨慎状态管理
    • context.state的读写操作需谨慎。变更在当前调用中立即生效,并在事件处理结束时持久化
    • 使用特定状态键而非修改宽泛结构,避免副作用
    • 考虑使用状态前缀(State.APP_PREFIXState.USER_PREFIXState.TEMP_PREFIX)提升可读性,特别是持久化SessionService实现时
  • 幂等设计:若回调会产生外部副作用(如外部计数器递增),尽可能设计为幂等操作(相同输入可安全重复执行),以应对框架或应用中的潜在重试
  • 全面测试:使用模拟上下文对象对回调函数进行单元测试。执行集成测试确保回调在完整智能体流程中正常工作
  • 清晰表达:为回调函数使用描述性名称。添加清晰文档字符串说明其目的、触发时机及副作用(特别是状态修改)
  • 正确上下文类型:始终使用指定的上下文类型(大模型/智能体用CallbackContext,工具用ToolContext),确保访问正确的方法和属性

通过应用这些模式与最佳实践,您可以在ADK中有效利用回调机制,构建更健壮、可观测且可定制的智能体行为。