手撕 LangChain Deep Agents 源码 (一):create_deep_agent 是如何"组装"出一个 AI 操作系统的

手撕 LangChain Deep Agents 源码 (一):create_deep_agent 是如何"组装"出一个 AI 操作系统的

系列定位:这不是官方文档翻译,而是一个从零到懂的源码阅读笔记。我会用最通俗的语言,带你把 Deep Agents 的每一行代码都看个明明白白------保证看完之后比看源码之前头发还多(大概)。

源码版本deepagents 0.5.9 阅读前建议 :你需要对 LangChain 的 BaseChatModelBaseTool、LangGraph 有基本了解。如果你用过 create_react_agent,那更好了------你会发现 Deep Agents 就是它的"究极进化形态"。


先说结论:create_deep_agent 到底干了什么?

一句话总结:

create_deep_agent 是一个"装电脑的大师傅"。你给它一颗 CPU(LLM 模型)和一堆外设(工具),它帮你把主板、内存、硬盘、驱动程序全部装好,最后交付一台能直接用的电脑。

用代码说就是:

python 复制代码
from deepagents import create_deep_agent

agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[my_custom_tool],
    memory=["/memory/AGENTS.md"],
    skills=["/skills/project/"],
)

这一行看似简单的代码背后,create_deep_agent 默默做了这些事:

  1. 🔧 解析模型字符串,初始化 LLM 客户端
  2. 🧠 加载模型专属的"性格配置"(Harness Profile)
  3. 🛠️ 组装 8+ 个中间件(Middleware),形成完整的处理链
  4. 🤖 自动创建一个"通用子智能体"(General Purpose SubAgent)
  5. 📝 拼装系统提示词(System Prompt),包含 4 层叠加逻辑
  6. 📋 注入默认工具集(文件操作、Shell 执行、TODO 管理)
  7. 🔒 校验权限和中间件排除规则
  8. 🚀 最终调用 create_agent,设置 9999 的递归上限,交付成品

如果用装电脑来比喻,大概是这样:

步骤 装电脑比喻 Deep Agents 实际行为
选 CPU 选 i9 还是 R9? 解析 model 参数,初始化对应 LLM
刷 BIOS 刷主板固件 加载 Harness Profile(模型专属调优)
插内存条 DDR5 插满 组装 Middleware 链(TodoList、FileSystem、Memory...)
挂硬盘 SSD 插 M.2 绑定 Backend(State/Filesystem/Sandbox)
装外设驱动 显卡驱动 + 网卡驱动 注入工具(Tools)并绑定中间件
装 OS 装 Windows/Linux 拼装 System Prompt
跑分 3DMark 设置 recursion_limit=9999 交付

💡 如果面试官问你 :"create_deep_agentcreate_react_agent 有什么区别?"

你可以这么答:"create_react_agent 是一台裸机------给你 CPU 和工具,剩下的自己搞。create_deep_agent 是一台整机------CPU、主板、内存、硬盘、驱动、操作系统全部装好,甚至还自带一个万能助理。你拿到手就能用。核心差异在于:Deep Agents 自动组装中间件栈、自动创建子智能体、自动匹配模型 Profile,让 Agent 从'能用'变成'好用'。"


源码目录结构:先认识一下"全家桶"

在正式开撕之前,我们先看一下 Deep Agents 的完整目录结构。

