返回博客列表

LangChain deepagents 架构拆解:中间件与 Backend 的双轴设计

2026-08-14T22:30:00+08:00
deepagentsLangChainAgent 架构MiddlewareBackendContext Engineering

LangChain deepagents 架构拆解:中间件与 Backend 的双轴设计

上次拆了 Pi 的上下文压缩,这次拆 deepagents 的骨架。两个项目思路完全不同,但都指向同一件事。

写 Agent 框架有两个绕不开的问题:行为从哪来(子 Agent、待办清单、文件工具、摘要压缩这些能力怎么组装),边界在哪(Agent 的"文件系统"到底落在什么存储上)。

LangChain 的 deepagents(GitHub 27k+ 星,自称"batteries-included agent harness")把这两个问题拆成了两根正交的轴:

  • Middleware 轴:每个能力是一段可插拔的中间件,包裹模型调用、工具执行、状态和输出流
  • Backend 轴:文件系统工具背后的存储引擎可替换,从内存状态到本地磁盘到跨线程 Store 再到沙箱

这篇文章拆解这两根轴的设计,以及它们咬合的方式。

本文提纲

  1. 全景图:create_deep_agent 背后的组装逻辑
  2. Middleware 轴:能力即中间件
  3. 中间件的 7 个拦截面
  4. 执行顺序:内层先执行,异常向外抛
  5. 内置中间件清单
  6. Backend 轴:8 种存储引擎
  7. CompositeBackend:按路径前缀路由的"文件系统路由器"
  8. 安全边界:三个警告你必须在部署前读完
  9. 选型速查表

全景图:create_deep_agent 背后的组装逻辑

deepagents 的核心入口是 create_deep_agent()(来自 deepagents.graph)。但真正值得看的是它的组装哲学--文档原话:"模块化中间件架构,每个核心能力都实现为可组合的中间件"(modular middleware architecture where each core capability is implemented as composable middleware)。

也就是说,deepagents 里的"深度 Agent"不是一个大类,而是一堆中间件叠出来的效果:

from langchain.agents import create_agent
from deepagents.middleware.filesystem import FilesystemMiddleware
from deepagents.middleware.subagents import SubAgentMiddleware

agent = create_agent(
    model="claude-sonnet-4-6",
    middleware=[
        FilesystemMiddleware(backend=None),
        SubAgentMiddleware(default_model="claude-sonnet-4-6", subagents=[...]),
    ],
)

想要文件工具?加 FilesystemMiddleware。想要子 Agent?加 SubAgentMiddleware。不想要待办清单?去掉对应中间件就行。"batteries-included" 和 "可裁剪" 在这个设计里不矛盾--电池都在盒子里,但每节都可以拆。

Middleware 轴:能力即中间件

中间件列表传给 create_agent()create_deep_agent(),每个中间件在特定拦截点上包裹 Agent 行为。理解这套设计的关键,是搞清楚中间件到底能动什么。

答案是 7 个面。

中间件的 7 个拦截面

拦截面 机制 典型中间件
工具集 向 Agent 注入新工具 FilesystemMiddlewarels/read_file/write_file/edit_fileSubAgentMiddlewaretask 工具;TodoListMiddlewarewrite_todos
System prompt 追加指导文本 TodoListMiddleware(system_prompt=...)FilesystemMiddleware(system_prompt=...)
状态/上下文 改写消息历史 SummarizationMiddleware 压缩旧消息;ContextEditingMiddleware 用占位符替换旧工具输出
模型调用 包裹模型调用 ModelFallbackMiddleware("gpt-5.4-mini", "claude-3-5-sonnet-20241022") 降级链
工具执行 拦截工具运行前后 错误处理、重试、调用限额(thread_limit/run_limit
输入输出流 过滤消息和流事件 PIIMiddleware 脱敏用户输入、模型输出、工具结果,流式场景用注册的 stream transformer
工具可见性 按需隐藏工具 schema LLMToolSelectorMiddleware 按查询过滤工具;ProviderToolSearchMiddleware 把工具检索推给 provider 侧

这张表比任何定义都直观:中间件不是"钩子"这么简单,它可以从 7 个维度重塑 Agent。对比上一篇文章拆的 Pi--Pi 的扩展能监听事件、改工具、改 TUI,而 deepagents 把拦截面标准化成了这 7 类,每种都有明确的语义。

执行顺序:内层先执行,异常向外抛

多个中间件叠加时,顺序有明确语义:列表里靠前的中间件在内层,更贴近核心执行。

文档给了一个典型组合:

middleware=[
    ToolRetryMiddleware(max_retries=3, on_failure="error"),  # 内层:先重试
    ToolErrorMiddleware(on_error=on_error),                   # 外层:兜底捕获
]

异常流动方向是向外:重试中间件耗尽 3 次尝试后重新抛出,错误中间件接住,转换成模型可见的错误 ToolMessage--模型能看到失败原因并自行调整策略。

这套语义和 Web 框架的洋葱模型一模一样:请求向内穿过多层中间件,响应和异常向外穿回。写惯了 Express/Koa 或 Django 的工程师可以无缝迁移心智模型。

内置中间件清单

deepagents 自带的中间件按能力分组,覆盖了深度 Agent 的全部标配:

文件系统FilesystemMiddleware):暴露 lsread_filewrite_fileedit_filedeleteglobgrep 一整套文件工具,还支持读视频文件(ReadVideoFileSchema)和 execute(取决于 backend)。权限用 FilesystemPermissionoperations/paths/mode)声明式控制。

