摘要:LangChain 的 Middleware 机制是生产级 Agent 的核心能力,相当于 Spring 的拦截器+过滤器。本文用 15 个预构建中间件 + 6 个钩子点 + 自定义全流程实战,帮你从 0 到 1 掌握 Agent 循环的精细化控制。含完整代码 + 流程图 + 避坑指南,看完直接复用。

关键词:LangChain、Middleware、中间件、Agent、Hook、钩子、create_agent


一、问题背景:为什么需要中间件?

写 Agent 最常见的问题:

  • Agent 死循环烧 token,怎么限制调用次数?
  • 模型偶尔 429 限流,怎么自动重试?
  • 删数据库前怎么让人审批?
  • 长对话超上下文窗口怎么办?

答案就是 Middleware(中间件)——在 Agent 循环的每一步插入自定义逻辑。

中间件是什么

中间件是钩子(Hook)的集合,在 Agent 循环的每一步(模型调用前后、工具调用前后)插入逻辑,用于实现日志、限流、重试、人工审批、成本控制等功能。

类比:相当于 Spring 的拦截器(Interceptor)+ 过滤器(Filter),在请求链上层层拦截处理。


二、核心拆解:中间件在 Agent 循环中的分布

2.1 Agent 循环回顾

调模型 → 模型选工具 → 执行工具 → 回传结果 → 直到模型不再调工具

中间件就在这个循环的每一步前后插入钩子。

2.2 完整流程图

用户输入
  │
  ▼
before_agent(Agent 启动时,只执行一次)
  • 加载记忆、连接资源、校验初始输入
  │
  ▼
before_model(每次调模型前)
  • PIIMiddleware          → 敏感信息脱敏
  • ContextEditingMiddleware → 清理旧工具结果
  • SummarizationMiddleware → 超 token 自动摘要
  │
  ▼
wrap_model_call(包裹整个模型调用)
  • ModelRetryMiddleware     → 模型失败自动重试
  • ModelFallbackMiddleware  → 主模型挂切备用
  • ModelCallLimitMiddleware → 限制模型调用次数
  • LLMToolSelectorMiddleware → 小模型先筛工具
  │
  ▼
  模型决策:选工具 or 直接回答
  │
  ├──── 直接回答 ─────────────────────────────────┐
  │                                               │
  ▼                                               ▼
after_model(模型返回后,工具执行前)
  • HumanInTheLoopMiddleware → 危险工具需人工审批
  • PIIMiddleware            → 模型输出脱敏
  │
  ▼
wrap_tool_call(包裹整个工具调用)
  • ToolRetryMiddleware      → 工具失败自动重试
  • ToolCallLimitMiddleware  → 限制工具调用次数
  • LLMToolEmulatorMiddleware → 测试时模拟工具
  │
  ▼
  工具执行,返回结果给模型
  │
  ▼
  回到 before_model(继续循环,直到模型不再调工具)
  │
  ▼
after_agent(Agent 结束时,只执行一次)
  • 保存结果、发通知、清理资源
  │
  ▼
  最终输出给用户

2.3 按流程步骤总结

步骤 钩子位置 涉及中间件
1. 人类询问→发给模型 before_model + wrap_model_call PII 脱敏 → Summarization 摘要 → ModelRetry 重试 → LLMToolSelector 筛工具
2. 模型调用工具 after_model + wrap_tool_call HumanInTheLoop 审批 → ToolRetry 重试 → ToolCallLimit 限次
3. 工具回复模型 before_model(回到循环) ContextEditing 清理 → Summarization 摘要
4. 输出答案 after_agent 自定义收尾逻辑

三、15 个预构建中间件(按工业使用频率排序)

3.1 高频(生产标配)

序号 中间件 作用 代码示例
1 SummarizationMiddleware 超 token 阈值自动摘要旧消息 SummarizationMiddleware(model=llm, trigger=("tokens", 4000))
2 ModelCallLimitMiddleware 限制模型调用次数 ModelCallLimitMiddleware(run_limit=5)
3 ToolCallLimitMiddleware 限制工具调用次数 ToolCallLimitMiddleware(run_limit=3)
4 ModelRetryMiddleware 模型调用失败自动重试 ModelRetryMiddleware(max_retries=3)
5 ModelFallbackMiddleware 主模型挂了自动切备用 ModelFallbackMiddleware(fallback_models=[llm_backup])

3.2 中频(按需启用)

序号 中间件 作用 典型场景
6 HumanInTheLoopMiddleware 危险工具需人工审批 金融/写库/发邮件
7 ToolRetryMiddleware 工具调用失败自动重试 第三方接口不稳定
8 ContextEditingMiddleware 清理旧工具结果保最新 长对话控上下文
9 LLMToolSelectorMiddleware 小模型先筛相关工具 工具多时省 token
10 PIIMiddleware 敏感信息检测与脱敏 合规/日志清洗

3.3 低频(特殊场景)

序号 中间件 作用
11 TodoListMiddleware 自动维护任务清单
12 FilesystemMiddleware 文件系统中间件
13 SubagentMiddleware 子智能体中间件
14 LLMToolEmulatorMiddleware 用 LLM 模拟工具执行(测试用)
15 ProviderToolSearchMiddleware 厂商端工具搜索

3.4 重点中间件详解

SummarizationMiddleware(长对话必备)
from langchain.agents.middleware import SummarizationMiddleware