bash 复制代码
deepagents/
├── __init__.py              # 包入口,导出核心 API
├── _version.py              # 版本号 0.5.9
├── graph.py                 # 🌟 核心入口!create_deep_agent 就在这里
├── _models.py               # 模型解析工具(resolve_model)
├── _tools.py                # 工具描述重写工具
├── _excluded_middleware.py  # 中间件排除/校验逻辑
│
├── _api/                    # 废弃警告等 API 级别工具
│   └── deprecation.py
│
├── backends/                # 🗄️ 存储后端(文件系统、状态、沙箱)
│   ├── protocol.py          # 后端协议接口定义
│   ├── state.py             # StateBackend(内存后端)
│   ├── filesystem.py        # FilesystemBackend(本地文件系统)
│   ├── sandbox.py           # SandboxBackend(沙箱后端)
│   ├── langsmith.py         # LangSmith 远程后端
│   ├── store.py             # StoreBackend
│   ├── composite.py         # 组合后端
│   └── ...
│
├── middleware/               # 🔌 中间件(灵魂所在!)
│   ├── filesystem.py        # 文件系统中间件(ls/read/write/edit/glob/grep)
│   ├── subagents.py         # 子智能体中间件(task 工具)
│   ├── async_subagents.py   # 异步子智能体中间件
│   ├── summarization.py     # 对话摘要中间件(自动压缩上下文)
│   ├── memory.py            # 记忆中间件(加载 AGENTS.md)
│   ├── skills.py            # 技能中间件(加载 SKILL.md)
│   ├── patch_tool_calls.py  # 修补悬挂工具调用
│   ├── _tool_exclusion.py   # 工具排除中间件
│   └── _utils.py            # 中间件公共工具
│
└── profiles/                # 🎭 模型配置档案
    ├── harness/             # Harness Profile(运行时行为配置)
    │   ├── harness_profiles.py          # Profile 核心定义
    │   ├── _anthropic_sonnet_4_6.py    # Claude Sonnet 4.6 专属配置
    │   ├── _anthropic_opus_4_7.py      # Claude Opus 4.7 专属配置
    │   ├── _anthropic_haiku_4_5.py     # Claude Haiku 4.5 专属配置
    │   └── _openai_codex.py            # OpenAI Codex 专属配置
    └── provider/           # Provider Profile(模型构造配置)
        ├── provider_profiles.py
        ├── _openai.py
        └── _openrouter.py

用装电脑来理解这个结构:

  • graph.py ------ 总装车间。所有的零件在这里被组装成一台完整的电脑。
  • middleware/ ------ 驱动程序。每个中间件都是一个独立的功能模块,像驱动一样被加载。
  • backends/ ------ 硬盘。不管底下是 SSD 还是 HDD,对上层都是同一个"存取"接口。
  • profiles/ ------ BIOS 固件。不同 CPU 有不同的出厂设置。

正片开始:逐行解剖 create_deep_agent

好,准备工作做完了。现在我们打开 graph.py,直接对着源码开始撕。沏杯茶,上菜了。

函数签名:每个参数都是什么?

python 复制代码
def create_deep_agent(
    # ===== 核心参数 =====
    model: str | BaseChatModel | None = None,
        # LLM 大模型。可以传字符串如 "anthropic:claude-sonnet-4-6",
        # 也可以直接传 BaseChatModel 实例。None 则用默认模型(已废弃)
    tools: Sequence[BaseTool | Callable | dict] | None = None,
        # 用户自定义工具列表。可以是 BaseTool 实例、普通函数、或工具描述字典。
        # 这些工具会追加到中间件提供的默认工具集(文件操作等)之后

    # ===== 行为配置 =====
    *,
    system_prompt: str | SystemMessage | None = None,
        # 自定义系统提示词。字符串会拼在 BASE 提示词前面;
        # SystemMessage 形式会保留 Anthropic 的 cache_control 标记
    middleware: Sequence[AgentMiddleware] = (),
        # 用户自定义中间件序列。插入在基础栈之后、尾部栈之前
    subagents: Sequence[SubAgent | CompiledSubAgent | AsyncSubAgent] | None = None,
        # 子智能体列表。三种类型:
        #   SubAgent --- 声明式,系统自动编译
        #   CompiledSubAgent --- 预编译好的
        #   AsyncSubAgent --- 远程异步(LangSmith Deployments)
        # 不传则自动创建一个 general-purpose 子智能体

    # ===== 知识与记忆 =====
    skills: list[str] | None = None,
        # 技能目录路径列表。SkillsMiddleware 会从这些路径加载 SKILL.md,
        # 并将内容注入系统提示词
    memory: list[str] | None = None,
        # 记忆文件路径列表。MemoryMiddleware 会读取这些文件(如 AGENTS.md),
        # 并将内容注入到对话上下文中

    # ===== 权限与存储 =====
    permissions: list[FilesystemPermission] | None = None,
        # 文件系统权限列表。控制 Agent 可以读写哪些目录,
        # 未声明则默认允许当前工作目录
    backend: BackendProtocol | BackendFactory | None = None,
        # 存储后端。决定文件操作、状态、沙箱等底层实现。
        # None 则自动根据环境选择(本地 vs 容器)

    # ===== 高级控制 =====
    interrupt_on: dict[str, bool | InterruptOnConfig] | None = None,
        # 人机协作中断配置。键为工具名,值为 True(始终中断)、
        # InterruptOnConfig(条件中断)。
        # 匹配的工具执行前会暂停,等待人类确认后继续
    response_format: ResponseFormat | type | dict | None = None,
        # 结构化输出格式。可以是 Pydantic 模型、JSON Schema 等,
        # 让 Agent 的最终回复符合指定格式
    context_schema: type | None = None,
        # 对话上下文的类型定义。用于向 Agent 提供运行时环境信息
        # (如当前工作目录、用户信息等)

    # ===== 持久化与调试 =====
    checkpointer: Checkpointer | None = None,
        # 检查点存储。用于保存和恢复对话状态,实现多轮对话持久化。
        # None 则无持久化,每次启动都是全新对话
    store: BaseStore | None = None,
        # 长期记忆存储。跨对话的键值存储,LangGraph 的 BaseStore 实现。
        # 用于 Agent 在不同对话间共享数据
    debug: bool = False,
        # 调试模式。开启后输出详细的中间件调用和状态转换日志
    name: str | None = None,
        # Agent 名称。用于 LangSmith 追踪和日志标识,
        # None 则自动生成
    cache: BaseCache | None = None,
        # LLM 调用缓存。避免重复的 LLM 请求,节省 token 和延迟。
        # None 则不缓存
) -> CompiledStateGraph:
    """创建一个深度智能体(Deep Agent),返回编译后的 LangGraph 状态图。"""

