LangChain deepagents 架构拆解:中间件与 Backend 的双轴设计
上次拆了 Pi 的上下文压缩,这次拆 deepagents 的骨架。两个项目思路完全不同,但都指向同一件事。
写 Agent 框架有两个绕不开的问题:行为从哪来(子 Agent、待办清单、文件工具、摘要压缩这些能力怎么组装),边界在哪(Agent 的"文件系统"到底落在什么存储上)。
LangChain 的 deepagents(GitHub 27k+ 星,自称"batteries-included agent harness")把这两个问题拆成了两根正交的轴:
- Middleware 轴:每个能力是一段可插拔的中间件,包裹模型调用、工具执行、状态和输出流
- Backend 轴:文件系统工具背后的存储引擎可替换,从内存状态到本地磁盘到跨线程 Store 再到沙箱
这篇文章拆解这两根轴的设计,以及它们咬合的方式。
本文提纲
- 全景图:create_deep_agent 背后的组装逻辑
- Middleware 轴:能力即中间件
- 中间件的 7 个拦截面
- 执行顺序:内层先执行,异常向外抛
- 内置中间件清单
- Backend 轴:8 种存储引擎
- CompositeBackend:按路径前缀路由的"文件系统路由器"
- 安全边界:三个警告你必须在部署前读完
- 选型速查表
全景图: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 SubAgentMiddlewareagent = 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 注入新工具 | FilesystemMiddleware 加 ls/read_file/write_file/edit_file;SubAgentMiddleware 加 task 工具;TodoListMiddleware 加 write_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):暴露 ls、read_file、write_file、edit_file、delete、glob、grep 一整套文件工具,还支持读视频文件(ReadVideoFileSchema)和 execute(取决于 backend)。权限用 FilesystemPermission(operations/paths/mode)声明式控制。
子 Agent(SubAgentMiddleware / AsyncSubAgentMiddleware):前者加一个 task 工具派生同步子 Agent,每个子 Agent 可独立配置 model、tools、middleware、system_prompt、response_format;后者管理后台任务,提供 start_async_task、check_async_task、cancel_async_task、list_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_dir。virtual_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,是可组合的结构。
参考文档与链接
- deepagents Middleware API Reference - 中间件与 profile 的完整 API 索引
- deepagents Backends API Reference - BackendProtocol 及 8 种 backend 的 API 清单
- Backends 概念文档 - 各 backend 的代码示例、安全警告与选型指南
- Middleware 概念文档 - 中间件拦截面、执行顺序与组合模式
- GitHub: langchain-ai/deepagents - MIT 协议开源,27k+ 星,"batteries-included agent harness"
作者: itech001
来源: 公众号:AI人工智能时代
网站: https://www.theaiera.cn/
每日分享最前沿的AI新闻资讯和技术研究。
本文首发于 AI人工智能时代,转载请注明出处。