子 AgentSubAgentMiddleware / AsyncSubAgentMiddleware):前者加一个 task 工具派生同步子 Agent,每个子 Agent 可独立配置 modeltoolsmiddlewaresystem_promptresponse_format;后者管理后台任务,提供 start_async_taskcheck_async_taskcancel_async_tasklist_async_tasks 一整套工具。

摘要压缩SummarizationMiddleware):上一篇文章拆 Pi compaction 时讲过的问题,这里的解法--触发条件支持按 tokens/messages/fraction 三种维度配置(TriggerClause),工具结果可按 keep/max_length 截断(TruncateArgsSettings)。还有 SummarizationToolMiddleware 变体,把压缩做成模型可主动调用的工具。

技能与记忆SkillsMiddleware / MemoryMiddleware):前者从多个 SkillSource 加载技能文件注入上下文,后者管理长期记忆,两者都支持自定义 system prompt 模板。

自评分RubricMiddleware):让 Agent 按评分标准(Rubric)自我评估、自我迭代,最多 max_iterations 轮,CriterionPass/CriterionFail 逐条记录。

模型适配:一组针对 NVIDIA Nemotron 的 harness 中间件(工具调用 shim、限流重试、消息兼容、进度预算、答案守卫等),通过 HarnessProfile 机制按模型注册--这套 profile 系统也支持你自己注册(register_harness_profile())。

Backend 轴:8 种存储引擎

Middleware 轴解决"Agent 能做什么",Backend 轴解决"Agent 的文件落在哪"。

deepagents 的文件工具不是直接操作磁盘,而是通过 BackendProtocol 接口(ls/read/write/edit/glob/grep,返回 ReadResult/WriteResult 等结构化结果)抽象出来。换 Backend,文件工具的行为整体改变,中间件层完全无感。

8 种 Backend 各管一段:

1. StateBackend(默认):文件存在 LangGraph 的 Agent 状态里,跟着 checkpointer 持久化,线程内共享(子 Agent 写的文件,任务结束后主 Agent 能看到),但不跨线程。适合做中间结果的草稿区。

agent = create_deep_agent(
    model="google_genai:gemini-3.6-flash",
    backend=StateBackend(),  # 默认值
)

2. FilesystemBackend:读写真实磁盘,rooted 在 root_dirvirtual_mode=True 会做路径规范化,挡掉 ..~ 和越界的绝对路径。注意文档警告:不开 virtual_mode 的话"设了 root_dir 也没有任何安全性"。

3. LocalShellBackend:在 FilesystemBackend 基础上加 execute 工具,subprocess.run(shell=True) 直接执行,默认超时 120 秒。无沙箱,命令以你的用户权限跑,可以碰系统上任何路径。

4. StoreBackend:文件存进 LangGraph BaseStore(Redis、Postgres、InMemory 都行),实现跨线程持久化。关键是 namespace 参数--一个 Runtime -> tuple 的工厂函数,用运行时上下文做数据隔离:

backend=StoreBackend(namespace=lambda rt: (rt.server_info.user.identity,))
# 按用户隔离;也可以按 assistant_id 或 thread_id 隔离

5. ContextHubBackend:把文件系统落在 LangSmith Hub 仓库上,写入即 commit,带乐观并发控制。适合已经深度使用 LangSmith 生态的团队。

6. BaseSandbox / 7. LangSmithSandbox:沙箱执行基类和 LangSmith 实现,提供带输出上限(MAX_OUTPUT_BYTES)的隔离执行,支持文件上传下载。生产环境跑不可信代码的正解。

8. CompositeBackend:本身不存储,是个路由器--见下一节。

CompositeBackend:按路径前缀路由的"文件系统路由器"

CompositeBackend 是整个 Backend 设计里最精巧的部分。它按路径前缀把文件操作路由到不同 Backend:

agent = create_deep_agent(
    model="google_genai:gemini-3.6-flash",
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(namespace=lambda rt: (rt.server_info.user.identity,)),
            "/workspace/": FilesystemBackend(root_dir="/path/to/project", virtual_mode=True),
        },
    ),
)

这一段配置的实际效果:

  • /memories/agent.md -> 进 StoreBackend,跨会话、按用户隔离的长期记忆
  • /workspace/plan.md 下的真实文件 -> 直接读写本地磁盘项目目录
  • 其他所有路径(包括 Agent 内部数据)-> 落在 StateBackend,会话结束即消失

对 Agent 来说,这是一个统一的虚拟文件系统;对你来说,每个目录前缀有不同的持久化语义和生命周期。

路由规则三条:前缀匹配优先走 route,其余走 default长前缀优先/memories/projects/ 可以覆盖 /memories/ 的规则);ls/glob/grep 会聚合所有 backend 的结果并保留原始前缀。

还有一个必须知道的细节:deepagents 会往 backend 写内部数据(卸载的大工具结果在 /large_tool_results/,对话历史在 /conversation_history/)。这些内部数据走 default backend--所以默认用 StateBackend,让它们随会话过期,别污染你的真实存储。这也是为什么文档特别提醒:单独用 FilesystemBackend 会把内部数据和你的项目文件混在一起,应该包一层 CompositeBackend。

安全边界:三个警告你必须在部署前读完

deepagents 文档里有三段加粗的安全警告,值得原样转述:

FilesystemBackend 授予真实文件系统访问。 Agent 能读到 secrets;配合网络工具,secrets "可能通过 SSRF 攻击被外泄"。适用场景:本地开发 CLI、CI/CD。不适用:Web 服务器。

LocalShellBackend 授予任意 shell 执行。 以你的用户权限运行,操作"永久且不可逆",命令可消耗无限资源。特别提醒:开了 shell 之后 virtual_mode=True 不提供任何安全性--路径沙箱挡不住 shell 里的 cat /etc/passwd

生产环境的正解是沙箱 backend。 需要文件交互或 shell 执行的线上场景,用 LangSmith Sandbox 或 Daytona/AgentCore 等沙箱集成,配合 FilesystemPermission 声明式规则(在 backend 之前评估,比如禁止写 /policies/**)和 HITL(human-in-the-loop)中间件。

这三条警告串起来是一个清晰的梯度:StateBackend 无风险 -> FilesystemBackend 限本地 -> LocalShellBackend 仅限可信环境 -> 沙箱才配进生产。选型时先问自己"这段代码我敢不敢让它 rm -rf",答案直接决定 backend 的下限。

选型速查表

需求 Backend
默认草稿区,线程内共享 StateBackend
本地项目文件,开发 CLI、CI FilesystemBackend + virtual_mode=True,或包 CompositeBackend
可信环境的本地 shell 执行 LocalShellBackend(仅开发环境)
跨线程记忆,多用户隔离 StoreBackend + namespace 工厂
LangSmith 原生持久化 ContextHubBackend
生产环境隔离执行 沙箱 backend
以上任意组合 CompositeBackend

两个版本迁移提示:backend 工厂(lambda rt: StateBackend(rt))从 0.5.0 起废弃,直接传实例;namespace 工厂从 0.5.2 起接收 Runtime 对象,旧的 BackendContext 访问方式将在 0.7 移除。

双轴咬合:为什么这个设计值得学

把两根轴放在一起看,deepagents 的架构答案其实很克制:

  • 能力(做什么)全部走 Middleware,可拆可换可叠
  • 资源(落在哪)全部走 Backend,统一协议、声明式权限
  • 两轴只在 FilesystemMiddleware(backend=...) 一个点上相交

想给 Agent 加子 Agent 能力?中间件层解决,存储层不动。想把本地 CLI 改成多用户 SaaS?换 Backend 加 namespace,能力层不动。这种正交性让变化被限制在单轴内--工程上叫关注点分离,写 Agent 框架时叫"别把能力和存储焊死"。

对比上次拆的 Pi:Pi 用 TypeScript 扩展 + 树形会话 + 手动压缩配置走"原语极简"路线,deepagents 用标准化的 7 个拦截面 + 8 种存储引擎走"协议全覆盖"路线。两条路背后是同一个判断:Agent 框架的核心资产不是 prompt,是可组合的结构

参考文档与链接


作者: itech001 来源: 公众号:AI人工智能时代 网站: https://www.theaiera.cn/ 每日分享最前沿的AI新闻资讯和技术研究。

本文首发于 AI人工智能时代,转载请注明出处。

分享给朋友