18 个参数,看起来是不是有点吓人?别慌,其实核心就 3 个:

参数 必要性 说明 装电脑比喻
model 推荐 你选的"大脑" 选哪颗 CPU
tools 可选 你给 AI 配的"手" 插什么外设
system_prompt 可选 你对 AI 说的"第一条指令" 装 OS 时选什么语言包

剩下的都是高级配置,后面我们遇到了再细讲。


第一阶段:模型初始化 & Profile 匹配

python 复制代码
# 如果没传 model,用默认的 Claude Sonnet 4.6(但会报警告)
if model is None:
    warn_deprecated(
        since="0.5.3",
        removal="1.0.0",
        message="Passing model=None is deprecated...",
    )
    model = _build_default_model()
else:
    model = resolve_model(model)

这里有两层逻辑:

第一层model=None 是"不选 CPU"模式------帮你默认选 Claude Sonnet 4.6,但这个行为在 0.5.3 版本就被标记为废弃了,1.0.0 版本会彻底移除。就像你去装电脑不选配置,老板默认给你最高配------听起来很爽,但下个月就不这么干了,你得自己选。

第二层resolve_model(model) 是真正的"CPU 适配器"。如果你传的是字符串 "anthropic:claude-sonnet-4-6",它会:

  1. 调用 LangChain 的 init_chat_model 创建模型实例
  2. 通过 ProviderProfile 注册表应用提供商级别的初始化配置
  3. 返回一个配置好的 BaseChatModel 实例

还是装电脑的比喻:resolve_model 就像你在装机单上写"i9-14900K",老板不光给你拿这颗 CPU,还顺带帮你选好配套的主板芯片组、确认散热器兼容性------不同品牌的 CPU(OpenAI、Anthropic、OpenRouter)有不同的"配套方案"。你写"R9 7950X",老板就给你换 AM5 主板;你写"i9-14900K",老板就给你上 LGA 1700。你不需要关心底层的针脚差异,老板全搞定。

接着是 Profile 匹配

python 复制代码
_profile = _harness_profile_for_model(model, _model_spec)

这行代码会根据你的模型自动匹配一个 HarnessProfile。比如你用的是 anthropic:claude-sonnet-4-6,系统就会加载 _anthropic_sonnet_4_6.py 里的配置。

那这个 Profile 里有什么呢?以 Claude Sonnet 4.6 为例:

python 复制代码
_SYSTEM_PROMPT_SUFFIX = """
<use_parallel_tool_calls>
If you intend to call multiple tools and there are no dependencies between
the tool calls, make all of the independent tool calls in parallel...
</use_parallel_tool_calls>

<investigate_before_answering>
Never speculate about code you have not opened...
</investigate_before_answering>

<tool_result_reflection>
After receiving tool results, carefully reflect on their quality...
</tool_result_reflection>
"""

也就是说,Profile 会在你的系统提示词后面追加一段"性格补丁"------告诉 Claude 要并行调用工具、不要瞎猜代码、拿到工具结果要先想清楚下一步。相当于出厂前工程师偷偷刷了一段微码,让 CPU 跑得更稳。

继续装电脑的比喻:Profile 就像 BIOS 里针对不同 CPU 的微码更新。同样是这块主板,你插 i9 和插 R9,BIOS 会自动加载不同的微码------i9 的微码侧重功耗管理,R9 的微码侧重温度墙策略。你不需要手动刷,BIOS 自己会识别。换 CPU 不换主板,系统照样跑,这就是 Profile 的威力。

