回调的设计模式和最佳实践¶
回调提供了强大的智能体生命周期钩子。以下是常见的设计模式,说明如何在 ADK 中有效地利用回调,随后是实施的最佳实践。
设计模式¶
这些模式展示了使用回调增强或控制智能体行为的典型方式:
1. 防护机制与策略执行¶
- 模式: 在请求到达 LLM 或工具之前拦截请求以执行规则。
- 如何实现: 使用
before_model_callback
检查LlmRequest
提示或before_tool_callback
检查工具参数 (args
)。如果检测到策略违规 (例如,禁止的主题、亵渎内容),返回预定义的响应 (LlmResponse
或dict
) 阻止操作,并可选择更新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
或结果dict
,跳过实际操作。如果未找到,允许操作继续,并使用相应的after_
回调 (after_model_callback
、after_tool_callback
) 使用该键将新结果存储在缓存中。 - 示例:
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
字典。after_tool_callback
:修改tool_response
字典。
- 示例: 如果
context.state['lang'] == 'es'
,before_model_callback
在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. 制品(Artifacts)处理¶
- 模式: 在智能体生命周期中保存或加载与会话相关的文件或大数据块。
- 如何实现: 使用
callback_context.save_artifact
/tool_context.save_artifact
存储数据 (例如,生成的报告、日志、中间数据)。使用load_artifact
检索先前存储的制品(Artifacts)。更改通过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")
加载配置制品(Artifacts)。
回调的最佳实践¶
- 保持专注: 设计每个回调用于单一、明确定义的目的 (例如,仅日志记录,仅验证)。避免单体回调。
- 注意性能: 回调在智能体的处理循环中同步执行。避免长时间运行或阻塞操作 (网络调用、重计算)。如有必要可以卸载,但要知道这会增加复杂性。
- 优雅处理错误: 在回调函数内使用
try...except
块。适当记录错误并决定智能体调用是应该停止还是尝试恢复。不要让回调错误崩溃整个进程。 - 谨慎管理状态:
- 有意识地从
context.state
读取和写入。更改在当前调用中立即可见,并在事件处理结束时持久化。 - 使用特定的状态键而不是修改广泛结构,以避免意外的副作用。
- 考虑使用状态前缀 (
State.APP_PREFIX
、State.USER_PREFIX
、State.TEMP_PREFIX
) 以清晰,特别是使用持久性SessionService
实现时。
- 有意识地从
- 考虑幂等性: 如果回调执行具有外部副作用的操作 (例如,递增外部计数器),如果可能的话,设计它为幂等 (使用相同输入多次运行是安全的),以处理框架或应用程序中的潜在重试。
- 彻底测试: 使用模拟上下文对象对回调函数进行单元测试。执行集成测试以确保回调在完整的智能体流程中正常工作。
- 确保清晰: 为回调函数使用描述性名称。添加清晰的文档字符串解释其目的、何时运行以及任何副作用 (特别是状态修改)。
- 使用正确的上下文类型: 始终使用提供的特定上下文类型 (
CallbackContext
用于智能体/模型,ToolContext
用于工具) 以确保访问适当的方法和属性。
通过应用这些模式和最佳实践,你可以有效地使用回调在 ADK 中创建更健壮、可观察和自定义的智能体行为。