Skip to main content
通过实现在代理执行流程特定点运行的钩子来构建自定义中间件。

钩子

中间件提供两种风格的钩子来拦截代理执行:

节点式钩子

在特定执行点按顺序运行。

包装式钩子

在每次模型或工具调用前后运行。

节点式钩子

在特定执行点按顺序运行。用于日志记录、验证和状态更新。 选择你的中间件所需的钩子。你可以在节点式钩子和包装式钩子之间进行选择。 节点式钩子在特定执行点运行: 包装式钩子在每次调用前后运行,让你控制执行: 示例:

包装式钩子

拦截执行并控制处理程序何时被调用。用于重试、缓存和转换。 你可以决定处理程序被调用零次(短路)、一次(正常流程)或多次(重试逻辑)。 可用钩子:
  • wrap_model_call - 在每次模型调用前后
  • wrap_tool_call - 在每次工具调用前后
示例:

状态更新

节点式和包装式钩子都可以更新代理状态。机制不同:
  • 节点式钩子before_agentbefore_modelafter_modelafter_agent):直接返回一个字典。该字典使用图的归约器应用到代理状态。
  • 包装式钩子wrap_model_callwrap_tool_call):对于模型调用,返回一个带有 CommandExtendedModelResponse,以便在模型响应旁注入状态更新。对于工具调用,直接返回一个 Command。当你需要基于模型或工具调用期间运行的逻辑来跟踪或更新状态时使用这些,例如摘要触发点、使用元数据或从请求或响应计算的自定义字段。

节点式钩子

从节点式钩子返回一个字典以将更新合并到代理状态。字典键映射到状态字段。

包装式钩子

wrap_model_call 返回一个带有 CommandExtendedModelResponse,以从模型调用层注入状态更新:
Command 通过图的归约器流动,因此更新被正确应用,并且消息是累加的,而不是替换现有状态。

与多个中间件组合

当多个中间件层返回 ExtendedModelResponse 时,它们的命令会组合:
  • 命令通过归约器应用: 每个 Command 成为一个单独的状态更新。对于消息,这意味着它们是累加的。
  • 外部在冲突时获胜: 对于非归约器状态字段,命令按从内到外的顺序应用。最外层中间件的值在冲突键上优先。
  • 重试安全: 如果外部中间件实现了可能导致多次调用 handler() 的逻辑(例如重试逻辑),则来自早期调用的命令将被丢弃。

创建中间件

你可以通过两种方式创建中间件:

基于装饰器的中间件

对于单钩子中间件快速简单。使用装饰器包装单个函数。

基于类的中间件

对于具有多个钩子或配置的复杂中间件更强大。

基于装饰器的中间件

对于单钩子中间件快速简单。使用装饰器包装单个函数。 可用装饰器: 节点式: 包装式: 便捷: 示例:
何时使用装饰器:
  • 需要单个钩子
  • 无复杂配置
  • 快速原型设计

基于类的中间件

对于具有多个钩子或配置的复杂中间件更强大。当你需要为同一个钩子定义同步和异步实现,或者想在单个中间件中组合多个钩子时使用类。 示例:
何时使用类:
  • 为同一个钩子定义同步和异步实现
  • 单个中间件中需要多个钩子
  • 需要复杂配置(例如,可配置阈值、自定义模型)
  • 通过初始化时配置在项目间复用

自定义状态模式

如果你的中间件需要跨钩子跟踪状态,中间件可以用自定义属性扩展代理的状态。这使得中间件能够:
  • 跨执行跟踪状态:维护计数器、标志或其他在整个代理执行生命周期中持久化的值
  • 在钩子之间共享数据:将信息从 before_model 传递到 after_model 或在不同中间件实例之间传递
  • 实现横切关注点:添加速率限制、使用跟踪、用户上下文或审计日志等功能,而无需修改核心代理逻辑
  • 做出条件决策:使用累积状态来确定是否继续执行、跳转到不同节点或动态修改行为

执行顺序

使用多个中间件时,了解它们的执行方式:
前置钩子按顺序运行:
  1. middleware1.before_agent()
  2. middleware2.before_agent()
  3. middleware3.before_agent()
代理循环开始
  1. middleware1.before_model()
  2. middleware2.before_model()
  3. middleware3.before_model()
包装钩子像函数调用一样嵌套:
  1. middleware1.wrap_model_call()middleware2.wrap_model_call()middleware3.wrap_model_call() → 模型
后置钩子按相反顺序运行:
  1. middleware3.after_model()
  2. middleware2.after_model()
  3. middleware1.after_model()
代理循环结束
  1. middleware3.after_agent()
  2. middleware2.after_agent()
  3. middleware1.after_agent()
关键规则:
  • before_* 钩子:从先到后
  • after_* 钩子:从后到先(反向)
  • wrap_* 钩子:嵌套(第一个中间件包装所有其他中间件)

代理跳转

要从中间件提前退出,返回一个包含 jump_to 的字典: 可用跳转目标:
  • 'end':跳转到代理执行结束(或第一个 after_agent 钩子)
  • 'tools':跳转到工具节点
  • 'model':跳转到模型节点(或第一个 before_model 钩子)

最佳实践

  1. 保持中间件专注 - 每个中间件应做好一件事
  2. 优雅地处理错误 - 不要让中间件错误导致代理崩溃
  3. 使用适当的钩子类型
    • 节点式用于顺序逻辑(日志记录、验证)
    • 包装式用于控制流(重试、回退、缓存)
  4. 清晰地记录任何自定义状态属性
  5. 在集成前独立进行单元测试
  6. 考虑执行顺序 - 将关键中间件放在列表前面
  7. 尽可能使用内置中间件

示例

动态提示

在运行时动态修改系统提示,以便在每次模型调用前注入上下文、用户特定指令或其他信息。这是最常见的中间件用例之一。 使用 ModelRequest 上的 system_message 字段来读取和修改系统提示。它包含一个 SystemMessage 对象(即使代理是用字符串 system_prompt 创建的)。
  • ModelRequest.system_message 始终是一个 SystemMessage 对象,即使代理是用 system_prompt="string" 创建的
  • 使用 SystemMessage.content_blocks 将内容作为块列表访问,无论原始内容是字符串还是列表
  • 修改系统消息时,使用 content_blocks 并追加新块以保留现有结构
  • 你可以将 SystemMessage 对象直接传递给 create_agentsystem_prompt 参数,用于缓存控制等高级用例

动态模型选择

动态选择工具

在运行时选择相关工具以提高性能和准确性。本节介绍过滤预注册工具。有关注册在运行时发现的工具(例如,从 MCP 服务器),请参阅运行时工具注册 好处:
  • 更短的提示 - 通过仅暴露相关工具来降低复杂性
  • 更好的准确性 - 模型从更少的选项中正确选择
  • 权限控制 - 基于用户访问动态过滤工具

工具调用监控

提示缓存(Anthropic)

使用 Anthropic 模型时,使用带有缓存控制指令的结构化内容块来缓存大型系统提示:
注意:
  • ModelRequest.system_message 始终是一个 SystemMessage 对象,即使代理是用 system_prompt="string" 创建的
  • 使用 SystemMessage.content_blocks 将内容作为块列表访问,无论原始内容是字符串还是列表
  • 修改系统消息时,使用 content_blocks 并追加新块以保留现有结构
  • 你可以将 SystemMessage 对象直接传递给 create_agentsystem_prompt 参数,用于缓存控制等高级用例
:::

附加资源