💡 如果面试官问你:"Deep Agents 怎么做到不同模型自动适配的?"

答案就是 Harness Profile。每个模型注册一个 Profile,里面包含模型专属的提示词后缀、工具描述覆盖、中间件排除规则。你切模型的时候不需要改一行提示词------Profile 自动帮你适配。这就是为什么你从 Claude 换到 GPT-4o,Agent 照样能正常工作。


第二阶段:子智能体处理(SubAgent 装配线)

这是整个函数最复杂的部分,也是 Deep Agents 和普通 Agent 的核心区别------它不只是一个人在战斗,而是一个团队

换到"开公司"的比喻来说:普通的 create_react_agent 就是你一个人干所有事------写代码、写文档、改 Bug、发邮件,全是你的活。create_deep_agent 是你开了一家公司,手下有不同职能的员工,你负责指挥,他们负责干活。你是老板,不是打工人。

create_deep_agent 支持三种类型的子智能体:

类型 类名 开公司比喻
声明式子智能体 SubAgent 招了个实习生,公司帮你培训上岗
预编译子智能体 CompiledSubAgent 招了个老手,自带技能直接上班
异步子智能体 AsyncSubAgent 外包团队,远程协作

代码里是这样区分的:

python 复制代码
for spec in subagents or []:
    if "graph_id" in spec:
        # AsyncSubAgent --- 外包团队
        async_subagents.append(cast("AsyncSubAgent", spec))
        continue
    if "runnable" in spec:
        # CompiledSubAgent --- 老手直接上班
        inline_subagents.append(spec)
    else:
        # SubAgent --- 实习生,需要完整培训
        # ... 一大堆处理逻辑 ...

对于声明式子智能体(SubAgent),系统会做以下"培训":

  1. 模型解析:子智能体可以有自己的大脑(不继承主智能体的模型)
  2. Profile 匹配:子智能体也匹配自己的 Harness Profile
  3. 权限继承:子智能体默认继承主智能体的权限,也可以自己定义
  4. 中间件组装:为子智能体组装一套独立的中间件栈
  5. 工具继承:默认继承主智能体的工具,也可以自定义
  6. Human-in-the-Loop 继承 :主智能体的 interrupt_on 配置会传给子智能体

这段代码有一处特别精妙的设计------子智能体的中间件组装

python 复制代码
subagent_middleware: list[AgentMiddleware] = [
    TodoListMiddleware(),                    # TODO 管理
    FilesystemMiddleware(backend=backend,    # 文件系统
        custom_tool_descriptions=_subagent_profile.tool_description_overrides,
        _permissions=subagent_permissions,
    ),
    create_summarization_middleware(...),     # 对话摘要
    PatchToolCallsMiddleware(),              # 修补悬挂工具调用
]

🔍 设计洞察 :子智能体的中间件比主智能体少!没有 SubAgentMiddleware(员工不需要再招自己的员工------不然套娃下去公司得破产),没有 MemoryMiddleware(记忆由老板统一管理),没有 HumanInTheLoopMiddleware(和客户的沟通由老板控制)。

但最精彩的还是"自动创建通用子智能体"

python 复制代码
gp_profile = _profile.general_purpose_subagent or GeneralPurposeSubagentProfile()
if gp_profile.enabled is not False and not any(
    spec["name"] == GENERAL_PURPOSE_SUBAGENT["name"] for spec in inline_subagents
):
    # ... 自动创建 general-purpose 子智能体 ...
    inline_subagents.insert(0, general_purpose_spec)

即使你没有显式传入任何子智能体,create_deep_agent 也会自动帮你创建一个名为 general-purpose 的通用子智能体 ,并且把它插到子智能体列表的第一位

继续开公司的比喻:这就好比你刚注册了一家公司,连员工都还没招,工商局自动给你派了一个"万能助理"------写文档、查资料、整理文件,什么杂活都能干。你只需要把任务通过 task 工具扔给它就行。免费的,不要白不要。

如果你不想要这个自动创建的助理?可以通过 Profile 配置"裁员":

python 复制代码
register_harness_profile(
    "anthropic:claude-sonnet-4-6",
    HarnessProfileConfig(
        general_purpose_subagent={"enabled": False}
    ),
)

但你想想,免费的万能助理你裁掉干嘛?除非你确信自己一个人能扛下所有活。

💡 如果面试官问你:"Deep Agents 的子智能体和 AutoGen 的多 Agent 有什么区别?"

