目录
一、30 秒看懂 Middleware
Middleware = 挂在 Agent 运行链路里的「自动插件」
它在 LLM 思考之前、工具执行前后自动介入,
不需要 LLM 主动「决定调用」。
类比:
| 角色 |
对应概念 |
谁触发 |
| 招式 |
tools=[] 里的普通工具 |
LLM 决定何时调用 |
| 神经系统 |
Middleware 中间件 |
框架每次自动执行 |
二、核心概念
2.1 Middleware 在链路中的位置
用户消息进入
│
▼
before_agent ← 启动前修状态(如 Patch 悬空 tool_call)
│
▼
wrap_model_call ← 调 LLM 前:改 prompt、改工具列表、压缩消息
│
▼
LLM 推理 → 产出 tool_call
│
▼
wrap_tool_call ← 每个工具调用前后:日志、鉴权、重试
│
▼
工具执行 → 结果写回 state
│
▼
下一轮循环...
2.2 Middleware 能做什么(能力表)
| 能力 |
典型中间件 |
触发方式 |
| 注入 system prompt |
MemoryMiddleware、SkillsMiddleware |
每次 wrap_model_call |
| 动态增删工具 |
FilesystemMiddleware |
每次 wrap_model_call |
| 自动压缩上下文 |
SummarizationMiddleware |
token 超阈值自动触发 |
| 文件权限拦截 |
FilesystemMiddleware + permissions |
工具执行时 |
| 人机审批暂停 |
HumanInTheLoopMiddleware |
指定工具调用前 |
| 委派子智能体 |
SubAgentMiddleware |
注入 task 工具 |
| 后台异步任务 |
AsyncSubAgentMiddleware |
注入 5 个 async 工具 |
| 修复消息历史 |
PatchToolCallsMiddleware |
before_agent |
| 跨轮次状态 |
TodoListMiddleware 等 |
写入 graph state |
2.3 两类 Middleware 来源
| 来源 |
你怎么用 |
是否默认存在 |
| DeepAgents 内置栈 |
传 skills=、memory=、subagents= 等参数自动挂载 |
大部分默认就有 |
| 你手动追加 |
create_deep_agent(middleware=[...]) |
需自己写或从 LangChain 引入 |
三、易混淆对比(重点)
这一节专门解决学习 Middleware 时最常见的「搞混」问题。
| 对比项 |
middleware |
tools=[] |
| 触发时机 |
LLM 每次调用前/后自动执行 |
只有 LLM 决定调用时才执行 |
| 能否改 system prompt |
✅ 能(wrap_model_call) |
❌ 不能 |
| 能否动态过滤工具列表 |
✅ 能 |
❌ 不能 |
| 能否跨轮次维护状态 |
✅ 能(graph state) |
❌ 一般无状态 |
| 典型场景 |
日志、权限、压缩、记忆注入 |
查天气、调 API、算数 |
| 写法 |
AgentMiddleware 类或 @wrap_tool_call |
@tool 装饰普通函数 |
记忆口诀:
要「每次自动介入」→ middleware
要「LLM 按需调用」→ tools
3.2 middleware= 参数 vs 默认内置栈
| 对比项 |
默认内置栈 |
middleware= 你传的 |
| 是否需要手动添加 |
❌ create_deep_agent 自动组装 |
✅ 需自己传入 |
| 是否替换默认栈 |
❌ 不替换,是追加 |
--- |
| 插入位置 |
框架固定顺序 |
Patch 之后、Memory/HITL 之前 |
| 典型成员 |
TodoList、Filesystem、Summarization |
你的日志、鉴权、计时 |
| 能否关掉某个默认成员 |
部分可通过 excluded_middleware |
--- |
易错点: 以为 middleware=[] 就没有任何中间件了------实际上默认栈仍然在。
3.3 MemoryMiddleware vs SkillsMiddleware
两者都会往 system prompt 里塞内容,但机制完全不同:
| 对比项 |
MemoryMiddleware |
SkillsMiddleware |
| 触发参数 |
memory=["/memory/AGENTS.md"] |
skills=["/skills/user/"] |
| 文件格式 |
AGENTS.md |
SKILL.md(YAML frontmatter) |
| 加载策略 |
启动即全量注入 |
Progressive disclosure(按需展开) |
| 内容性质 |
项目规范、偏好、架构说明 |
可执行工作流技能 |
| 栈中位置 |
尾部(AnthropicCache 之后) |
前部(Filesystem 之前) |
| 子智能体继承 |
general-purpose 继承主 agent |
自定义 subagent 不继承,需单独配 |
| 对比项 |
默认 SummarizationMiddleware |
create_summarization_tool_middleware() |
| 是否默认安装 |
✅ 是 |
❌ 需手动加到 middleware= |
| 触发方式 |
token/消息数超阈值自动压缩 |
Agent 获得 summarize 工具,主动调用 |
| 适合场景 |
长对话自动保命 |
任务间隙、步骤之间主动整理上下文 |
| 控制权 |
框架自动 |
LLM 决定何时压缩 |
3.5 SubAgentMiddleware vs AsyncSubAgentMiddleware
| 对比项 |
SubAgentMiddleware(同步) |
AsyncSubAgentMiddleware(异步) |
| 子智能体类型 |
SubAgent 字典 / CompiledSubAgent |
AsyncSubAgent |
| 主智能体行为 |
调用 task() 后阻塞等待 |
start_async_task 后立即继续 |
| 注入工具 |
task |
start/check/update/cancel/list_async_task |
| 中途追加指令 |
❌ |
✅ update_async_task |
| 取消任务 |
❌ |
✅ cancel_async_task |
| 状态存储 |
消息历史 |
专用 async_tasks 通道(防压缩丢失) |
3.6 HumanInTheLoopMiddleware vs FilesystemPermission interrupt
两者都能「暂停等人」,但入口不同:
| 对比项 |
interrupt_on={...} |
FilesystemPermission(mode="interrupt") |
| 配置方式 |
直接传 interrupt_on 参数 |
传 permissions 列表 |
| 作用范围 |
任意工具名 |
仅内置文件系统工具 |
| HITL 中间件 |
手动配置时自动安装 |
有 interrupt 规则时自动合并安装 |
| 典型场景 |
自定义工具也要审批 |
敏感路径写入要审批 |
| 是否需要 checkpointer |
✅ 必须 |
✅ 必须 |
易错点: 两种可以同时存在;同一工具名时,用户传的 interrupt_on 优先。
3.7 主智能体栈 vs 子智能体栈
| 对比项 |
主智能体 |
同步子智能体(SubAgent) |
SubAgentMiddleware |
✅ 有(可 task() 派活) |
❌ 无(不能再派下级) |
SkillsMiddleware 位置 |
Filesystem 之前 |
Patch 之后 |
middleware 参数 |
create_deep_agent(middleware=...) |
SubAgent 字典里 "middleware": [...] |
| 是否继承主 agent middleware |
--- |
❌ 不继承,各自独立配置 |
| 对比项 |
wrap_tool_call |
wrap_model_call |
| 拦截点 |
每个工具调用 |
每次 LLM 请求 |
| 典型用途 |
日志、鉴权、计时、重试 |
改 prompt、过滤工具、注入上下文 |
| 写法 |
@wrap_tool_call 装饰器 |
继承 AgentMiddleware 实现方法 |
| 入门难度 |
⭐ 简单 |
⭐⭐ 中等 |
3.9 graph state vs self 属性存状态
| 对比项 |
写入 graph state |
修改 self.x |
| 并发安全 |
✅ 按 thread 隔离 |
❌ 子智能体/并行工具会竞态 |
| 官方推荐 |
✅ 推荐 |
❌ 禁止 |
| 写法 |
return {"counter": state["counter"] + 1} |
self.x += 1 |
| 典型场景 |
轮次计数、任务状态 |
--- |
3.10 excluded_middleware:能排除 vs 不能排除
| 中间件 |
能否 excluded |
原因 |
FilesystemMiddleware |
❌ 不能 |
脚手架,文件工具 + 权限安全依赖它 |
SubAgentMiddleware |
❌ 不能 |
脚手架,task 工具依赖它 |
SummarizationMiddleware |
✅ 可以 |
按 name 字符串排除 |
TodoListMiddleware |
✅ 可以 |
非脚手架 |
| 你自定义的 middleware |
✅ 可以 |
--- |
正确禁用子智能体的方式:
# ❌ 错误:excluded_middleware=[SubAgentMiddleware]
# ✅ 正确:
general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False)
# 且 subagents= 不传同步子智能体
四、middleware 参数与插入位置
4.1 参数含义
def create_deep_agent(
...
middleware: Sequence[AgentMiddleware] = (), # 你额外追加的中间件
...
)
| 要点 |
说明 |
默认值 () |
空元组 = 不追加自定义中间件,但默认栈仍在 |
| 类型 |
AgentMiddleware 实例,或 @wrap_tool_call 装饰后的 callable |
| 作用域 |
仅主智能体;子智能体在 SubAgent 字典里单独配 |
4.2 最小示例
from deepagents import create_deep_agent
from langchain.agents.middleware import wrap_tool_call
@wrap_tool_call
def log_tool_calls(request, handler):
print(f"[MW] 调用工具: {request.name}")
result = handler(request)
print(f"[MW] 工具完成: {request.name}")
return result
agent = create_deep_agent(
model="qwen-plus",
middleware=[log_tool_calls], # 追加,不替换默认栈
)
4.3 你的 middleware 插在哪?
... → PatchToolCallsMiddleware
↓
【你的 middleware】 ← middleware= 插入点
↓
Profile extras → AnthropicCache → Memory? → HumanInTheLoop?
| 影响 |
说明 |
| 在 Patch 之后 |
消息历史已被修复,你的逻辑看到干净的历史 |
| 在 Memory 之前 |
你的 middleware 看不到 Memory 注入后的 prompt(除非自己在 wrap_model_call 里读 state) |
| 在 HITL 之前 |
你的 middleware 无法拦截 HITL 暂停逻辑 |
五、默认中间件栈全览
5.1 主智能体完整顺序表
| 序号 |
中间件 |
启用条件 |
核心作用 |
提供工具 |
| 1 |
TodoListMiddleware |
始终 |
任务规划与 todo 跟踪 |
todo 相关 |
| 2 |
SkillsMiddleware |
skills= |
加载 SKILL.md |
无(改 prompt) |
| 3 |
FilesystemMiddleware |
始终 |
文件读写 + permissions |
ls/read/write/grep 等 |
| 4 |
SubAgentMiddleware |
有同步 subagents |
委派子智能体 |
task |
| 5 |
SummarizationMiddleware |
始终 |
上下文自动压缩 |
无(自动触发) |
| 6 |
PatchToolCallsMiddleware |
始终 |
修复悬空 tool_call |
无 |
| 7 |
AsyncSubAgentMiddleware |
有 async subagents |
后台任务管理 |
start/check/update/cancel/list |
| 8 |
你的 middleware |
middleware= |
自定义逻辑 |
可选 |
| 9 |
Profile extras |
按 harness profile |
厂商特定 |
视 profile |
| 10 |
_ToolExclusionMiddleware |
profile excluded_tools |
过滤禁用工具 |
无 |
| 11 |
AnthropicPromptCachingMiddleware |
始终注册 |
Anthropic 缓存 |
无(非 Anthropic 时 no-op) |
| 12 |
MemoryMiddleware |
memory= |
加载 AGENTS.md |
无(改 prompt) |
| 13 |
HumanInTheLoopMiddleware |
interrupt_on= 或 permissions interrupt |
人机审批 |
无(暂停执行) |
5.2 哪些参数会自动挂载哪个 Middleware?
create_deep_agent 参数 |
自动挂载的中间件 |
| (无额外参数) |
TodoList + Filesystem + Summarization + Patch + AnthropicCache |
skills=[...] |
+ SkillsMiddleware |
memory=[...] |
+ MemoryMiddleware |
subagents=[SubAgent(...)] |
+ SubAgentMiddleware |
subagents=[AsyncSubAgent(...)] |
+ AsyncSubAgentMiddleware |
permissions=[...](含 interrupt) |
FilesystemMiddleware 内权限 + HumanInTheLoopMiddleware |
interrupt_on={...} |
+ HumanInTheLoopMiddleware |
middleware=[...] |
+ 你的自定义中间件 |
六、DeepAgents 内置中间件详解
以下中间件由框架自动管理,一般不需要你手动 import 再传入,除非你要单独复用。
6.1 FilesystemMiddleware
| 项目 |
内容 |
| 作用 |
提供文件系统工具;在工具层执行 permissions 规则 |
| 启用 |
始终 |
| 脚手架 |
✅ 不可 excluded |
| 关联参数 |
backend=、permissions= |
提供的工具:
| 工具名 |
权限类型 |
说明 |
ls |
read |
列目录 |
read_file |
read |
读文件 |
glob |
read |
模式匹配找文件 |
grep |
read |
搜索文件内容 |
write_file |
write |
写文件 |
edit_file |
write |
编辑文件 |
execute |
--- |
仅 sandbox backend 支持 |
6.2 SubAgentMiddleware
| 项目 |
内容 |
| 作用 |
注入 task(description, subagent_type) 工具 |
| 启用 |
至少有一个同步 subagent(含默认 general-purpose) |
| 脚手架 |
✅ 不可 excluded |
| 关联参数 |
subagents= |
6.3 AsyncSubAgentMiddleware
| 项目 |
内容 |
| 作用 |
注入 5 个异步任务管理工具 |
| 启用 |
配置了 AsyncSubAgent |
| 状态通道 |
async_tasks(独立于 messages,防压缩丢失) |
| 关联参数 |
subagents=[AsyncSubAgent(...)] |
注入的工具:
| 工具 |
返回 |
start_async_task |
立即返回 task_id |
check_async_task |
状态 + 结果 |
update_async_task |
向运行中任务追加指令 |
cancel_async_task |
取消任务 |
list_async_tasks |
所有任务汇总 |
6.4 SkillsMiddleware
| 项目 |
内容 |
| 作用 |
从 backend 加载 SKILL.md,注入技能元数据到 prompt |
| 启用 |
skills=["/skills/user/", ...] |
| 覆盖规则 |
同名 skill,后路径覆盖前路径 |
| 子智能体 |
自定义 subagent 不继承,需 "skills": [...] |
6.5 MemoryMiddleware
| 项目 |
内容 |
| 作用 |
加载 AGENTS.md,注入持久项目记忆 |
| 启用 |
memory=["/memory/AGENTS.md"] |
| 栈位置 |
尾部(减少对 Anthropic 缓存前缀的影响) |
| 与 Skills 区别 |
见 [3.3 对比表](#项目 内容 作用 加载 AGENTS.md,注入持久项目记忆 启用 memory=["/memory/AGENTS.md"] 栈位置 尾部(减少对 Anthropic 缓存前缀的影响) 与 Skills 区别 见 3.3 对比表) |
6.6 SummarizationMiddleware
| 项目 |
内容 |
| 作用 |
对话 token 接近上限时,自动摘要旧消息 |
| 启用 |
始终(框架内部 create_summarization_middleware 创建) |
| 手动触发版 |
create_summarization_tool_middleware() 加到 middleware= |
| 项目 |
内容 |
| 作用 |
修复「AI 发了 tool_call 但没有 ToolMessage」的悬空调用 |
| 钩子 |
before_agent |
| 典型场景 |
人机中断恢复、参数 malformed、并发打断 |
6.8 HumanInTheLoopMiddleware(LangChain 提供,DeepAgents 自动挂载)
| 项目 |
内容 |
| 作用 |
指定工具调用前暂停,等待人工 approve/reject |
| 启用 |
interrupt_on= 或 permissions mode="interrupt" |
| 前置条件 |
必须配 checkpointer |
| 与 permissions 关系 |
见 [3.6 对比表](#项目 内容 作用 指定工具调用前暂停,等待人工 approve/reject 启用 interrupt_on= 或 permissions mode="interrupt" 前置条件 必须配 checkpointer 与 permissions 关系 见 3.6 对比表) |
七、LangChain 可追加的预置中间件
以下不会 默认安装,需要你自己加到 middleware=[...]:
| 中间件 |
作用 |
与默认栈关系 |
ModelCallLimitMiddleware |
限制 LLM 调用次数 |
追加 |
ToolCallLimitMiddleware |
限制工具调用次数 |
追加 |
ModelFallbackMiddleware |
主模型失败切换备用 |
追加 |
ToolRetryMiddleware |
工具失败指数退避重试 |
追加 |
ModelRetryMiddleware |
模型调用失败重试 |
追加 |
PIIMiddleware |
PII 检测与脱敏 |
追加 |
LLMToolSelectorMiddleware |
小模型先筛工具 |
追加 |
ContextEditingMiddleware |
裁剪工具输出 |
追加 |
ShellToolMiddleware |
持久 shell 会话 |
追加 |
SummarizationMiddleware |
自动摘要 |
⚠️ 默认已有,勿重复加 |
TodoListMiddleware |
Todo 规划 |
⚠️ 默认已有,勿重复加 |
追加示例:工具失败自动重试
from langchain.agents.middleware import ToolRetryMiddleware
agent = create_deep_agent(
model="qwen-plus",
middleware=[
ToolRetryMiddleware(max_retries=3),
],
)
八、自定义中间件:两种写法
8.1 写法对比
| 对比项 |
@wrap_tool_call 装饰器 |
AgentMiddleware 子类 |
| 难度 |
⭐ 入门 |
⭐⭐ 进阶 |
| 能拦截 LLM 调用 |
❌ |
✅ wrap_model_call |
| 能拦截工具调用 |
✅ |
✅ wrap_tool_call |
| 能改启动前状态 |
❌ |
✅ before_agent |
| 适合场景 |
日志、计时、简单鉴权 |
改 prompt、动态工具、复杂状态 |
import os
from deepagents import create_deep_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.chat_models import init_chat_model
model = init_chat_model(
model="qwen-plus",
model_provider="openai",
api_key=os.getenv("ali_api_key"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
@wrap_tool_call
def audit_log(request, handler):
"""拦截每个工具调用:执行前打印、执行后打印。"""
print(f">>> [{request.name}] 开始, 参数={request.args}")
try:
return handler(request)
finally:
print(f">>> [{request.name}] 结束")
agent = create_deep_agent(model=model, middleware=[audit_log])
8.3 写法二:AgentMiddleware 子类
from langchain.agents.middleware import AgentMiddleware
class TurnCounterMiddleware(AgentMiddleware):
"""每次 Agent 启动前,轮次 +1 写入 graph state。"""
def before_agent(self, state, runtime):
n = state.get("turn_count", 0) + 1
print(f"[MW] 第 {n} 轮")
return {"turn_count": n}
agent = create_deep_agent(
model="qwen-plus",
middleware=[TurnCounterMiddleware()],
)
8.4 子智能体单独挂 middleware
agent = create_deep_agent(
model="qwen-plus",
middleware=[global_log], # 仅主智能体
subagents=[
{
"name": "coder",
"description": "写代码",
"system_prompt": "你是程序员",
"middleware": [strict_audit], # 仅 coder 子智能体
},
],
)
| 配置位置 |
作用范围 |
create_deep_agent(middleware=...) |
主智能体 |
SubAgent 字典 "middleware": [...] |
该子智能体独享 |
| 子智能体是否继承主 agent 的 middleware |
❌ 不继承 |
九、中间件钩子(Hooks)速查
| 钩子 |
执行时机 |
能否改 state |
典型用途 |
before_agent |
整轮 Agent 开始前 |
✅ |
修消息历史(PatchToolCalls) |
after_agent |
整轮 Agent 结束后 |
✅ |
汇总、清理 |
wrap_model_call |
每次调 LLM 前 |
✅(通过 request override) |
改 prompt、过滤工具 |
wrap_tool_call |
每次调工具前后 |
❌(改返回值) |
日志、鉴权、重试 |
执行顺序(简化):
before_agent → [循环: wrap_model_call → LLM → wrap_tool_call → 工具] → after_agent
十、实战示例
示例 1:工具调用耗时统计
import time
from langchain.agents.middleware import wrap_tool_call
from deepagents import create_deep_agent
@wrap_tool_call
def timing(request, handler):
t0 = time.time()
result = handler(request)
print(f"{request.name} 耗时 {time.time() - t0:.2f}s")
return result
agent = create_deep_agent(model="qwen-plus", middleware=[timing])
示例 2:组合 Memory + Skills + 自定义 middleware
from deepagents import create_deep_agent
from langchain.agents.middleware import wrap_tool_call
@wrap_tool_call
def log_all(request, handler):
print(f"[MW] → {request.name}")
return handler(request)
agent = create_deep_agent(
model="qwen-plus",
skills=["/skills/project/"], # → SkillsMiddleware 自动挂载
memory=["/memory/AGENTS.md"], # → MemoryMiddleware 自动挂载
middleware=[log_all], # → 你的 middleware 追加
)
示例 3:限制工具调用次数(LangChain 预置)
from langchain.agents.middleware import ToolCallLimitMiddleware
from deepagents import create_deep_agent
agent = create_deep_agent(
model="qwen-plus",
middleware=[ToolCallLimitMiddleware(max_calls=20)],
)
十一、避坑清单
| 序号 |
坑 |
正确做法 |
| 1 |
以为 middleware=[] 就没有中间件 |
默认栈始终在,空参数只是不追加自定义 |
| 2 |
用 excluded_middleware 去掉 Filesystem/SubAgent |
会 ValueError;用 profile enabled=False 禁用 subagent |
| 3 |
在 middleware 里 self.x += 1 |
用 graph state:return {"x": state["x"] + 1} |
| 4 |
重复加 SummarizationMiddleware / TodoListMiddleware |
默认已有,再加可能冲突 |
| 5 |
子智能体以为会继承主 agent 的 middleware |
需在 SubAgent 字典里单独配 "middleware" |
| 6 |
HITL / permissions interrupt 不配 checkpointer |
无法 resume,必须配 InMemorySaver 等 |
| 7 |
自定义 middleware 依赖 Memory 注入内容 |
你的 MW 在 Memory 之前执行,需自己在 wrap_model_call 读 state |
| 8 |
把 permissions 当成万能沙箱 |
只管 FilesystemMiddleware 的内置文件工具,不管自定义工具 |
十二、一张表总结
12.1 用哪个 Middleware?
| 需求 |
方案 |
是否需要 middleware= |
| Agent 读写文件 |
默认 FilesystemMiddleware |
❌ 自动 |
| 控制文件读写权限 |
permissions=[FilesystemPermission(...)] |
❌ 自动 |
| 敏感写入需人工审批 |
permissions mode="interrupt" 或 interrupt_on |
❌ 自动装 HITL |
| 委派同步子任务 |
subagents= → SubAgentMiddleware |
❌ 自动 |
| 委派异步后台任务 |
AsyncSubAgent → AsyncSubAgentMiddleware |
❌ 自动 |
| 加载技能 |
skills= → SkillsMiddleware |
❌ 自动 |
| 加载项目记忆 |
memory= → MemoryMiddleware |
❌ 自动 |
| 上下文太长自动压缩 |
默认 SummarizationMiddleware |
❌ 自动 |
| 记录工具调用日志 |
自定义 @wrap_tool_call |
✅ |
| 改 system prompt |
自定义 AgentMiddleware.wrap_model_call |
✅ |
| 工具失败自动重试 |
ToolRetryMiddleware |
✅ |
| 限制 LLM/工具调用次数 |
ModelCallLimitMiddleware / ToolCallLimitMiddleware |
✅ |
| 主动触发摘要 |
create_summarization_tool_middleware() |
✅ |
12.3 一句话记住 Middleware
Middleware 不是「工具」,而是「在工具和 LLM 之间的自动化管道」。
搞懂默认栈 + 搞懂 middleware= 插入点 + 搞懂钩子,
就搞懂了 DeepAgents 大部分内置能力从哪来。
📚 官方文档