跳转到内容

工具调用中间件链:在 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_agentAgent 执行开始前状态更新字典初始化状态、发送通知事件
before_model每次模型调用前状态更新字典动态修改消息、注入系统提示
wrap_model_call包裹模型调用ModelResponse重试、模型回退、缓存、响应重写
after_model模型调用完成后状态更新字典日志、结果验证、流事件
wrap_tool_call包裹工具执行ToolMessage工具重试、缓存、权限验证
after_agentAgent 执行完成后状态更新字典清理、统计、最终通知

装饰器快捷模式:LangChain 提供 @before_model@after_model@dynamic_prompt@wrap_model_call@wrap_tool_call 等装饰器,将普通函数自动转换为中间件,无需显式继承 AgentMiddleware

条件跳转机制before_modelafter_model 钩子可通过 @hook_config(can_jump_to=["end", "model"]) 声明允许跳转的目标节点,结合状态中的 jump_to 字段实现运行时控制流修改。

中间件组合:多个中间件按注册顺序串联,第一个注册的成为最外层。每个中间件通过 handler 回调调用内层,形成责任链模式。


补充:Agno 的工具中间件与 Callable Factory 模式

来源:Agno libs/agno/agno/agent/_tools.pylibs/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 开始时调用工厂函数,注入 agentrun_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 中的条件
  • 工具之间的互斥关系