【一文吃透】LangChain Middleware 中间件详解:15个预构建中间件+6个钩子点+自定义全流程实战
·
摘要: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,敬请关注。
更多推荐


所有评论(0)