这题有深度。AutoGen 的多 Agent 是"平级对话"------大家坐在会议室里讨论。Deep Agents 的子智能体是"上下级关系"------主 Agent 派活,子 Agent 干活,干完了汇报。关键区别在于:Deep Agents 的子智能体由主 Agent 通过 task 工具调度,主 Agent 控制权限、工具、中断策略,层级分明。这更接近真实公司的运作方式------老板不会和实习生平起平坐开会。


第三阶段:主智能体中间件栈组装

这是真正的"主板插内存条"环节。create_deep_agent 会按照严格的顺序组装中间件:

python 复制代码
deepagent_middleware: list[AgentMiddleware] = [
    TodoListMiddleware(),              # 1. TODO 管理(最先加载)
]
if skills is not None:
    deepagent_middleware.append(       # 2. 技能加载(如果配置了)
        SkillsMiddleware(backend=backend, sources=skills)
    )
deepagent_middleware.append(           # 3. 文件系统(核心中间件)
    FilesystemMiddleware(backend=backend, ...)
)
if inline_subagents:
    deepagent_middleware.append(       # 4. 子智能体管理
        SubAgentMiddleware(backend=backend, subagents=inline_subagents, ...)
    )
if async_subagents:
    deepagent_middleware.append(       # 5. 异步子智能体
        AsyncSubAgentMiddleware(async_subagents=async_subagents)
    )
deepagent_middleware.extend([           # 6. 对话摘要 + 工具调用修补
    create_summarization_middleware(model, backend),
    PatchToolCallsMiddleware(),
])
# --- 用户自定义中间件插入点 ---
if middleware:
    deepagent_middleware.extend(middleware)
# --- Profile 额外中间件 + 尾部中间件 ---
deepagent_middleware.extend(_profile.materialize_extra_middleware())
if _profile.excluded_tools:
    deepagent_middleware.append(_ToolExclusionMiddleware(...))
deepagent_middleware.append(AnthropicPromptCachingMiddleware(...))
if memory is not None:
    deepagent_middleware.append(MemoryMiddleware(...))
if interrupt_on is not None:
    deepagent_middleware.append(HumanInTheLoopMiddleware(...))

完整中间件加载顺序图

sql 复制代码
┌─────────────────────────────────────────────────┐
│              BASE STACK(基础栈)                 │
│                                                  │
│  1. TodoListMiddleware     --- TODO 列表管理       │
│  2. SkillsMiddleware        --- 技能加载(可选)    │
│  3. FilesystemMiddleware    --- 文件系统操作        │
│  4. SubAgentMiddleware      --- 子智能体调度        │
│  5. AsyncSubAgentMiddleware --- 异步子智能体(可选) │
│  6. SummarizationMiddleware --- 对话自动摘要        │
│  7. PatchToolCallsMiddleware --- 修补悬挂工具调用   │
├─────────────────────────────────────────────────┤
│         USER MIDDLEWARE(用户自定义中间件)       │
│         你的插件插在这里 ↑↑↑                     │
├─────────────────────────────────────────────────┤
│              TAIL STACK(尾部栈)                │
│                                                  │
│  8.  Profile extra_middleware --- Profile 额外中间件│
│  9.  _ToolExclusionMiddleware --- 工具排除过滤     │
│  10. AnthropicPromptCachingMiddleware --- 提示缓存 │
│  11. MemoryMiddleware       --- 记忆加载(可选)    │
│  12. HumanInTheLoopMiddleware --- 人机协作(可选)  │
└─────────────────────────────────────────────────┘

还是装电脑的比喻:基础栈是出厂就焊在主板上的芯片(必须有的核心功能),用户插入点是主板上的 PCIe 插槽(你想加什么自己插),尾部栈是 BIOS 层面的优化(和性能/安全相关,最后生效)。

所以你写自定义中间件的时候,它在基础栈和尾部栈之间------在核心功能之后、在安全校验之前。位置恰到好处,不会挡住核心流程,也不会被安全策略漏掉。

有几个特别值得注意的中间件:

PatchToolCallsMiddleware ------ 它会检查消息历史,如果发现某个 AI 消息调用了工具但没有收到对应的工具结果(可能是因为上下文溢出导致工具结果被截断),就自动补一个"已取消"的回复,防止后续处理崩溃。

继续装电脑的比喻:这就像机箱里的"断电保护器"------如果某根线松了没插紧,保护器自动帮你把电路断开,防止短路烧主板。不是修复问题,而是确保不会因为一个小毛病导致整台电脑挂掉。这种"坏了我兜底"的设计,在大型系统里特别重要------你可以犯错,但系统不会因为你的错而崩。

