目录
[一、认识 Agent Middleware](#一、认识 Agent Middleware)
[1. 什么是中间件](#1. 什么是中间件)
[1. Agent 生命周期与 Hook 介入点](#1. Agent 生命周期与 Hook 介入点)
[2. 多个 Middleware 的执行](#2. 多个 Middleware 的执行)
[3. 中间件的分类](#3. 中间件的分类)
[1. 工作原理](#1. 工作原理)
[2. 参数说明](#2. 参数说明)
[3. 配置自动摘要中间件](#3. 配置自动摘要中间件)
[1. 参数说明](#1. 参数说明)
[2. 配置 HITL 中间件与捕获中断请求](#2. 配置 HITL 中间件与捕获中断请求)
[五、PIIMiddleware 与 TodoListMiddleware](#五、PIIMiddleware 与 TodoListMiddleware)
[1. PIIMiddleware](#1. PIIMiddleware)
[2. 代码案例](#2. 代码案例)
[3. TodoListMiddleware](#3. TodoListMiddleware)
[六、其他常用内置 Middleware](#六、其他常用内置 Middleware)
[1. 核心内置 Middleware 盘点](#1. 核心内置 Middleware 盘点)
[2. 内置中间件选型](#2. 内置中间件选型)
一、认识 Agent Middleware
在前两篇文章里,我们使用 system_prompt 约束智能体行为,response_format 限定输出,同时通过 stream_mode 掌握流式交互效果
然而在应用开发中,很多需求超出了这些基础配置参数的能力范畴:
-
模型调用前:如何根据当前用户权限动态修改 Prompt 或注入额外凭证?
-
Tool 调用前/后:如何对敏感工具的参数进行校验,或将工具执行结果自动记录到日志审计系统?
-
长对话上下文:当对话轮数过多触发 Token 窗口溢出时,如何实现自动摘要与历史裁剪?
-
数据安全:如何拦截用户输入的身份证号、手机号,并在送入大模型前自动脱敏?
这些无法通过简单 Prompt 搞定的横向控制逻辑,正需要 Agent Middleware(中间件) 发挥作用
1. 什么是中间件
Middleware(中间件)是一种生命周期拦截机制。 它允许开发者在 Agent 运行的关键节点(如 Model 调用前后、Tool 执行前后、State 改变时)植入自定义的逻辑代码,从而对数据包进行审查、改写或中断控制

Agent 自身负责做业务,而 Middleware 负责把关
二、中间件的工作机制
理解中间件的机制,核心在于厘清两个概念:Middleware(中间件) 与 Hook(钩子)
-
Middleware 是整体机制与逻辑载体:它是一个具体的类或功能模块,里面编写了我们希望执行的拦截逻辑
-
Hook 是 Middleware 介入生命周期的具体点位:它是 Agent 运行流程中预留的 "插座",决定了中间件的代码到底在哪个瞬间被触发
1. Agent 生命周期与 Hook 介入点
在 Agent 的 ReAct 循环中,中间件通过挂载在不同的 Hook 点位,实现对全流程的动态监管:

2. 多个 Middleware 的执行
当我们在 Agent 中同时配置了多个 Middleware 时,它们的执行顺序遵循经典的洋葱圈模型:
请求进入 ──> [ Middleware A (前置) ] ──> [ Middleware B (前置) ] ──> [ 核心执行 (Model/Tool) ]
│
输出返回 <── [ Middleware A (后置) ] <── [ Middleware B (后置) ] <───┘
按顺序注册的中间件,在前置 Hook 阶段按照 A -> B 顺序正向执行;而在后置 Hook 阶段则按照 B -> A 顺序逆向剥离
3. 中间件的分类
可以按照中间件在实际业务中解决的工程问题进行分类:
Agent Middleware
│
├── 1. 上下文管理 (Context Management)
│ └── 核心解决:Token 溢出与历史冗余(如 SummarizationMiddleware)
│
├── 2. 人工干预与控制 (Human-in-the-loop)
│ └── 核心解决:高危操作防护与人工审批(如 HumanInTheLoopMiddleware)
│
├── 3. 安全与合规治理 (Security & Privacy)
│ └── 核心解决:敏感信息泄露与 PII 拦截(如 PIIMiddleware)
│
├── 4. 能力与任务扩展 (Capability Extension)
│ └── 核心解决:长链条任务拆解与显式规划(如 TodoListMiddleware)
│
└── 5. 稳定性与容错 (Resilience)
└── 核心解决:网络波动与 API 重试(如 Retry 相关中间件)
三、SummarizationMiddleware
在多轮对话或复杂长链条任务中,Agent 极易遇到上下文窗口溢出。随着对话轮数的累积,系统性能与经济成本会迅速恶化
HumanMessage ──► AIMessage ──► HumanMessage ──► AIMessage ...
│
▼
上下文膨胀 ──► Token 消耗指数级上升 ──► 触发模型 Token 上限 ──► 响应变慢且成本暴涨
SummarizationMiddleware (自动摘要中间件) 正是为此而生
1. 工作原理
SummarizationMiddleware 会在 Agent 执行周期的 Before Model 阶段监视当前状态流。一旦发现历史消息触及预设阈值,它就会在后台自动介入:

这样既通过摘要保护了 Agent 对早期事实的记忆,又解放了上下文窗口压力
2. 参数说明
1. model --- 用于摘要的模型
指定负责生成摘要的模型。支持直接传入模型对象或字符串模型名称(如 "gpt-4o-mini")。如果传递的是模型名称,底层会自动调用 init_chat_model 完成初始化。在实际生产中,通常建议传入一个更便宜、速度更快的小模型来降低摘要成本
2. trigger --- 摘要触发条件
传入一个包含条件元组的列表,当列表中任意一个条件满足时,即刻触发摘要逻辑:
-
**("tokens", N):**历史消息累计的 Token 数达到 N 时触发
-
**("messages", N):**历史消息的总条数达到 N 时触发
-
**("fraction", ratio):**历史 Token 占模型最大上下文窗口的比例达到 max_input_tokens * ratio 时触发
如果使用 fraction 条件,要求模型的配置 profile 中必须包含 max_input_tokens。若某些模型的 profile 为空(如部分 DeepSeek 模型),需手动配置补充上下文长度(例如 DeepSeek 模型的上下文长度通常为 128K)
3. keep --- 摘要时保留的原始消息
决定完成摘要后,保留多少最新的原始上下文不被压缩**。与 trigger 不同,keep 同一时间只接收一种条件设定:**
-
**("tokens", N):**摘要时保留最新的 N 个 Token
-
**("messages", N):**摘要时保留最新的 N 条历史消息
-
**("fraction", ratio):**摘要时保留 max_input_tokens * ratio 个 Token
4. token_counter --- 统计 Token 数量的函数
统计上下文 Token 的自定义函数,默认使用 LangChain 提供的 count_tokens_approximately,一般无需更改
原理:该函数通过先统计文本字符数,除以单 Token 的估算字符数转换为粗略 Token数,再叠加额外开销进行近似计算
5. summary_prompt --- 自定义摘要提示词
用于定制生成摘要时的 Prompt 模板。该提示词中必须包含 {messages} 占位符,框架会将待压缩的历史消息列表自动注入其中。若不指定,则使用内置的标准摘要提示词
6. trim_token_to_summarize --- 摘要时历史消息的最大 Token 数限制
单次送入摘要模型的历史消息 Token 上限,默认值为 "4000"。如果待摘要的历史消息总 Token 数超过该值,超出的部分会被提前裁剪。
如果 trigger 中设置了较大的 Token 触发阈值,必须同步调大该参数,否则长历史消息在生成摘要前就会丢失信息
3. 配置自动摘要中间件
下面是一个完整的实战示例。我们构建一个包含 6 条历史消息的上下文,并为 Agent 挂载中间件,观察它是如何自动触发摘要并保留最新消息的:
python
# 1. 准备模型对象
model = ChatOpenAI(
api_key=DEEPSEEK_API_KEY,
base_url=DEEPSEEK_BASE_URL,
model="deepseek-v4-flash",
extra_body={
"thinking": {"type": "disabled"}
},
profile={"max_input_tokens": 4000}
)
# 2. 模拟一组达到触发条件的多轮对话上下文(共 6 条消息)
messages = [
SystemMessage("你是个非常友好的AI助手"),
HumanMessage("你好啊,我是老王,你是谁?"),
AIMessage("你好老王,我是小王"),
HumanMessage("好的小王,很高兴认识你"),
AIMessage("你高兴得太早了"),
HumanMessage("呵呵,你什么意思")
]
# 3. 创建挂载了 SummarizationMiddleware 的 Agent
agent = create_agent(
model=model,
middleware=[
SummarizationMiddleware(
model=model,
# 满足任意一个条件即触发摘要:Token>100 或 消息数>=6 或 占比>0.001
trigger=[
("tokens", 100),
("messages", 6),
("fraction", 0.001)
],
# 摘要完成后,仅保留最新 2 条消息,其余压缩为 Summary
keep=("messages", 2)
)
]
)
# 4. 执行 Agent 观察最终消息列表结构
response = agent.invoke({"messages": messages})
# 打印压缩与响应后的上下文消息
for msg in response["messages"]:
msg.pretty_print()
输出示例:

通过这套机制,旧的对话记录被高效提炼为系统摘要,最新消息被保留,Agent 既记住了 "用户是老王",又避免了随着对话深入而导致的上下文爆仓问题
四、HumanInTheLoopMiddleware
在真实工程落地中,自主 Agent ≠ 无监管放任。对于查询天气、检索新闻等只读操作,尽可以放手让 Agent 自主决策;但对于具有真实副作用的操作(如发送邮件、划转资金、修改数据库),一旦 Agent 发生幻觉或误判,造成的损失往往不可逆
HumanInTheLoopMiddleware (人在环中间件) 正是解决这一痛点的防线。它可以在 Tool 执行前强行中断 Agent 进程,抛出等待审批的中断请求,由人工确认后再继续执行
1. 参数说明
配置 HumanInTheLoopMiddleware 时,主要通过 interrupt_on 和 description_prefix 精细化控制每个 Tool 的中断策略与提示信息:
1. interrupt_on --- 工具名和中断策略的映射
传入一个字典,实现工具上的精细控制。策略值支持 True、False 或 InterruptOnConfig 字典对象:
python
interrupt_on = {
"get_weather": True,
"read_email_tool": False,
"send_email_tool": {
"allowed_decisions": ["approve", "reject"],
"description": "发送邮件中断啦"
}
}
-
True:表示开启中断,且允许所有决策动作(approve 批准、edit 编辑、reject 拒绝)。等价于:
python"get_weather": { "allowed_decisions": ["approve", "edit", "reject"] } -
False :表示不中断,即该工具无需审批,直接放行
-
InterruptOnConfig:是一个 TypedDict 子类,可以通过字典形式赋值,支持的 Key 包括:
-
allowed_decisions:控制中断后允许的决策列表(如只允许 "approve", "reject")
-
description:特定工具专属的中断描述信息。优先级高于 description_prefix,配置后会覆盖该工具的全局前缀描述
-
2. description_prefix --- 自定义中断描述
用于设定全局的中断描述前缀,默认值为 "Tool execution requires approval"。如果某个 Tool 内部没有独立定义 description,则会统一复用该前缀描述
2. 配置 HITL 中间件与捕获中断请求
下面演示四个不同 Tool 的拦截配置,并展示如何在 Agent 触发中断后提取 interrupt 和 action_requests 信息:
python
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain_core.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
@tool
def get_weather(city: str) -> str:
"""查询指定城市天气"""
return f"{city}今天天气不错"
@tool
def send_email(recipient: str, body: str) -> str:
"""发送邮件"""
print(">>> 真正执行发送邮件")
return f"邮件已发送给 {recipient}"
agent = create_agent(
model=model,
tools=[get_weather, send_email],
checkpointer=InMemorySaver(),
middleware=[
HumanInTheLoopMiddleware(
interrupt_on={
"get_weather": False, # 直接执行
"send_email": {
"allowed_decisions": ["approve", "reject"]
},
}
)
],
)
config = {
"configurable": {
"thread_id": "demo-1"
}
}
response = agent.invoke(
{
"messages": [
{
"role": "user",
"content": (
"查询北京天气,并给 test@example.com "
"发送一封内容为"今天天气不错"的邮件"
),
}
]
},
config=config,
)
print(response.get("__interrupt__", []))
输出示例:

创建 Agent 时通过 checkpointer 参数启用了短期记忆,在调用时通过传递相同的 config 加载记忆。记住固定用法即可
五、PIIMiddleware 与 TodoListMiddleware
除了上下文窗口管理与人工风险控制外,数据合规治理 与复杂任务规划也是决定系统能否安全、稳定落地的能力。LangChain 提供了 PIIMiddleware 与 TodoListMiddleware,分别应对这两类需求
1. PIIMiddleware
提示词无法从根本上保障数据合规。一旦用户在对话中传入手机号、身份证或信用卡,大模型依然可能将其记录、回显甚至通过日志泄漏。PIIMiddleware 拦截在模型调用的前置与后置阶段,确保敏感数据在进入模型前被自动脱敏,防止隐私泄露
核心处理策略(strategy)
PIIMiddleware 支持四种数据处理策略:
-
redact(默认):直接将敏感信息替换为占位符(如 REDACTED_EMAIL)。适用于日志合规与通用审计
-
mask:对敏感数据进行掩码局部隐藏(如 ****-****-****-1234)。适用于需要对人类保留部分可读性的场景
-
block:一旦检测到敏感字段,立即抛出 PIIDetectionError 异常。适用于高敏感度的场景
-
hash:将敏感信息替换为确定性的哈希指纹。在保持匿名化的同时保留关联标识,适用于数据分析与调试
主要配置参数
-
pii_type:要检测的敏感信息类型。支持内置类型("email", "credit_card", "ip", "mac_address", "url")或自定义类型名称
-
strategy:选择上述处理策略之一
-
detector:自定义检测逻辑。可传入正则表达式字符串(如匹配 API Key)或自定义检测函数
-
apply_to_input:是否在调用模型前检测(默认为 True)
-
apply_to_output:是否在模型调用后检测(默认为False)
-
apply_to_tool_results:是否对 Tool 的执行返回结果实施 PII 脱敏(默认为 False)
2. 代码案例
python
from rich import print as rprint
# 构建针对不同敏感信息的安全防线
agent = create_agent(
model=model,
middleware=[
# 邮箱地址自动替换为 [REDACTED_EMAIL]
PIIMiddleware(pii_type="email", strategy="redact"),
# 信用卡号自动隐藏前段,只留尾数
PIIMiddleware(pii_type="credit_card", strategy="mask"),
# 通过正则检测 API Key,一旦发现直接阻断请求
PIIMiddleware(
pii_type="api_key",
detector=r"sk-[a-zA-Z0-9]{32}",
strategy="block"
)
]
)
response = agent.invoke({
"messages": [HumanMessage("""
帮我向 156168188@qq.com 发送一封邮件
同时查看银行卡号: 5105-1051-0510-5100 的余额
""")]
})
rprint(response["messages"][0])
输出示例:

3. TodoListMiddleware
面对多步骤任务时,普通 Agent 极易发生漏步、无序执行或执行中断后无法恢复的问题
TodoListMiddleware 显式赋予了 Agent 任务规划与进度跟踪能力。它通过自动拦截 Agent 的思考链路,引导其将大目标拆解为清晰的子任务列表,并按 pending(待办)、in_progress(进行中)、completed(已完成)三种状态动态更新执行进度。它不仅是输出控制,更是帮助复杂 Agent 显式维护状态与记忆的基础设施
工作原理与使用约定
-
自动工具注入:只需挂载 TodoListMiddleware()(无需额外配置复杂参数),中间件会自动向 Agent 注入 write_todos 管理工具
-
依赖 Checkpointer:由于任务清单状态需要在多轮推导和工具调用间持续同步,必须为 Agent 配置 checkpointer(如 InMemorySaver())
-
驱动推导循环:Agent 在接收复杂任务时会自动判定并调用 write_todos 初始化 Task 清单,每完成一步自动更新状态,避免步骤遗漏
参数说明:
-
**system_prompt:**自定义指导 todo 列表使用的提示词,不提供则使用内置提示词,通常不必提供
-
**tool_description:**自定义 write_tools 工具描述,不提供则使用内置描述,通常不必提供
代码案例:
python
from rich import print as rprint
@tool
def search_web(query: str) -> str:
"""搜索资料"""
return f"已搜索:{query}"
@tool
def write_summary(topic: str) -> str:
"""生成主题总结"""
return f"已生成 {topic} 的总结"
agent = create_agent(
model=model,
tools=[search_web, write_summary],
middleware=[
TodoListMiddleware()
],
)
result = agent.invoke(
{
"messages": [{
"role": "user",
"content": (
"请使用 Todo 列表规划并完成以下任务:"
"1. 搜索 LangChain;"
"2. 搜索 LangGraph;"
"3. 总结 LangChain 用途;"
"4. 总结 LangGraph 用途;"
"5. 比较两者差异;"
"6. 给出最终结论。"
)
}]
}
)
rprint(result["todos"])
输出示例:

TodoListMiddleware 并不会强制 Agent 每次都生成 Todo,而是向 Agent 注入 write_todos 工具和对应提示词。只有当模型判断任务足够复杂时才会调用该工具;是否实际生效,可以通过消息中的 write_todos 工具调用或最终的 result"todos" 字段观察
六、其他常用内置 Middleware
LangChain 预置了丰富的内置中间件,将弹性容错、成本控制、上下文管理等非功能性需求封装为即插即用的模块
1. 核心内置 Middleware 盘点
-
模型与工具容错(Fault Tolerance)
-
**ModelRetryMiddleware / ToolRetryMiddleware:**自动拦截模型网络波动或 Tool 执行报错,支持指数退避、随机抖动及重试上限控制。可在耗尽重试后将错误文本返还给模型自行修正
-
ModelFallbackMiddleware:当主模型触发限流或服务崩溃时,自动降级切换至备用模型
-
ToolErrorMiddleware:捕获工具未处理的 Exception 并转化为标准的 ToolMessage 错误日志,避免系统直接溃败中断
-
-
成本控制与上下文管理
-
SummarizationMiddleware:监控历史对话 Token 消耗,当触发阈值时,自动将早期上下文压缩为摘要,保障长对话不超限
-
ModelCallLimitMiddleware / ToolCallLimitMiddleware:支持在运行级或线程级限制模型及工具的最高调用次数,防范 Agent 陷入推导死循环导致账单失控
-
ContextEditingMiddleware:动态裁剪或清空历史交互日志中的冗余 Tool 返回值
-
-
能力扩展与高效路由
-
**LLMToolSelector / ProviderToolSearch:**在大规模工具集场景下,预先通过轻量模型或服务端检索过滤候选 Tool,提升路由准确度并节省 Prompt Token
-
**FilesystemMiddleware / ShellToolMiddleware / SubagentMiddleware:**为 Agent 注入文件持久化存储、Shell 命令行交互以及动态派生子 Agent 协助执行的能力
-
2. 内置中间件选型
| 中间件名称 | 分类 | 解决的痛点 | 应用场景 |
|---|---|---|---|
| HumanInTheLoopMiddleware | 业务风控 | 高危操作产生不可逆副作用 | 资金划转、发邮件、写 / 删数据库 |
| PIIMiddleware | 安全合规 | 用户敏感隐私泄露至外部 LLM | 手机号、身份证、API Key 脱敏 |
| TodoListMiddleware | 规划决策 | 复杂长流程任务漏步、无序 | 多步骤 ETL、自动化工作流 |
| ModelRetryMiddleware / ToolRetryMiddleware | 弹性容错 | 网络抖动或接口偶发性报错 | 生产环境高可用保障 |
| ModelFallbackMiddleware | 弹性容错 | 主模型 API 服务不可用 | 异构大模型服务降级 |
| SummarizationMiddleware | 上下文管理 | 上下文超出 Token 限制 / 成本高昂 | 多轮客服、长文本助手 |
| ModelCallLimitMiddleware / ToolCallLimitMiddleware | 成本治理 | Agent 陷入死循环耗尽预算 | 复杂自主推理任务兜底 |
中间件机制解耦了 Agent 的业务推导逻辑与非功能性控制逻辑。开发人员无需改动 LLM 提示词或工具内部代码,只需组合不同的 Middleware,即可快速构建出具备安全防护、自我修复与高效规划能力的高可用 Agent 应用
总结
本章正式进入 LangChain 的 Agent Middleware 中间件机制,了解了中间件在 Agent 运行生命周期中的作用,以及 Middleware 与 Hook 钩子之间的关系。通过中间件,我们可以在不修改 Agent 核心逻辑的情况下,为其增加上下文管理、人工审批、隐私保护和任务规划等能力
随后,我们重点学习了 SummarizationMiddleware、HumanInTheLoopMiddleware、PIIMiddleware 和 TodoListMiddleware 等常用内置中间件,理解了它们各自解决的问题、基本配置方式和典型应用场景,并对 LangChain 提供的其他 Middleware 能力有了整体认识
下一篇将进一步学习 自定义 Middleware,深入 Agent 的执行生命周期与各种 Hook,掌握如何根据实际业务需求编写自己的中间件,从 "使用现成能力" 进一步走向 "扩展 Agent 运行机制"
