你以为写 Agent 最难的是 Prompt?其实最难的是怎么让一堆 Agent 不打架、不跑偏、不循环。
一、开场:为什么你需要 DeepAgent
学会 LangChain 和 LangGraph 之后,很多人会经历一个"能力过剩但效率不足"的阶段:
- 单个 Agent 写起来很快,但一旦业务复杂起来(调研 → 分析 → 起草 → 审阅 → 定稿),一个 Prompt 根本装不下;
- 多 Agent 协作听起来很美,但状态怎么传?子 Agent 怎么调度?上下文爆炸怎么办?
- 你开始手动写一堆 Node 拼接,结果发现 80% 的时间都花在"造轮子"上,而不是业务逻辑。
DeepAgent 就是来解决这个问题的。
它是 LangGraph 生态里面向生产落地的高阶封装方案,把复杂 Agent 需要的核心能力------任务规划、长期记忆、子 Agent 调度、上下文压缩------全部打包好了。你可以把它理解为:
- LangChain:给你一堆 AI 开发积木
- LangGraph:搭建复杂工作流的底层蓝图
- DeepAgent:直接给你一栋能住人的房子
跳过重复的底层基建,直接聚焦 Agent 的业务逻辑与能力迭代。
今天我们用一个真实案例------「深度调研助手」------把 DeepAgent 的核心用法讲透。这个助手只需要你给一个主题,它就能自己规划、派调研员、算数据、写报告、找编辑审稿,最后一篇中文调研简报就出来了。
二、先认识一下 DeepAgent 的三大核心能力
在进入代码之前,先搞清楚 DeepAgent 帮你兜住了哪些底。
1. 状态管理(State)
多 Agent 协作最大的坑是什么?状态不一致。
主 Agent 说"我已经调研完了",调研员子 Agent 其实还在写文件;分析师读到的是旧数据;编辑器拿到的是半成品草稿。DeepAgent 通过内建的状态管理把这层脏活接了过去,你只需要关心"每一步产出了什么文件"。
2. 循环路由与持久化执行
复杂 Agent 不是一条直线跑到底,而是"规划 → 执行 → 检查 → 修正"的循环。DeepAgent 帮你处理了循环的边界条件------什么时候该停、什么时候该重试、什么时候该回退。
3. 上下文压缩
子 Agent 之间传信息最怕什么?上下文爆炸 。三个调研员每个返回 5000 token,主 Agent 还没开始写就被淹死了。DeepAgent 的解法很聪明:Agent 之间通过文件传递信息,不依赖对话历史。
这一条看起来简单,但你会在后面代码里反复看到它的威力。
三、架构拆解:一个主 Agent + 三个子 Agent
我们的「深度调研助手」要处理这样一个任务:
调研国家统计局公开的 2023 年省级地区生产总值(GDP)数据:提取 GDP 总量前 6 名省份的具体数值及同比增速,计算六省 GDP 总和、各省占全国 GDP 的比重,并按增速从高到低排名。
这个任务天然适合分工:
| 角色 | 职责 | 关键工具 |
|---|---|---|
| 主 Agent(编排) | 规划流程、协调子 Agent、亲自起草报告 | write_todos、task、write_file |
| 调研员(researcher) | 联网搜集资料,写入 findings 文件 | web_search |
| 分析师(analyst) | 数值计算、排名、同比对比 | eval REPL(沙箱) |
| 编辑(editor) | 审阅草稿,返回修改建议 | 无(纯审阅) |
主 Agent 遵循的标准流程是:
规划 → 调研 → 分析 → 起草 → 审阅 → 定稿
关键设计原则有三条,值得你抄下来:
- 专业的事交给专业的 Agent:主 Agent 不亲自做联网搜索,调研员只负责一个子主题。
- 审阅与修订分离:编辑器只给意见,不直接改稿。这样主 Agent 能保持整体思路做针对性修改。
- 文件是唯一的共享状态 :所有 Agent 通过
/workspace/sources/和/workspace/reports/交换信息。
金句:多 Agent 协作的本质不是"让 AI 聊天",而是"让 AI 通过文件交接工作"。
四、核心代码走读
4.1 主 Agent 的创建:createDeepAgent
先看骨架代码 agent.mjs:
php
import { createDeepAgent, FilesystemBackend } from "deepagents"
import { ChatOpenAI } from "@langchain/openai"
const model = new ChatOpenAI({
model: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
temperature: 0,
})
export function createIntelligenceDeskAgent() {
const backend = new FilesystemBackend({
rootDir: projectDir,
virtualMode: true, // 虚拟文件系统,Agent 只能看到 /workspace
})
return createDeepAgent({
model,
systemPrompt: orchestratorPrompt,
subagents: [researchSubAgent, analystSubAgent, editorSubAgent],
memory: [path.join(projectDir, "AGENTS.md")], // 所有子 Agent 可读
backend,
skills: ["/skills/"] // 挂载技能目录
})
}
几个关键参数拆开讲:
backend:DeepAgent 的文件读写后端。virtualMode: true意味着 Agent 感知的是一个虚拟路径/workspace,实际映射到项目目录。这一层抽象非常重要------Agent 之间的所有协作都建立在文件之上。subagents:注册子 Agent。主 Agent 通过task工具按名字调用它们。memory:共享的长期记忆文件,所有子 Agent 都能读到,适合放全局规则。skills:技能目录,本质是给主 Agent 看的"写作指南"(后文细说)。
4.2 调研员子 Agent:约束比能力更重要
调研员是最容易失控的角色------LLM 天生的"探索欲"会让它不停搜索、反复写文件。看看这段 systemPrompt 是怎么给它套上缰绳的:
ini
const researchSubAgent = {
name: "research_sub_agent",
description: "通过联网搜索调研单一子主题。每次只分配一个子主题; 多个独立子主题可并行启动多个调研员。",
systemPrompt: dedent`
你是一名专业调研员,负责调研**一个**分配给你的子主题,并写入**一份**调研结果文件。
## 工作流程(严格遵守,禁止空转循环)
1. **可选**: 用 write_todos 列出最多3条中文执行步骤
2. 最多调用3次 web_search (硬性上限,绝不超过)
3. 将搜索结果整体为结构化摘要, 包含关键事实与来源URL。
4. 调用 write_file **一次**, 保存到任务指定的路径
5. 用一句话确认已完成,然后**立即停止**
## write_todos 使用规则 (若使用)
- 最多3条,每条 content 必须使用中文
- 最后一条 todo 必须是[写入 findings 文件]
...
`,
tools: [webSearch],
}
这套 Prompt 的写法值得你逐句品:
- "硬性上限,绝不超过" ------ 把数字限制写死,不要给 LLM 留模糊空间。
- "调用 write_file 一次" ------ 显式约束写文件次数,防止反复覆盖。
- "用一句话确认已完成,然后立即停止" ------ 给 Agent 一个明确的终止信号。
- "禁止空转循环" ------ 直接点出最常见的失败模式。
你会发现,约束一个好的子 Agent,比给它更多工具更重要。
4.3 分析师子 Agent:让 LLM 写代码,别让它算数
LLM 不擅长计算,这是常识。但很多团队还是会踩这个坑------让模型"心算"一堆 GDP 加总,结果错得离谱还自信满满。
正确的姿势是:LLM 擅长写代码,让代码去计算。
javascript
import { createCodeInterpreterMiddleware } from "@langchain/quickjs"
const analystSubAgent = {
name: "analyst",
description: "使用 eval REPL 进行数值计算与结构化数据分析。",
systemPrompt: dedent`
你是一名数据分析师,所有计算必须通过 eval REPL 完成 **禁止** 猜测数字。
1. 从 /workspace/sources/ 读取数据文件
2. 在 REPL 中编写并运行 JavaScript, 计算综合、均值、排名、增长率等
3. 将分析结果保存到 /workspace/sources/analysis_*.md
必须展示计算过程,结论可以从 REPL 输出复现。
`,
middleware: [createCodeInterpreterMiddleware()]
}
@langchain/quickjs 提供一个沙箱环境,Agent 在里面写 JS 并执行,不会影响主进程。这个设计非常关键:
- 安全性:即使 LLM 写出危险的代码,也跑不出沙箱。
- 可复现性:结论能从 REPL 输出推导出来,而不是模型"感觉"出来的。
- 零幻觉:数字是算出来的,不是编出来的。
4.4 编辑子 Agent:审阅与修订分离
编辑器最容易犯的错误是"顺手改稿"。看看这个 Prompt 是怎么堵死的:
csharp
const editorSubAgent = {
name: "editor",
description: "审阅报告草稿的准确性、结构与完整性。",
systemPrompt: dedent`
你是一名资深情报编辑,负责**审阅**报告草稿 **不要**亲自改写报告。
## 审阅要点
- 报告是否直接回答了原始问题?
- 章节结构是否清晰、段落是否充实(而非只有bullet 列表)
- 是否引用了来源,并在 【参考资料】 章节列出?
- 是否有遗漏、无依据的断言或缺失的视角?
## 输出
返回简洁的审阅意见和具体、可操作的修改建议。
**不要**写入报告文件,只提供反馈。
`
}
为什么要这么设计?
- 单一职责:编辑专注"挑刺",主 Agent 专注"改稿"。
- 保持整体思路:如果编辑器直接改稿,可能会打乱主 Agent 的叙事逻辑。
- 可追溯:修改建议是显式的,主 Agent 可以选择采纳或不采纳。
五、编排的核心:主 Agent 的 System Prompt
主 Agent 是整个系统的大脑,它的 systemPrompt 定义了流程规则、委派规则、文件约定三件事。原文很长,我提炼几个关键片段:
5.1 强制中文输出
markdown
## 语言要求
- **所有输出必须使用中文**:对话回复、write_todos 任务列表、文件内容、搜索关键词
- 搜索时优先使用中文关键词;英文专有名词(如 LangGraph、AutoGen)可保留
中文任务用中文搜索,命中率会高很多。这条规则看着小,实际影响很大。
5.2 明确区分"技能"和"子 Agent"
markdown
## task 工具(子 Agent 委派)
**仅**以下 subagent_type 合法:researcher、analyst、editor、general-purpose。
- web-research、report-writer 是**技能**(写作指南),**不是**子 Agent,禁止作为 subagent_type 调用
- 报告起草、修订、定稿由**主 Agent 自己**用 write_file / edit_file 完成,不要委派 task
这是踩坑之后的血泪教训------LLM 看到"web-research"这种名字,很容易把它当成子 Agent 去 task 调用。必须在 Prompt 里显式禁止。
5.3 委派的硬性上限
markdown
## 委派规则
- 每个调研员只负责一个聚焦的子主题
- **每份报告最多 3 个调研员**------只选最相关的子主题
- 最多并行启动 3 个调研员,已有 3 份 findings 文件后不再新增调研员
- 每份报告只调用编辑一次(草稿完成后)
为什么是 3?这是一个经验值:
- 太少覆盖不全,太多协调成本爆炸;
- 并行超过 3 个,主 Agent 的上下文会被 findings 文件淹没;
- 编辑只调一次,防止"改来改去没完没了"。
六、Skills:给主 Agent 的写作指南
DeepAgent 的 skills 目录挺有意思,它不是可执行代码,而是给主 Agent 看的写作手册 。比如 report-writer/SKILL.MD:
yaml
---
name: report-writer
description: 将调研结果整理为结构清晰、专业的中文情报报告
---
# 报告撰写技能
> **注意**: 本技能是主Agent的写作指南,不是子Agent。请主 Agent 亲自用`write_file`
撰写报告,**不要**通过 `task` 工具委派`report-writer`。
## 报告结构
1. **标题** - `# [主题]: 情报简报`
2. **执行摘要** - 3-5 条核心要点
3. **背景** - 主题背景与当前重要性
4. **核心发现** - 按主题组织,而非按来源堆砌
5. **结论** - 直接回答原始问题
6. **参考资料** - 编号列表
这种设计把"写作规范"从 systemPrompt 里剥离出来,单独放文件,好处是:
- 主 Prompt 更清爽:只放流程,不放细节。
- 可替换 :换一个
report-writer就是另一种报告风格。 - 可组合 :
web-research+report-writer就是一套"调研 + 写作"流程。
金句:Prompt 是瞬时的,Skill 是资产化的。把可复用的写作规范沉淀成文件,才是生产级 Agent 的做法。
七、流式输出与工具追踪(cli.mjs 片段)
cli.mjs 负责把 Agent 的执行过程"可视化"给开发者,核心是这段:
csharp
for await (const [namespace, chunk] of await agent.stream(
{ messages: [new HumanMessage(query)] },
{ streamMode: "updates", subgraphs: true, recursionLimit }
)) {
// ...
}
三个参数值得注意:
streamMode: "updates":每完成一步就推送一次更新,而不是等全部跑完。subgraphs: true:把子 Agent 的执行过程也暴露出来------你能看到"调研员 1 搜索了关键词 A"。recursionLimit: 300:防止 Agent 陷入无限循环,超限直接中止。
配合 FILE_TOOLS 和 EVAL_TOOL 集合,CLI 可以针对不同类型的工具调用给出不同的展示格式:
ini
const FILE_TOOLS = new Set([
"write_file", "edit_file", "read_file",
"ls", "glob", "grep"
])
const EVAL_TOOL = "eval"
这个模式特别适合调试------当你看到 Agent 反复调用同一个工具时,就知道它的 Prompt 需要加强了。
八、三个关键踩坑点
坑 1:子 Agent 无限循环
症状:调研员不停搜索,同一个关键词搜三遍。
解法:在 systemPrompt 里写死上限,并给一个"停止信号":
markdown
2. 最多调用3次 web_search (硬性上限,绝不超过)
5. 用一句话确认已完成,然后**立即停止**,不要再搜索、写文件或更新 todo
坑 2:技能被当成子 Agent
症状 :主 Agent 尝试 task(subagent_type="web-research"),报错找不到。
解法:在 systemPrompt 里显式禁止,同时给"技能"和"子 Agent"起不同风格的名字(技能用 kebab-case,子 Agent 用名词)。
坑 3:上下文爆炸
症状:3 个调研员各返回 5000 token,主 Agent 还没开始写就超限了。
解法 :Agent 之间通过文件传递信息,不依赖对话历史 。子 Agent 只返回一句"已完成",具体内容写进 findings_*.md,主 Agent 按需读取。
九、总结:DeepAgent 到底帮你省了什么
| 能力 | 自己实现 | DeepAgent |
|---|---|---|
| 状态管理 | 手写 State Graph | 内建 |
| 子 Agent 调度 | 手动 orchestrate | task 工具 |
| 文件系统 | 自己封装 | FilesystemBackend |
| 上下文压缩 | 手动裁剪 | 内建 |
| 技能资产化 | 没有 | skills 目录 |
| 沙箱计算 | 自己搭 | quickjs 中间件 |
回到最初那句话 :DeepAgent 不是替代 LangChain/LangGraph,而是让你跳过重复的底层基建,直接聚焦 Agent 的业务逻辑与能力迭代。