AnthropicPromptCachingMiddleware ------ 这个中间件非常聪明:它无条件加载,但对非 Anthropic 模型自动静默跳过unsupported_model_behavior="ignore")。这样你切换模型时不需要改任何代码。

还是装电脑:这就像一个万能驱动------你插 NVIDIA 显卡它就干活,你插 AMD 显卡它就安静待着,不会报错也不会蓝屏。主打一个"有了更好,没有也不碍事"。

💡 如果面试官问你:"中间件的加载顺序为什么这么讲究?能随便调吗?"

不能。顺序就是优先级。TodoListMiddleware 在最前面,因为 TODO 管理是基础功能,其他中间件可能依赖它。HumanInTheLoopMiddleware 在最后面,因为人机协作是最外层的安全网,必须在所有内部处理完成后才拦截。你随便调,可能就会出这种 bug:Agent 先问你要不要确认,再去做文件操作------顺序反了,确认了个寂寞。


第四阶段:中间件排除校验(安全检查)

组装完中间件栈之后,代码进行了一系列安全校验:

python 复制代码
# 验证排除配置不会移除必需的脚手架中间件
_validate_excluded_middleware_config(
    _profile,
    required_classes=_REQUIRED_MIDDLEWARE_CLASSES,
    required_names=_REQUIRED_MIDDLEWARE_NAMES,
)

# 应用排除规则
deepagent_middleware = _apply_excluded_middleware(
    deepagent_middleware,
    _profile,
    matched_classes=_main_matched_classes,
    matched_names=_main_matched_names,
)

# 验证每个排除规则都匹配到了实际的中间件
_verify_excluded_middleware_coverage(
    _profile,
    _main_matched_classes,
    _main_matched_names,
    required_classes=_REQUIRED_MIDDLEWARE_CLASSES,
    required_names=_REQUIRED_MIDDLEWARE_NAMES,
)

系统定义了两组不可移除的脚手架中间件

python 复制代码
_REQUIRED_MIDDLEWARE = (
    (FilesystemMiddleware, ()),   # 文件系统中间件------所有文件工具都依赖它
    (SubAgentMiddleware, ()),     # 子智能体中间件------task 工具依赖它
)

🚧 设计原则FilesystemMiddlewareSubAgentMiddleware 被标记为"脚手架"------你可以移除摘要中间件、可以移除记忆中间件,但你不能移除文件系统和子智能体。因为一旦移除,核心功能就会静默失效,这比直接报错更危险。

校验逻辑分三步:

  1. 语法校验:排除配置中的名称不能为空、不能有下划线前缀、不能有冒号(防拼写错误)
  2. 脚手架校验 :不能排除 FilesystemMiddlewareSubAgentMiddleware(防核心功能丢失)
  3. 覆盖率校验:每个排除规则都必须匹配到至少一个实际存在的中间件(防无效配置)

🔒 安全哲学:宁可报错让你修,也不能静默失效让你排查三天。这是优秀的库设计------把运行时的静默失败转化为构建时的显式错误。你的配置写错了,启动时就炸,而不是上线后跑了半个月才发现 Agent 怎么不读文件了。

继续装电脑的比喻:你不能把 CPU 供电线拔了然后问"为什么开不了机"。系统直接不让你拔------拔之前就报错提醒你"这根线是必须的"。你要是硬拔,电源直接罢工给你看。


第五阶段:System Prompt 拼装(四层叠加)

create_deep_agent 的系统提示词不是简单的"你是一个 AI 助手",而是一个四层叠加的结构:

sql 复制代码
┌──────────────────────────────────┐
│  USER(用户自定义)               │  ← 你传入的 system_prompt 参数
│  "你是一个开发专家..."            │     永远在最前面,优先级最高
├──────────────────────────────────┤
│  BASE / CUSTOM(基础 / Profile)  │  ← BASE_AGENT_PROMPT 或 Profile 的 base_system_prompt
│  "You are a deep agent..."       │     Profile 可以完全替换 BASE
├──────────────────────────────────┤
│  SUFFIX(Profile 尾部指令)       │  ← Profile 的 system_prompt_suffix
│  "<use_parallel_tool_calls>..."  │     距离对话历史最近,模型最关注
└──────────────────────────────────┘

代码实现:

python 复制代码
base_prompt = _apply_profile_prompt(_profile, BASE_AGENT_PROMPT)

if system_prompt is None:
    final_system_prompt = base_prompt
