工具调用中间件链:在 Agent 循环中插入横切关注点
工具调用中间件链:在 Agent 循环中插入横切关注点
1. 概念定义
中间件链(Middleware Chain)是一种在 Agent 工具调用生命周期的特定阶段插入处理逻辑的设计模式。它借鉴了 Web 框架中的中间件模式,将横切关注点(如错误处理、上下文管理、安全审计)从核心 Agent 逻辑中解耦。
在基于 LangGraph 的 Agent 中,中间件链通常按严格顺序组装,每个中间件在工具执行前或后运行,形成一个 15-20 层的处理管道。
2. 中间件链的典型阶段
一个完整的 Super Agent 中间件链通常包含以下阶段(按 append 顺序):
3. 中间件分类
3.1 上下文管理中间件
- ThreadDataMiddleware:为每个线程创建隔离的工作目录
- SummarizationMiddleware:当接近 Token 限制时压缩对话历史
- TitleMiddleware:首次完整对话后自动生成会话标题
3.2 错误处理中间件
- LLMErrorHandlingMiddleware:将模型调用失败转为可恢复的错误消息
- ToolErrorHandlingMiddleware:将工具异常转为结构化 ToolMessage
- DanglingToolCallMiddleware:为用户中断等场景下的悬空工具调用注入占位符
3.3 安全中间件
- GuardrailMiddleware:工具执行前的授权检查
- SandboxAuditMiddleware:沙箱操作的安全审计日志
3.4 优化中间件
- TokenUsageMiddleware:Token 使用统计和计量
- LoopDetectionMiddleware:检测工具调用循环并强制降级
4. 设计优势
- 单一职责:每个中间件只处理一个横切关注点,逻辑清晰
- 可组合性:中间件可按需启用/禁用,不同场景组合不同
- 可测试性:每个中间件独立测试,降低集成复杂度
- 热插拔:新增中间件只需 append 到链中,无需修改已有代码
5. 陷阱与规避
5.1 中间件顺序依赖
中间件之间存在隐式依赖。例如,ClarificationMiddleware 必须在链的最后(因为它通过 Command(goto=END) 中断执行),SubagentLimitMiddleware 必须在模型响应之后运行。
规避:在代码中明确注释中间件的组装顺序和依赖关系。
5.2 中间件性能开销
每个中间件在每次工具调用时都运行。20 层中间件可能引入显著的延迟。
规避:中间件应尽早返回(fast-path),不需要工作的中间件直接 pass。
5.3 状态传播
中间件之间通过共享状态(Agent State)通信。不当的 state 修改可能导致后续中间件读到脏数据。
规避:中间件只追加自己的 state 变更,避免覆盖其他中间件写入的数据。
6. 替代方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 中间件链 | 模块化、可测试、可扩展 | 顺序依赖复杂、性能开销 |
| 装饰器模式 | 实现简单 | 多层装饰器嵌套难以调试 |
| 内联处理 | 无额外开销 | 核心逻辑膨胀、难以维护 |
参考来源
补充:LangChain v1 的五层中间件钩子体系
LangChain v1 定义了一套完整的 AgentMiddleware 基类,提供五个生命周期钩子,覆盖 Agent 执行的完整流程:
| 钩子 | 时机 | 返回值 | 典型用途 |
|---|---|---|---|
| before_agent | Agent 执行开始前 | 状态更新字典 | 初始化状态、发送通知事件 |
| before_model | 每次模型调用前 | 状态更新字典 | 动态修改消息、注入系统提示 |
| wrap_model_call | 包裹模型调用 | ModelResponse | 重试、模型回退、缓存、响应重写 |
| after_model | 模型调用完成后 | 状态更新字典 | 日志、结果验证、流事件 |
| wrap_tool_call | 包裹工具执行 | ToolMessage | 工具重试、缓存、权限验证 |
| after_agent | Agent 执行完成后 | 状态更新字典 | 清理、统计、最终通知 |
装饰器快捷模式:LangChain 提供 @before_model、@after_model、@dynamic_prompt、@wrap_model_call、@wrap_tool_call 等装饰器,将普通函数自动转换为中间件,无需显式继承 AgentMiddleware。
条件跳转机制:before_model 和 after_model 钩子可通过 @hook_config(can_jump_to=["end", "model"]) 声明允许跳转的目标节点,结合状态中的 jump_to 字段实现运行时控制流修改。
中间件组合:多个中间件按注册顺序串联,第一个注册的成为最外层。每个中间件通过 handler 回调调用内层,形成责任链模式。
补充:Agno 的工具中间件与 Callable Factory 模式
来源:Agno
libs/agno/agno/agent/_tools.py、libs/agno/agno/tools/function.py,2026-05-30
Toolkit + Function 两层封装
Agno 的工具系统采用两层封装:
# 伪代码class Function: name: str description: str parameters: dict # JSON Schema callable: Callable # 实际执行函数
class Toolkit: """一组 Function 的容器,提供批量注册和命名空间""" name: str functions: List[Function]@tool decorator 将普通 Python 函数转为 Function 实例,自动从函数签名提取 JSON Schema 参数定义。
Callable Factory 模式
tools 字段不仅接受工具列表,还支持 Callable[..., List] 工厂函数:
# 伪代码:运行时动态生成工具列表agent = Agent( tools=lambda agent, run_context: [ get_user_tool(run_context.user_id), get_db_tool(run_context.session_state["db_name"]), ],)每次 run 开始时调用工厂函数,注入 agent 和 run_context 参数。通过 cache_callables=True 缓存结果,避免每次 run 都重新创建。
工具中间件 Hook
tool_hooks 是环绕每个工具调用的函数列表:
# 伪代码:工具中间件def log_tool_hook(function_name: str, args: dict, result: Any, **kwargs) -> Any: log.info(f"Tool {function_name} called with {args}, result: {result}") return result # 可以修改 result与 LangChain 的责任链中间件不同,Agno 的工具 Hook 是简单的函数列表,每个 Hook 接收工具名称、参数和结果,可以修改返回值。这种设计更轻量,适合不需要复杂状态流转的场景。
工具确定(Dynamic Tool Filtering)
Agno 在步骤 5(determine_tools_for_model)执行动态工具过滤——不是所有声明的工具都发送给模型。过滤逻辑考虑:
- 当前 session 状态
- run context 中的条件
- 工具之间的互斥关系