钩子
中间件提供两种风格的钩子来拦截代理执行:节点式钩子
在特定执行点顺序运行。
包装式钩子
围绕每次模型或工具调用运行。
节点式钩子
在特定执行点顺序运行。用于日志记录、验证和状态更新。 选择中间件所需的钩子。您可以选择节点式钩子或包装式钩子。 节点式钩子在特定执行点运行:
包装式钩子围绕每次调用运行,让您控制执行:
示例:
- 装饰器
- 类
包装式钩子
拦截执行并控制处理器的调用时机。用于重试、缓存和转换。 您可以决定处理器是调用零次(短路)、一次(正常流程)还是多次(重试逻辑)。 可用钩子:wrap_model_call- 围绕每次模型调用wrap_tool_call- 围绕每次工具调用
- 装饰器
- 类
状态更新
节点式和包装式钩子都可以更新代理状态。其机制不同:- 节点式钩子 (
before_agent,before_model,after_model,after_agent): 直接返回一个字典。字典通过图的归约器应用到代理状态。 - 包装式钩子 (
wrap_model_call,wrap_tool_call): 对于模型调用,返回一个包含Command的ExtendedModelResponse,以在模型响应旁注入状态更新。对于工具调用,直接返回一个Command。当您需要根据在模型或工具调用期间运行的逻辑来跟踪或更新状态时(例如摘要触发点、使用元数据或根据请求或响应计算的自定义字段),请使用这些钩子。
节点式钩子
从节点式钩子返回一个字典,将更新合并到代理状态中。字典键映射到状态字段。包装式钩子
从wrap_model_call 返回一个包含 Command 的 ExtendedModelResponse,以从模型调用层注入状态更新:
Command 通过图的归约器流动,因此更新被正确应用,消息是累加的,而不是替换现有状态。
多个中间件的组合
当多个中间件层返回ExtendedModelResponse 时,它们的命令会组合:
- 命令通过归约器应用: 每个
Command成为一个独立的状态更新。对于消息,这意味着它们是累加的。 - 外层在冲突中获胜: 对于非归约器状态字段,命令按内层优先应用,然后是外层。对于有冲突的键,最外层中间件的值优先。
- 重试安全: 如果外层中间件实现了可能导致再次调用
handler()的逻辑(例如重试逻辑),则之前调用中的命令会被丢弃。
创建中间件
您可以通过两种方式创建中间件:基于装饰器的中间件
对于单钩子中间件,快速简单。使用装饰器包装单个函数。
基于类的中间件
对于具有多个钩子或配置的复杂中间件更强大。
基于装饰器的中间件
对于单钩子中间件,快速简单。使用装饰器包装单个函数。 可用装饰器: 节点式:@before_agent- 代理启动前运行(每次调用一次)@before_model- 每次模型调用前运行@after_model- 每次模型响应后运行@after_agent- 代理完成后运行(每次调用一次)
@wrap_model_call- 用自定义逻辑包装每次模型调用@wrap_tool_call- 用自定义逻辑包装每次工具调用
@dynamic_prompt- 生成动态系统提示
- 需要单个钩子
- 无复杂配置
- 快速原型设计
基于类的中间件
对于具有多个钩子或配置的复杂中间件更强大。当您需要为同一个钩子定义同步和异步实现,或者想要将多个钩子组合在一个中间件中时,请使用类。 python 一个AgentMiddleware 子类可以声明三个类属性,代理工厂在编译时会拾取这些属性:
state_schema— 使用自定义字段扩展代理状态。参见 自定义状态模式。tools— 注册随中间件一起提供的额外工具(例如,待办事项列表中间件上的write_todos)。transformers— 注册作用域感知的流转换器工厂。参见 自定义流转换器。 :::
- 为同一个钩子定义同步和异步实现
- 单个中间件需要多个钩子
- 需要复杂配置(例如,可配置阈值、自定义模型)
- 通过初始化时配置在项目间重用
自定义状态模式
如果您的中间件需要在钩子之间跟踪状态,中间件可以使用自定义属性扩展代理的状态。这使得中间件能够:- 跨执行跟踪状态:维护计数器、标志或其他在代理执行生命周期内持续存在的值
-
在钩子之间共享数据:将信息从
before_model传递到after_model或在不同的中间件实例之间传递 - 实现横切关注点:添加速率限制、使用跟踪、用户上下文或审计日志等功能,而无需修改核心代理逻辑
- 做出条件决策:使用累积的状态来确定是否继续执行、跳转到不同的节点或动态修改行为
- 装饰器
- 类
自定义流转换器
中间件注册的转换器需要
langchain>=1.3.2。ToolCallTransformer 保持在前面,并让调用者提供的条目落在最后。
将 transformers 类属性设置为工厂可调用对象的元组。每个工厂的形状为 Callable[[tuple[str, ...]], StreamTransformer],并作为 factory(scope) 调用,其中 scope 是 mini-mux 作用域元组(() 表示根,非空表示子图);每次调用返回一个新的转换器可保持每个子图隔离。
执行顺序
使用多个中间件时,了解它们的执行方式:执行流程
执行流程
前置钩子按顺序运行:
middleware1.before_agent()middleware2.before_agent()middleware3.before_agent()
middleware1.before_model()middleware2.before_model()middleware3.before_model()
middleware1.wrap_model_call()→middleware2.wrap_model_call()→middleware3.wrap_model_call()→ 模型
middleware3.after_model()middleware2.after_model()middleware1.after_model()
middleware3.after_agent()middleware2.after_agent()middleware1.after_agent()
before_*钩子:从第一个到最后一个after_*钩子:从最后一个到第一个(反向)wrap_*钩子:嵌套(第一个中间件包装所有其他中间件)
代理跳转
要从中间件提前退出,请返回一个包含jump_to 的字典:
可用的跳转目标:
'end': 跳转到代理执行结束(或第一个after_agent钩子)'tools': 跳转到工具节点'model': 跳转到模型节点(或第一个before_model钩子)
- 装饰器
- 类
最佳实践
- 保持中间件专注 - 每个中间件应做好一件事
- 优雅地处理错误 - 不要让中间件错误导致代理崩溃
- 使用合适的钩子类型:
- 节点式用于顺序逻辑(日志记录、验证)
- 包装式用于控制流(重试、回退、缓存)
- 清晰地记录任何自定义状态属性
- 在集成之前独立测试中间件
- 考虑执行顺序 - 将关键中间件放在列表的前面
- 尽可能使用内置中间件
示例
动态提示
在运行时动态修改系统提示,以便在每次模型调用前注入上下文、用户特定指令或其他信息。这是最常见的中间件用例之一。 使用ModelRequest 上的 system_message 字段来读取和修改系统提示。它包含一个 SystemMessage 对象(即使代理是使用字符串 system_prompt 创建的)。
- 装饰器
- 类
ModelRequest.system_message始终是一个SystemMessage对象,即使代理是使用system_prompt="string"创建的- 使用
SystemMessage.content_blocks以块列表的形式访问内容,无论原始内容是字符串还是列表 - 修改系统消息时,使用
content_blocks并附加新块以保留现有结构 - 您可以直接将
SystemMessage对象传递给create_agent的system_prompt参数,用于高级用例,如缓存控制
动态模型选择
- 装饰器
- 类
动态选择工具
在运行时选择相关工具以提高性能和准确性。本节涵盖过滤预注册工具。有关注册在运行时发现的工具(例如来自 MCP 服务器),请参阅 运行时工具注册。 好处:- 更短的提示 - 通过仅暴露相关工具来降低复杂性
- 更好的准确性 - 模型从更少的选项中正确选择
- 权限控制 - 根据用户访问权限动态过滤工具
- 装饰器
- 类
工具调用监控
- 装饰器
- 类
提示缓存 (Anthropic)
使用 Anthropic 模型时,使用带有缓存控制指令的结构化内容块来缓存大型系统提示:- 装饰器
- 类
ModelRequest.system_message始终是一个SystemMessage对象,即使代理是使用system_prompt="string"创建的- 使用
SystemMessage.content_blocks以块列表的形式访问内容,无论原始内容是字符串还是列表 - 修改系统消息时,使用
content_blocks并附加新块以保留现有结构 - 您可以直接将
SystemMessage对象传递给create_agent的system_prompt参数,用于高级用例,如缓存控制
其他资源
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

