DeepAgents Middleware 中间件:概念与实战

目录


一、30 秒看懂 Middleware

text 复制代码
Middleware = 挂在 Agent 运行链路里的「自动插件」

它在 LLM 思考之前、工具执行前后自动介入,
不需要 LLM 主动「决定调用」。

类比:

角色 对应概念 谁触发
招式 tools=[] 里的普通工具 LLM 决定何时调用
神经系统 Middleware 中间件 框架每次自动执行

二、核心概念

2.1 Middleware 在链路中的位置

text 复制代码
用户消息进入
    │
    ▼
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 时最常见的「搞混」问题。

3.1 Middleware vs tools\[\]

对比项 middleware tools=[]
触发时机 LLM 每次调用前/后自动执行 只有 LLM 决定调用时才执行
能否改 system prompt ✅ 能(wrap_model_call ❌ 不能
能否动态过滤工具列表 ✅ 能 ❌ 不能
能否跨轮次维护状态 ✅ 能(graph state) ❌ 一般无状态
典型场景 日志、权限、压缩、记忆注入 查天气、调 API、算数
写法 AgentMiddleware 类或 @wrap_tool_call @tool 装饰普通函数

记忆口诀:

text 复制代码
要「每次自动介入」→ 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 不继承,需单独配

3.4 SummarizationMiddleware vs create_summarization_tool_middleware

对比项 默认 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 --- 不继承,各自独立配置

3.8 wrap_tool_call vs wrap_model_call

对比项 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 ✅ 可以 ---

正确禁用子智能体的方式:

python 复制代码
# ❌ 错误:excluded_middleware=[SubAgentMiddleware]
# ✅ 正确:
general_purpose_subagent=GeneralPurposeSubagentProfile(enabled=False)
# 且 subagents= 不传同步子智能体

四、middleware 参数与插入位置

4.1 参数含义

python 复制代码
def create_deep_agent(
    ...
    middleware: Sequence[AgentMiddleware] = (),  # 你额外追加的中间件
    ...
)
要点 说明
默认值 () 空元组 = 不追加自定义中间件,但默认栈仍在
类型 AgentMiddleware 实例,或 @wrap_tool_call 装饰后的 callable
作用域 仅主智能体;子智能体在 SubAgent 字典里单独配

4.2 最小示例

python 复制代码
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 插在哪?

text 复制代码
... → 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=

6.7 PatchToolCallsMiddleware

项目 内容
作用 修复「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 规划 ⚠️ 默认已有,勿重复加

追加示例:工具失败自动重试

python 复制代码
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、动态工具、复杂状态

8.2 写法一:wrap_tool_call(推荐入门)

python 复制代码
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 子类

python 复制代码
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

python 复制代码
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 每次调工具前后 ❌(改返回值) 日志、鉴权、重试

执行顺序(简化):

text 复制代码
before_agent → [循环: wrap_model_call → LLM → wrap_tool_call → 工具] → after_agent

十、实战示例

示例 1:工具调用耗时统计

python 复制代码
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

python 复制代码
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 预置)

python 复制代码
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

text 复制代码
Middleware 不是「工具」,而是「在工具和 LLM 之间的自动化管道」。
搞懂默认栈 + 搞懂 middleware= 插入点 + 搞懂钩子,
就搞懂了 DeepAgents 大部分内置能力从哪来。

📚 官方文档