第 7 章:中间件:扩展智能体行为的机制
中间件让你可以在智能体执行的关键节点插入自定义逻辑,从而实现日志、过滤、状态修改等功能。
中间件的原理:在执行关键阶段拦截与修改
中间件(Middleware)为 LangChain 智能体提供了强大的可扩展性,允许开发者在执行的不同阶段自定义智能体行为。通过中间件,开发者可以拦截和修改数据流,实现高级的上下文工程(Context Engineering)。
将中间件添加到智能体的方法是在创建智能体时将其传入 middleware 列表。
中间件是实现上下文工程的底层机制。智能体的核心执行循环涉及模型调用和工具执行,而中间件则在这些步骤的之前、之后和周围暴露了钩子(hooks)。
中间件的核心能力包括:
- 更新上下文:修改状态(State)和存储(Store),以持久化更改、更新对话历史或注入上下文信息。
- 控制流程:跳转到智能体生命周期中的不同步骤,实现提前退出或重试逻辑。
中间件无缝集成到智能体的执行图(Execution Graph)中,允许开发者在不改变核心智能体逻辑的情况下定制流程。
常见应用场景包括:
- 在调用模型之前处理状态(如消息截断、上下文注入)。
- 修改或验证模型响应(如防护栏、内容过滤)。
- 使用自定义逻辑处理工具执行错误。
- 基于状态或上下文实现动态模型选择。
- 添加自定义日志记录、监控或分析。
核心钩子介绍:节点样式与包装样式
自定义中间件可通过装饰器(Decorator-based,适用于简单单钩子)或类(Class-based,适用于多钩子和复杂逻辑)方式创建,均围绕两种核心钩子样式展开。
节点样式钩子在智能体执行流的特定点运行,包装样式钩子则用于拦截执行并控制何时调用处理程序。
下表对比了两种钩子的运行阶段与典型用途:
| 钩子名称 | 运行阶段 | 描述 |
|---|---|---|
@before_agent | 智能体开始前 | 每次调用开始前运行一次 |
@before_model | 每次模型调用前 | 在调用模型之前运行 |
@after_model | 每次模型响应后 | 在模型返回响应后运行 |
@after_agent | 智能体完成后 | 每次调用完成时运行一次 |
@wrap_model_call | 围绕每次模型调用 | 拦截模型调用,可用于修改请求或重试 |
@wrap_tool_call | 围绕每次工具调用 | 拦截工具调用,可用于错误处理或监控 |
包装样式钩子允许实现控制流逻辑,如重试或短路(short-circuit)。例如,动态模型选择可通过 @wrap_model_call 修改请求中的模型实例实现。
中间件的执行顺序:before_(先入先出)与 after_(后进先出)
当使用多个中间件时,理解它们的执行顺序至关重要。下表总结了不同钩子的执行规则:
| 钩子类型 | 执行顺序规则 |
|---|---|
before_* 钩子 | 按添加顺序运行(先入先出,FIFO) |
wrap_* 钩子 | 嵌套运行,第一个中间件包裹所有其他中间件 |
after_* 钩子 | 按相反顺序运行(后进先出,LIFO) |
下方流程图展示了多个中间件的典型执行顺序:
控制流程:利用 jump_to 实现提前退出或跳转
中间件不仅可以修改数据,还能控制智能体的执行路径。
- 提前退出:可返回包含
jump_to字段的字典,实现流程跳转。 - 跳转目标包括:
'end':跳转到智能体执行末尾,提前结束本次调用。'tools':跳转到工具节点。'model':跳转到模型节点(或第一个@before_model钩子)。
注意:从 @before_model 或 @after_model 跳转到 'model' 时,所有 @before_model 中间件将再次运行。
启用跳转需使用 @hook_config(can_jump_to=[...]) 装饰器进行配置。
典型场景如前置防护栏(@before_agent)检测到速率限制或不适当请求时,立即返回 jump_to: 'end',跳过所有模型调用和工具执行,节省成本并增强安全性。
自定义中间件实战:日志记录与状态修改
自定义中间件是实现特定业务逻辑和调试能力的强大方式。以下介绍三类典型应用:
日志记录和监控
日志记录是中间件最基本的应用之一。通过节点样式钩子,可记录执行流的开始和结束,以及关键步骤的输入输出。
- 在
@before_model钩子中访问模型请求,记录模型调用前信息。 - 在
@after_model钩子中记录模型响应,如 token 用量和延迟。
状态修改与上下文注入
中间件可读取和写入智能体的状态(State)或短时记忆。
- 瞬态修改:修改模型调用中发送的消息,但不改变状态中保存内容。例如,使用
@dynamic_prompt动态生成系统提示。 - 持久性修改:永久修改智能体状态或存储,如摘要中间件用摘要替换原始消息。
在 @before_model 钩子中访问状态,可实现如消息截断逻辑,防止超出大语言模型(LLM, Large Language Model)上下文窗口。
动态模型选择
通过包装样式钩子 @wrap_model_call 实现复杂逻辑。
- 机制:中间件拦截模型调用,根据当前状态(如任务复杂度)或运行时上下文(如用户权限)修改请求中的模型实例,实现智能体在不同成本和能力模型间动态切换,优化成本。
下方流程图展示了动态模型选择的执行过程:
总结
中间件是扩展智能体行为的利器,合理设计能提高可观测性与可维护性,同时避免对执行路径产生不可控影响。