elif isinstance(system_prompt, SystemMessage):
    # 保留 SystemMessage 的 cache_control 标记
    final_system_prompt = SystemMessage(
        content_blocks=[*system_prompt.content_blocks,
                        {"type": "text", "text": f"\n\n{base_prompt}"}]
    )
else:
    # 字符串形式:USER + BASE/CUSTOM
    final_system_prompt = system_prompt + "\n\n" + base_prompt

关键设计决策

  1. USER 永远在最前面:你传入的指令不会被 Profile 覆盖
  2. SUFFIX 在最后面:模型对末尾内容关注度最高(这个有心理学研究支撑------人也是这样,最后说的话最容易被记住)
  3. 支持 SystemMessage 形式 :保留 Anthropic 的 cache_control 标记,方便做提示缓存优化

用做菜来比喻:USER 是你点菜时说的"不要辣"(最优先,厨师必须听),BASE 是菜谱的基本做法(默认流程),SUFFIX 是装盘前厨师加的那把葱花(最后加的但最显眼,食客最先看到)。

💡 如果面试官问你:"为什么 USER prompt 放最前面,SUFFIX 放最后面?"

这是故意的设计。USER 是你的"硬性指令"------不管底层怎么变,你的要求不能被覆盖,所以放最前面,类似宪法。SUFFIX 是模型的"行为微调"------距离对话最近,模型最关注,相当于执行层面的战术指导。中间的 BASE 是"基本法"------兜底用的,你不出 system_prompt 的时候它就是全部规则。三层各有分工,互不干扰。


第六阶段:最终交付(调用 create_agent)

所有零件都装好了,最后一步------拧上最后一颗螺丝:

python 复制代码
return create_agent(
    model,
    system_prompt=final_system_prompt,
    tools=_tools,
    middleware=deepagent_middleware,
    response_format=response_format,
    context_schema=context_schema,
    checkpointer=checkpointer,
    store=store,
    debug=debug,
    name=name,
    cache=cache,
).with_config({
    "recursion_limit": 9_999,    # 🔥 递归上限设为 9999!
    "metadata": {
        "ls_integration": "deepagents",
        "versions": {"deepagents": __version__},
        "lc_agent_name": name,
    },
})

注意那个 recursion_limit: 9_999 ------普通的 create_react_agent 默认递归限制通常是 25 或 50,而 Deep Agents 直接拉到了 9999

继续装电脑的比喻:普通 Agent 就像一台设了 30 分钟自动休眠的电脑------你正干活呢,它突然"累了"不动了。Deep Agents 直接把休眠时间设到 9999 分钟------基本上等于"你别休眠了,给我一直跑,跑到任务完成或者没电为止"。

但别担心这会导致无限循环------SummarizationMiddleware 会在上下文接近溢出时自动压缩对话历史,确保 Agent 不会因为 token 用完而崩溃。就像电脑的虚拟内存------内存快满了自动把冷数据换到硬盘,确保系统不会 OOM。所以 9999 不是让你真的递归 9999 次,而是"别因为递归限制打断我,我自己有别的机制控制"。

💡 如果面试官问你:"递归上限设 9999 会不会导致死循环烧 token?"

不会。9999 是天花板,不是目标。Deep Agents 有 SummarizationMiddleware 做上下文压缩,有 PatchToolCallsMiddleware 做断路保护。真正控制递归的是"任务完成"和"上下文预算",不是这个数字。设 9999 的意思是------"我不希望因为技术限制被迫中断,只在任务真正完成时停下"。


全流程总结:一张图看懂 create_deep_agent

scss 复制代码
用户调用 create_deep_agent(model, tools, ...)
        │
        ▼
  ┌─────────────┐
  │ 1. 解析模型  │  resolve_model() → BaseChatModel 实例
  └──────┬──────┘
         ▼
  ┌─────────────┐
  │ 2. 匹配配置  │  _harness_profile_for_model() → HarnessProfile
  └──────┬──────┘
         ▼
  ┌──────────────────────────────────────────┐
  │ 3. 处理子智能体                            │
  │   ├── 解析用户传入的 subagents             │
  │   ├── 为每个子智能体组装中间件              │
  │   └── 自动创建 general-purpose 子智能体    │
  └──────┬───────────────────────────────────┘
         ▼
  ┌──────────────────────────────────────────┐
  │ 4. 组装主中间件栈                          │
  │   基础栈 → 用户中间件 → 尾部栈             │
  └──────┬───────────────────────────────────┘
         ▼
  ┌──────────────────────────────────────────┐
  │ 5. 安全校验                                │
  │   ├── 验证排除规则不会移除脚手架中间件       │
  │   ├── 应用排除规则过滤中间件                │
  │   └── 验证所有排除规则都有对应中间件         │
  └──────┬───────────────────────────────────┘
         ▼
  ┌──────────────────────────────────────────┐
  │ 6. 拼装 System Prompt                     │
  │   USER → BASE/CUSTOM → SUFFIX             │
  └──────┬───────────────────────────────────┘
         ▼
  ┌──────────────────────────────────────────┐
  │ 7. 交付成品                                │
  │   create_agent(...).with_config({          │
  │       recursion_limit: 9_999,              │
  │       metadata: {ls_integration: "deepagents"}│
  │   })                                       │
  └──────────────────────────────────────────┘