SummarizationMiddleware(
    model=llm,                        # 用哪个模型做摘要
    trigger=("tokens", 4000),         # 触发条件:累计超过 4000 tokens
    keep=("messages", 20),            # 摘要后保留最近 20 条消息
)

触发流程

每轮对话后检查总 tokens
  ↓
超过 4000?
  ├─ 否 → 不做任何事
  └─ 是 → 触发摘要
         旧消息 → 模型生成摘要
         保留最近 20 条
         继续
HumanInTheLoopMiddleware(危险操作审批)
from langchain.agents.middleware import HumanInTheLoopMiddleware

HumanInTheLoopMiddleware(
    interrupt_on={
        "send_email": {
            "allowed_decisions": ["approve", "edit", "reject"],
        },
        "read_email": False,  # 读邮件不需要审批
    }
)
决策 含义 效果
"approve" 同意 直接执行工具
"edit" 编辑 人类修改参数后执行
"reject" 拒绝 不执行,告诉模型被拒绝
ModelFallbackMiddleware(自动降级)
ModelFallbackMiddleware(
    fallback_models=[llm_backup1, llm_backup2],
)
主模型 qwen-plus 调用失败(429/500)
       ↓
自动切备用模型1 → 还是失败
       ↓
自动切备用模型2 → 成功
       ↓
返回结果,用户无感

四、自定义中间件全流程实战

4.1 方式一:装饰器式(单个钩子)

from langchain.agents.middleware import before_model, after_model

@before_model
def log_before(state, runtime):
    """模型调用前:打印消息数"""
    print(f"即将调用模型,消息数: {len(state['messages'])}")
    return None  # 返回 None 不修改 state

@after_model
def log_after(state, runtime):
    """模型调用后:打印返回内容"""
    last_msg = state["messages"][-1]
    print(f"模型返回: {last_msg.content[:50]}")
    return None

4.2 方式二:类式(多个钩子封装在一起)

from langchain.agents.middleware import AgentMiddleware

class MyLoggingMiddleware(AgentMiddleware):
    """自定义日志中间件:同时定义 before_model + after_model"""

    def before_model(self, state, runtime):
        print(f"[before] 消息数: {len(state['messages'])}")
        return None

    def after_model(self, state, runtime):
        last_msg = state["messages"][-1]
        print(f"[after] 返回: {last_msg.content[:50]}")
        return None

4.3 使用自定义中间件

agent = create_agent(
    model=llm,
    tools=[get_weather],
    middleware=[log_before, MyLoggingMiddleware()],  # 装饰器式 + 类式混用
)

4.4 装饰器式 vs 类式对比

装饰器式 类式
适合 单个钩子 多个钩子共享状态
代码量
可复用性 高(可带配置)
类比 Java @Before 注解 HandlerInterceptor 接口实现

4.5 钩子返回值规则

返回值 效果
None 不修改 state
dict 合并进 state(messages 走 add_messages reducer)

五、6 个钩子点总结

钩子 时机 执行次数 典型用途
before_agent Agent 启动 1 次 加载记忆、连接资源
before_model 每次调模型前 每轮 脱敏、摘要、清理上下文
wrap_model_call 包裹模型调用 每轮 重试、降级、限流、筛工具
after_model 模型返回后、工具执行前 每轮 人工审批、输出脱敏
wrap_tool_call 包裹工具调用 每次工具调用 工具重试、限次、模拟
after_agent Agent 结束 1 次 保存结果、发通知

六、避坑指南

坑1:after_model 签名没有 response 参数

# ❌ 错误:after_model 没有 response 参数
@after_model
def my_hook(state, runtime, response):
    print(response.content)  # 报错!

# ✅ 正确:模型返回的内容在 state["messages"][-1] 里
@after_model
def my_hook(state, runtime):
    last_msg = state["messages"][-1]
    print(last_msg.content)

坑2:中间件顺序影响执行结果

# 顺序敏感:先摘要再脱敏 vs 先脱敏再摘要,结果可能不同
middleware=[
    PIIMiddleware(...),                # 先脱敏
    SummarizationMiddleware(...),      # 再摘要(基于脱敏后的内容)
]

坑3:HumanInTheLoopMiddleware 需要 checkpointer

# 没有 checkpointer,HumanInTheLoop 无法暂停恢复
agent = create_agent(
    model=llm,
    tools=[send_email],
    middleware=[HumanInTheLoopMiddleware(interrupt_on={"send_email": ...})],
    checkpointer=InMemorySaver(),  # ← 必须有!
)

七、总结

概念 一句话理解
中间件 钩子集合,在 Agent 循环每一步插入逻辑
钩子 特定位置的插入点(before_model/after_model 等)
预构建中间件 15 个开箱即用,按频率分三档
自定义中间件 装饰器式(单钩子)或类式(多钩子)
钩子返回值 None 不修改;dict 合并进 state

核心认知:中间件是 Agent 的"免疫系统+神经系统"——控制成本(限流)、保障稳定(重试/降级)、确保安全(PII/审批)、管理上下文(摘要/清理)。6 个钩子点覆盖了 Agent 循环的每一个环节,15 个预构建中间件 + 自定义能力 = 生产级 Agent 的精细化控制。


互动讨论

你在实际项目中用过哪些中间件?遇到过什么坑?欢迎评论区交流!

下一篇我们将深入 Short-term Memory(短期记忆),包括 Checkpointer 机制、trim 裁剪策略、自定义 State,敬请关注。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