本篇小结:三个值得记住的设计

1. Deep Agents 不只是一个 Agent,是一个 Agent 工厂

它帮你自动创建子智能体(general-purpose),就像开公司时系统自动给你配了一个万能助理。你不需要自己写子智能体的创建逻辑,系统帮你搞定了中间件、权限、工具继承。这种"开箱即用的团队"设计,是 Deep Agents 和普通 Agent 最大的区别。你一个人能干多少活?有了团队,产能直接翻倍。

2. Harness Profile 是"隐形的 BIOS 微码"

你看不到它,但它一直在工作。不同的模型有不同的"出厂调优"------Claude Sonnet 被告诉要"并行调工具",OpenAI Codex 被告诉要"像个自主的高级工程师"。这个机制让你切换模型时不需要改一行提示词代码,BIOS 自动适配。你插 i9 它就刷 i9 的微码,你插 R9 它就刷 R9 的微码,丝滑切换。

3. 脚手架中间件 = 不可拔的 CPU 供电线

很多框架把"附加功能"做成可选项,但 Deep Agents 把 FilesystemMiddlewareSubAgentMiddleware 设计为不可移除的脚手架 。这不是限制自由,而是防止你把"CPU 供电线"拔了还奇怪为什么电脑开不了机。宁可构建时报错,也不运行时静默失效。这个设计哲学值得学习------好的约束不是枷锁,是护栏


下一篇预告

手撕 LangChain Deep Agents 源码 (二):System Prompt 的组装------四层叠加背后的"潜规则"

我们刚刚看到了 System Prompt 的四层结构(USER → BASE → CUSTOM → SUFFIX),但只是走马观花。下一篇我们深入拆解:

  • BASE_AGENT_PROMPT 的完整内容------Deep Agents 默认给 Agent 塞了多少"出厂指令"?
  • HarnessProfile 如何通过 base_system_prompt 完全替换 BASE?
  • system_prompt_suffix 里的 XML 标签到底在告诉模型什么?
  • _apply_profile_prompt 的合并逻辑------当 CUSTOM 和 SUFFIX 同时存在时,谁覆盖谁?
  • Anthropic 的 cache_control 标记如何影响 Prompt 缓存策略?

看完下一篇,你就知道为什么 Deep Agents 的 Agent 比"手写 prompt"的 Agent 聪明那么多------因为它的 System Prompt 根本不是一个人写的,而是一整条流水线组装出来的。


📖 源码阅读建议 :如果你想跟着看源码,建议从 graph.pycreate_deep_agent 函数开始,然后跳到 _models.pyresolve_model,再看 profiles/harness/harness_profiles.py 理解 Profile 机制。中间件的细节留给后续篇章。

💬 有什么问题? 欢迎在评论区讨论。如果你发现我哪里理解有误,或者有什么想让我深入讲的部分,直接说------源码面前,没有废话。

相关推荐
用户298698530141 小时前
Java 操作 Word 文档:数学公式与符号的插入方法
java·后端
小撒的私房菜1 小时前
Day 5:Agent Loop——整个系列里最关键的一天
人工智能·后端
XovH1 小时前
Django 模型(Model)设计:无需 SQL,用 Python 类定义你的数据库
后端
传说之后1 小时前
Go 调用 OpenAI 兼容 API:对话、流式输出、上下文与图片识别
后端
传说之后1 小时前
Go Channel 解析:原理与实践
后端
XovH1 小时前
Django Admin:5 分钟搭建一个全功能的后台管理系统
后端
阿星做前端1 小时前
不想再给ai回复下一步了,于是我给agent装上了一个自动挡
前端·后端·程序员
SimonKing1 小时前
Firefox 太卡?换了这浏览器,内存占用直接降了 70%
java·后端·程序员