做一个"会联网搜索的 Agent"并不难。给模型挂一个 web_search 工具,让它查几条网页,再把搜索摘要拼成回答,就能跑出一个看起来不错的 demo。
但只要把任务换成稍微严肃一点的深度调研,问题马上就暴露出来:模型会重复搜索,资料没有归档,数据计算靠猜,报告引用不完整,长上下文越积越乱,最后你很难判断哪一步出了问题。更麻烦的是,单个 Agent 一边规划、一边搜索、一边算数、一边写报告、一边审稿,角色混在一起,输出很容易变成"搜索结果改写版",而不是一份能交付的调研简报。
这篇文章的核心结论是:
createDeepAgent 的价值不只是少写几段 middleware 配置,而是把深度调研类 Agent 需要的规划、文件工作区、Skill、长期记忆、子 Agent、代码执行和上下文压缩组织成一个可运行的工程架构。真正的关键不在 API 有多短,而在你是否把角色边界、工具权限、数据流和验收机制设计清楚。
为什么深度调研不能只靠一个 Agent
先看一个典型需求:
调研国家统计局公开的 2023 年省级 GDP 数据:提取 GDP 总量前 6 名省份的具体数值及同比增速,计算六省 GDP 总和、各省占全国 GDP 的比重,并按增速从高到低排名。
这个任务表面上像一次搜索问答,实际至少包含 6 类动作:
- 明确问题和输出口径。
- 查找官方来源、媒体汇总、专业数据库等多类资料。
- 将资料归档到可复查的文件。
- 提取数字并做计算。
- 起草报告并列出来源。
- 审阅报告,修正遗漏、结构和引用问题。
如果全部交给一个 Agent,它会面临几个天然困难。
第一,搜索和写作目标冲突。搜索阶段应该尽量发散,写作阶段则要收敛成结构化结论。一个上下文里同时做这两件事,模型容易边搜边写,导致资料未充分沉淀就开始下结论。
第二,计算不能靠自然语言推理。GDP 总和、占比、增速排名都应该由可复现代码算出来。让模型"心算"会带来不可控错误,尤其是小数、百分比和排名。
第三,报告质量需要审稿角色。写作者通常会维护自己的叙事结构,但审稿需要从外部视角检查"有没有回答问题、来源是否充分、是否有无依据断言"。这两个角色最好分开。
第四,长任务需要过程状态。没有 todo、文件归档和流式事件,你只能看到最后答案,很难知道 Agent 搜了什么、写了什么、哪里卡住了。
所以,深度调研助手的默认架构不应该是"一个全能 Agent + 一个搜索工具",而应该是"主 Agent 编排 + 专业子 Agent 执行 + 文件系统传递状态 + Skill 约束流程 + 可观测事件跟踪"。
createDeepAgent 在系统里的位置
如果你已经用过 LangChain 和 LangGraph,可以这样理解三者关系:
- LangChain 提供模型、消息、工具、middleware 等基础能力。
- LangGraph 提供状态图、循环、持久化和流式执行能力。
- DeepAgents 在上层提供一套偏长任务的 Agent harness,把常见运行时设施预装起来。
createDeepAgent 是 DeepAgents 中更高阶的入口。相比自己手动组合 createAgent、createFilesystemMiddleware、createSkillsMiddleware、createSubAgentMiddleware、createMemoryMiddleware、createSummarizationMiddleware,它把一批默认中间件和提示词组织好了。你主要配置模型、后端、长期记忆、Skill 目录、子 Agent 和自定义系统提示词。
当前 demo 的核心创建函数是 createIntelligenceDeskAgent(),它的职责很清楚:把一个"深度调研助手"的运行环境组装出来。
js
export function createIntelligenceDeskAgent() {
const apiKey = process.env.OPENAI_API_KEY?.trim()
if (!apiKey) {
throw new Error("未设置 OPENAI_API_KEY 环境变量")
}
const model =
process.env.MODEL_NAME?.trim() ||
process.env.OPENAI_MODEL?.trim() ||
"gpt-4o"
const baseURL = process.env.OPENAI_BASE_URL?.trim() || undefined
const backend = new FilesystemBackend({
rootDir: projectDir,
virtualMode: true,
})
const chatModel = new ChatOpenAI({
model,
temperature: 0,
apiKey,
...(baseURL
? { configuration: { baseURL } }
: {}),
})
return createDeepAgent({
model: chatModel,
systemPrompt: orchestratorPrompt,
backend,
memory: [path.join(projectDir, "AGENTS.md")],
skills: ["/skills/"],
subagents: [researcherSubAgent, editorSubAgent, analystSubAgent],
})
}
这段代码在整个链路里的位置是"装配层"。它没有写具体搜索逻辑,也没有写报告内容,而是决定 Agent 运行时有哪些能力:
ChatOpenAI负责模型调用,支持通过OPENAI_BASE_URL接入兼容 OpenAI 协议的服务。FilesystemBackend负责文件系统后端,virtualMode: true让 Agent 看到的是虚拟路径。memory按源码配置加载AGENTS.md作为长期记忆,提供报告偏好和工作区约定。skills指向/skills/,让 Agent 按需读取web-research和report-writer。subagents注册researcher、analyst、editor三个专业子 Agent。
如果换成手写 middleware,也能实现同样能力,但你要自己处理默认提示词、工具组合和子 Agent 接入。createDeepAgent 的工程价值,是把这些长任务 Agent 常见结构变成默认框架,让你把精力放在"角色和流程设计"上。
整体架构:主 Agent 管流程,子 Agent 管专业动作
这个 demo 的设计不是让每个 Agent 都全能,而是明确分工。
主 Agent 是编排中心。它负责理解用户问题、写 todo、保存原始问题、制定调研计划、委派子 Agent、读取结果文件、起草报告、调用编辑审稿、修订并保存最终报告。主 Agent 不应该亲自包揽所有搜索和计算,否则多 Agent 架构就退化成"一个 Agent 自言自语"。
researcher 是调研员。每次只负责一个聚焦子主题,最多搜索有限次数,然后把结果写入 findings_*.md。它的价值不是"会搜索",而是把搜索过程约束成可归档的事实收集。
analyst 是数据分析师。只在涉及数字、排名、增长率、表格处理时启用。它必须通过 QuickJS 的 eval 工具完成计算,并把计算逻辑写入 analysis_*.md。这解决的是"结论可复现"的问题。
editor 是编辑。它不直接改写报告,只检查准确性、结构、引用和语言,并返回修改建议。这个设计很重要:审阅和修订分离,主 Agent 仍然负责最终文档的一致性。
Skill 则不是子 Agent。web-research 和 report-writer 是流程指南。它们告诉主 Agent 如何做联网调研、如何组织报告,但不能被当成 subagent_type 调用。demo 的系统提示词专门强调这一点,因为模型很容易把"技能名称"误当成"子 Agent 名称"。
数据流:Agent 之间靠文件协作,而不是靠记忆猜
这个项目最值得借鉴的一点,是它没有让子 Agent 的输出只停留在聊天历史里,而是用工作区文件承接中间状态。
为什么要用文件,而不是直接把子 Agent 结果塞回主上下文?
第一,文件是可复查的。你可以打开 workspace/sources/research_plan.md 看计划是否合理,打开 findings_*.md 看资料是否支撑结论,打开 analysis_*.md 看计算是否可复现。
第二,文件减少上下文污染。每个子 Agent 完成自己的文件,主 Agent 需要时再读取,不必把所有搜索过程永久塞在消息历史里。
第三,文件让失败可恢复。如果报告写到一半因为 recursionLimit 停了,已有的计划、发现和分析结果仍然在工作区,下一次可以接着查。
本地工作区里已经有一次 GDP 调研产物:
workspace/sources/research_plan.md:调研计划。workspace/sources/findings_nbs_official.md:官方数据源调研。workspace/sources/findings_economic_databases.md:专业经济数据库调研。workspace/sources/analysis_results.md:GDP 前 6 省份的计算结果。workspace/reports/draft_gdp_2023.md:草稿。workspace/reports/report_gdp_2023_20240527.md:最终报告。
这些文件说明 demo 不是只有控制台输出,而是形成了一个可审计的调研工作区。
主 Agent:不要把所有规则塞给模型,而是定义清楚流程边界
主 Agent 的系统提示词是整个架构的"流程合同"。它规定语言、职责、标准流程、委派规则、文件约定和完成时反馈。
下面是经过压缩后的关键结构:
js
const orchestratorPrompt = dedent`
你是「深度调研助手」的主 Agent,负责协调调研、分析与编辑。
## 标准流程
1. 规划:用 write_todos 拆解任务,并保存用户问题
2. 调研:按 web-research 技能写 research_plan.md,委派 researcher
3. 分析:若涉及数字对比或数据表,委派 analyst
4. 起草:由你亲自按 report-writer 技能写 draft_*.md
5. 审阅:委派 editor 审稿,根据反馈修订一次
6. 定稿:保存最终报告到 report_*_[日期].md
## task 工具
仅 researcher、analyst、editor、general-purpose 是合法 subagent_type。
web-research、report-writer 是技能,不是子 Agent。
## 委派规则
每份报告最多 3 个调研员。
每份报告只调用编辑一次。
调研完成后进入起草、审阅、定稿,不要额外开调研轮次。
`
这里的重点不是提示词写得长,而是流程上有"停止条件"。例如每份报告最多 3 个调研员、编辑只调用一次、调研完成后不要额外开调研轮次。这些约束能减少 Agent 在搜索阶段空转。
dedent 的作用是让你可以保持代码缩进,同时去掉模板字符串里多余的前导空格。它不是核心能力,但对大型 Prompt 很有用:Prompt 可读,实际传给模型的文本也干净。
如果换一种写法,把这些规则分散到用户 Prompt 或 README 里,模型每次不一定稳定遵守。系统提示词集中定义主流程,更适合需要重复运行的 Agent 应用。
researcher:搜索工具要有硬上限和写入约束
联网搜索工具本身很简单,当前 demo 用 Bocha API 封装了一个 web_search:
js
export const webSearch = tool(
async input => {
const count = input.count ?? 10
console.log(` 🔎 搜索: ${input.query}(${count} 条)`)
return bochaWebSearch(input.query, count)
},
{
name: "web_search",
description:
"使用 Bocha 联网搜索 API 检索互联网网页。返回标题、URL、摘要、网站名称、图标和发布时间。",
schema: z.object({
query: z.string().min(1).describe("搜索关键词,优先使用中文"),
count: z.number().int().min(1).max(20).optional(),
}),
}
)
这里的 zod schema 很关键。query 必须非空,count 限制在 1 到 20。这个约束不是为了类型好看,而是为了让模型的工具调用空间可控。没有 schema,模型可能传空字符串、传过大的数量,或者传一个不符合 API 的对象。
真正决定搜索质量的,是 researcherSubAgent 的 Prompt:
js
const researcherSubAgent = {
name: "researcher",
description:
"通过联网搜索调研单一子主题。每次只分配一个子主题;多个独立子主题可并行启动多个调研员。",
systemPrompt: dedent`
1. 可选:用 write_todos 列出最多 3 条中文执行步骤
2. 最多调用 3 次 web_search
3. 将搜索结果整理为结构化摘要,包含关键事实与来源 URL
4. 调用 write_file 一次,保存到 /workspace/sources/findings_*.md
5. 用一句话确认已完成,然后立即停止
`,
tools: [webSearch],
}
注意这里有两个上限:最多 3 条 todo,最多 3 次搜索。原文中 Skill 还提到最多 10 次搜索,但当前源码的 researcher Prompt 使用的是更严格的 3 次,这是写博客时必须以源码为准的地方。
为什么要限制搜索次数?因为联网搜索是最容易空转的工具。模型觉得"还可以再搜一个关键词",就会不断扩展主题。深度调研不是搜索越多越好,而是每一轮搜索都要服务于明确子主题,并产出可用文件。
工程上,researcher 的输出应至少包含:
- 子主题范围。
- 关键事实。
- 来源 URL。
- 信息冲突或不确定点。
- 与主问题的关系。
如果搜索 API 返回的是摘要而不是原文全文,报告里就要标注信息局限。搜索结果是线索,不是最终事实。
analyst:让模型写代码,让解释器负责计算
大模型擅长解释和组织语言,不擅长稳定精确计算。GDP 总和和占比这种任务,必须交给可执行代码。
demo 里的 analyst 使用了 @langchain/quickjs:
js
const analystSubAgent = {
name: "analyst",
description:
"使用 eval REPL 进行数值计算与结构化数据分析。适用于计算、排名、同比对比或 JSON/CSV 分析。",
systemPrompt: dedent`
你是一名数据分析师,所有计算必须通过 eval REPL 完成,禁止猜测数字。
1. 从 /workspace/sources/ 读取数据文件
2. 在 REPL 中编写并运行 JavaScript
3. 将分析结果保存到 /workspace/sources/analysis_*.md
`,
middleware: [createCodeInterpreterMiddleware()],
}
这段设计有一个非常明确的工程判断:计算能力不应该混在自然语言推理里,而应该变成工具调用。
QuickJS 的优势是轻量、嵌入方便,适合运行 JavaScript 片段做局部计算。对于这个 demo,它足够处理 GDP 总和、占比、排序等任务。如果换成更复杂的数据分析,比如读取大型 CSV、做统计建模、画图,可能更适合 Python 沙箱或专门的数据处理服务。
本地 analysis_results.md 中已经有可复查结果:
md
## 基础数据
- 全国 GDP 总量:1260583 亿元
- 广东:135673.2 亿元,增速 4.8%
- 江苏:128204.7 亿元,增速 5.8%
## 计算结果
### 六省 GDP 总和
559978.1 亿元
### 各省占全国 GDP 比重
- 广东:10.76%
- 江苏:10.17%
这里仍然有一个改进点:报告说明了计算结果,但更理想的分析文件应该同时保留实际运行的 JavaScript 代码。这样审阅者不仅能看到结果,还能复现公式。写技术博客时要点出这一点:eval 工具解决了可执行性,但"保存计算代码"仍然要靠 Prompt 或额外规范约束。
editor:审稿不改稿,避免责任混乱
很多多 Agent 设计会让编辑子 Agent 直接改写报告,看似省事,实际上容易引入两个问题:一是编辑会改变主报告叙事结构,二是主 Agent 无法很好地解释最终修改来自哪里。
demo 的设计更稳妥:editor 只审阅,不写文件。
js
const editorSubAgent = {
name: "editor",
description:
"审阅报告草稿的准确性、结构与完整性。在 /workspace/reports/draft_*.md 写好后使用。",
systemPrompt: dedent`
你是一名资深情报编辑,负责审阅报告草稿,不要亲自改写报告。
审阅要点:
- 是否直接回答原始问题?
- 章节结构是否清晰?
- 是否引用来源并列出参考资料?
- 是否有遗漏、无依据断言或缺失视角?
- 语言是否为中文,表述是否专业?
返回具体、可操作的修改建议。
`,
}
这个模式适合大多数文档生成场景:生成者负责写,审阅者负责指出问题,最终修订仍由主 Agent 统一执行。它牺牲了一点自动化程度,但换来更清晰的责任边界。
如果在生产系统里进一步演进,可以把 editor 的输出结构化,比如:
severity:critical、major、minor。section:对应报告章节。issue:问题描述。suggestion:修改建议。
当前 demo 没做结构化输出,足够教学,但如果要做稳定产品,编辑反馈最好变成 schema,方便主 Agent 逐项修订。
Skill:流程知识不等于子 Agent
项目有两个 Skill:
skills/web-research/SKILL.mdskills/report-writer/SKILL.md
web-research 规定如何做联网调研:先保存问题,再写 research_plan.md,再委派 researcher,最后综合 findings。
report-writer 规定如何写报告:标题、执行摘要、背景、核心发现、分析、结论、参考资料,以及文件命名规范。
Skill 的作用是把流程知识从主 Prompt 中拆出去,做到"按需加载"。模型启动时看到 Skill 名称和描述,真正需要时再读取完整 SKILL.md。这比把所有写作规范和调研流程塞进主系统提示词更可维护。
这里最常见的错误,是把 Skill 当成子 Agent。当前系统提示词明确写了:
web-research、report-writer是技能,不是子 Agent,禁止作为subagent_type调用。
这句话非常实用。Agent 运行时经常会把所有"看起来像能力名称"的东西都当作可调用对象。如果不说清楚,模型可能会调用一个不存在的 report-writer 子 Agent,然后流程失败。
工程上,我建议把 Skill 当作"可复用工作手册",把子 Agent 当作"有独立上下文和工具的执行角色"。两者可以协作,但不要混淆。
Todo:复杂任务需要显式进度状态
createDeepAgent 内置了 todo 规划能力,底层来自 LangChain 的 todoListMiddleware。项目里还有一个独立示例 src/todo-middleware-test.mjs,用普通 createAgent 验证 todo 能力:
js
const agent = createAgent({
model,
tools: [],
systemPrompt:
"你是生活规划助手。收到需要多步完成的请求时,先用 write_todos 列出中文执行步骤,然后简要说明你的计划。",
middleware: [todoListMiddleware()],
})
const result = await agent.invoke({
messages: [new HumanMessage(query)],
})
console.log("todos:", JSON.stringify(result.todos, null, 2))
Todo 的价值不是"让输出更像项目管理",而是给长任务一个可更新的状态结构。对于深度调研,主 Agent 的 todo 可以覆盖规划、调研、分析、起草、审阅、定稿;researcher 子 Agent 的 todo 则只覆盖本子主题的搜索和写入。
有两个实践建议。
第一,todo 要分层。主 Agent 的 todo 不应该被子 Agent 重复;子 Agent 只管理自己的小任务。
第二,todo 要有停止条件。比如 researcher 写完 findings 后必须停止,避免继续搜索;editor 审阅后只返回反馈,不进入改写。
没有 todo 的 Agent 容易"走一步想一步"。有 todo 但没有停止条件的 Agent,又容易把 todo 当作不断扩展任务的理由。
CLI:可观测性不是锦上添花
src/cli.mjs 不只是简单调用 Agent,它做了一个重要工作:把流式更新、子图执行、工具调用和输出文件列出来。
js
for await (const [namespace, chunk] of await agent.stream(
{ messages: [new HumanMessage(query)] },
{ streamMode: "updates", subgraphs: true, recursionLimit }
)) {
for (const [node, data] of Object.entries(chunk)) {
if (node === "model_request") {
trackFileCalls(data, pending)
trackEvalCalls(data, pendingEval)
console.log(stepLabel(namespace, node))
} else if (node === "tools") {
logToolResults(data, pending, pendingEval)
} else if (node === "todoListMiddleware.after_model") {
console.log(stepLabel(namespace, node))
}
}
}
这段代码的作用是观察运行过程:
streamMode: "updates"让你看到每轮状态更新。subgraphs: true让子 Agent 的执行也能出现在流里。recursionLimit控制最大循环步数,避免无限调用工具。trackFileCalls记录读写文件工具。trackEvalCalls记录 QuickJS 代码执行。logToolResults打印工具结果预览。
如果没有这层可观测性,深度调研 Agent 出错时很难排查。你不知道它有没有写计划、有没有调用 researcher、有没有执行 eval、写了哪些文件、是不是卡在递归限制。
这也是为什么 LangSmith 这类链路追踪工具很有价值。控制台日志适合本地 demo,生产环境需要更完整的 trace、工具调用过滤、失败原因、耗时和 token 统计。
上下文压缩:依赖模型 profile,也要理解默认值
长任务一定会遇到上下文膨胀。DeepAgents 内置 summarization 逻辑,会根据模型上下文信息决定何时压缩历史。当前 demo 里有 src/max-input-tokens-test.mjs,用于说明如何覆盖模型 profile:
js
const model = new ChatOpenAI({
model: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
})
console.log(model.profile.maxInputTokens)
Object.defineProperty(model, "profile", {
get: () => ({ maxInputTokens: 1_024 }),
})
console.log(model.profile.maxInputTokens)
为什么要改 profile.maxInputTokens?因为有些兼容 OpenAI 协议的模型服务,不一定能提供准确的模型上下文 profile。DeepAgents 的默认摘要策略会参考模型上下文窗口;如果 profile 缺失,fraction 类型阈值就可能无法按预期工作。
本地类型声明显示,DeepAgents 的摘要默认计算逻辑会根据模型 profile 选择基于比例的设置;如果没有 maxInputTokens,会退回固定 token 或消息数量策略。LangChain 的 summarization middleware 内部也会通过 model.profile.maxInputTokens 或模型名查上下文大小。
这里要注意一个边界:上下文压缩不是长期记忆,也不是事实库。它只是把旧对话变成摘要,帮助后续继续执行。需要精确引用的数据、来源、计算代码,应该写入文件,而不是只留在摘要里。
参数和配置:每个值都对应一个工程取舍
这个项目的参数不多,但每个都影响系统行为。
| 配置 | 位置 | 工程含义 |
|---|---|---|
OPENAI_API_KEY |
.env |
模型服务认证。缺失时 createIntelligenceDeskAgent() 直接抛错。 |
OPENAI_BASE_URL |
.env |
接入兼容 OpenAI 协议的模型服务,例如 DashScope compatible mode。 |
MODEL_NAME / OPENAI_MODEL |
.env |
当前源码优先读取 MODEL_NAME,再读 OPENAI_MODEL,最后默认 gpt-4o。 |
BOCHA_API_KEY |
.env |
联网搜索 API Key。缺失时 web_search 返回可读错误,不直接抛异常。 |
RECURSION_LIMIT |
.env / cli.mjs |
控制 Agent 最大递归步数,默认 300。长任务可调高,但不应无限。 |
virtualMode |
FilesystemBackend |
将项目目录映射成虚拟根路径,减少真实路径暴露和路径穿透风险。 |
memory |
createDeepAgent |
源码配置加载 AGENTS.md 长期记忆,注入报告偏好和工作区约定。当前目录里实际文件名是 AGENT.md,运行前应统一文件名或修改配置。 |
skills |
createDeepAgent |
加载 Skill 目录,支持按需读取流程指南。 |
subagents |
createDeepAgent |
注册专业子 Agent,是多 Agent 分工的核心接口。 |
count |
web_search schema |
控制搜索返回数量,默认 10,最大 20。 |
profile.maxInputTokens |
模型 profile | 影响上下文压缩触发阈值。兼容模型缺失 profile 时需要显式补。 |
一个容易忽略的问题是:当前 FilesystemBackend 没有配置 permissions。virtualMode 能限制虚拟路径映射到 rootDir 内,但它不是业务权限策略。生产场景如果只允许写 /workspace/reports/** 和 /workspace/sources/**,应该显式配置 permissions,而不是单靠 Prompt 说"禁止写其他目录"。
方案对比:什么时候用 createDeepAgent,什么时候不用
可以把 Agent 方案分成三档。
| 方案 | 适合场景 | 优点 | 代价 |
|---|---|---|---|
createAgent + tools |
单轮问答、简单工具调用 | 简单、可控、启动快 | 长任务能力要自己补 |
createAgent + middleware |
需要自定义状态、todo、guardrail、工具包装 | 组合灵活,适合渐进增强 | 要自己设计 middleware 栈 |
createDeepAgent |
深度调研、代码助手、报告生成、多步骤任务 | 内置规划、文件系统、Skill、子 Agent、摘要 | 需要理解默认行为和边界 |
| 原生 LangGraph | 强流程、审批、事务、复杂状态机 | 控制力最强,可审计性最好 | 开发成本最高 |
这篇文章里的深度调研助手,默认适合 createDeepAgent。原因是它天然需要文件状态、Skill、子 Agent、todo 和上下文压缩。如果从 createAgent 开始,会花大量代码搭运行时基建;如果直接用原生 LangGraph,又会在一开始承担过重的图编排成本。
但 createDeepAgent 也不是万能。如果你的任务是严格业务流程,例如贷款审批、订单支付、医疗建议审核,不应该把关键路径完全交给 Agent 自主规划。更稳妥的做法是用 LangGraph 或业务工作流控制状态,把 Agent 放在某些分析和生成节点里。
当前 demo 的真实效果与可改进点
本地工作区已经生成了一份 2023 年省级 GDP 前 6 名省份调研简报,最终报告包含 GDP 总量、同比增速、六省总和、占全国比重和增速排名。这说明整个链路已经跑通:计划、调研、分析、起草、定稿都有文件产物。
不过,从工程质量看,还有几个可以继续加强的点。
第一,调研文件的来源深度不足。findings_nbs_official.md 更像国家统计局数据平台介绍,没有直接摘出 GDP 前 6 省份原始表格。正式调研应该要求 researcher 必须产出与主问题直接相关的数据来源,而不是泛泛介绍数据平台。
第二,专业数据库调研文件偏百科化。findings_economic_databases.md 罗列 CSMAR、Wind、CEIC 等数据库,但没有直接支撑本次 GDP 数值。对报告有帮助的 findings,应该围绕主问题,不应变成背景资料堆砌。
第三,分析结果缺少可执行代码。analysis_results.md 有计算结果,但没有保留 JavaScript 计算片段。更好的 analyst 规范是"写入输入数据、计算代码、输出结果、解释",这样才能真正可复现。
第四,最终报告参考资料不够完整。报告提到了国家统计局和各省统计公报,但没有完整 URL 列表。report-writer Skill 已要求参考资料章节,后续应强化编辑检查。
第五,长期记忆文件名存在不一致。src/agent.mjs 中配置的是 memory: [path.join(projectDir, "AGENTS.md")],但当前项目目录里实际文件名是 AGENT.md。如果直接运行,长期记忆可能不会按预期加载。工程里应该统一为 AGENTS.md,或者把代码里的路径改成真实文件名。
这些问题不否定 demo 的价值,反而说明真实 Agent 工程不能停留在"跑通链路"。你需要用产物反查 Prompt、Skill 和子 Agent 的约束是否足够强。
常见误区
误区一:以为多 Agent 就等于质量更高。
多 Agent 只是提供角色隔离。如果 researcher 搜不到关键资料,analyst 没有可靠输入,editor 没有结构化审阅标准,多 Agent 只会制造更多中间文本。
误区二:把 Skill 当成工具或子 Agent。
Skill 是按需加载的流程说明,不是可执行工具,也不是 subagent_type。执行动作仍然要靠主 Agent、子 Agent 和工具。
误区三:让模型自己算数。
只要涉及排名、占比、同比、总和,就应该通过代码解释器或确定性工具完成。自然语言解释可以由模型写,数字结果应由代码算。
误区四:只看最终报告,不看中间文件。
深度调研的质量取决于资料链路。research_plan.md、findings_*.md、analysis_*.md 比最终答案更能暴露问题。
误区五:把虚拟文件系统当成完整安全边界。
virtualMode 很有用,但生产环境仍然要配置 permissions、执行沙箱、密钥隔离和审计日志。
误区六:上下文摘要等于长期记忆。
摘要是当前会话的压缩,不保证细节无损;长期记忆是稳定约定;事实数据应该写入文件或数据库。
误区七:搜索结果摘要就是事实。
搜索 API 返回的摘要只是线索。重要结论要优先引用官方来源,至少用多个独立来源交叉验证,并标注时间。
工程建议:把深度调研助手做成可验收系统
如果要把这个 demo 继续演进成可长期使用的调研助手,我会按下面顺序推进。
第一,先强化输出验收。给最终报告定义固定验收项:是否回答原问题、是否有执行摘要、是否有参考资料 URL、是否列出局限、是否有计算过程。不要只看语言是否流畅。
第二,强化 researcher 产物格式。要求每个 findings_*.md 至少包含"结论、证据、来源 URL、可信度、与主问题关系、信息缺口"。泛泛背景资料不算完成。
第三,强化 analyst 可复现性。analysis_*.md 必须包含输入数据表、实际 JS 代码、输出结果和解释。必要时把输入数据保存成 JSON 或 CSV。
第四,引入权限配置。限制 Agent 只能读写 /workspace/sources/**、/workspace/reports/**、/skills/**、/AGENTS.md,避免误写项目其他文件。
第五,结构化 editor 反馈。让 editor 输出 JSON 或固定 Markdown 表格,主 Agent 根据问题逐项修订,减少"泛泛建议"。
第六,接入可观测平台。控制台日志足够演示,但生产环境需要 trace:每次搜索关键词、工具耗时、模型调用、子 Agent 返回、文件变更、摘要触发都应该可查。
第七,明确人工介入点。对高风险调研主题,比如政策、金融、医疗、法律,最终报告应该进入人工审核,而不是自动发布。
总结:createDeepAgent 是起点,不是终点
DeepAgents 的 createDeepAgent 让我们用很少的装配代码,就搭出一个具备规划、文件工作区、Skill、长期记忆、子 Agent、代码执行和上下文压缩能力的深度调研助手。这确实比从零组合 middleware 更快,也更适合长任务 Agent 的第一版落地。
但真正决定系统质量的,不是 createDeepAgent 这一行 API,而是你如何设计运行时边界:主 Agent 管流程,researcher 管资料,analyst 管计算,editor 管审阅,Skill 管流程知识,文件系统管状态传递,CLI 和 LangSmith 管可观测性。
这也是本文的核心结论:深度调研助手不是一个"会搜索的聊天机器人",而是一套多角色协作的调研工作流。createDeepAgent 帮你搭好了基础运行时,但工程上仍然要补上角色边界、证据链、权限、安全和验收标准。
如果只是做 demo,你可以让 Agent 搜完直接回答;如果要做可发布、可复查、可迭代的调研系统,就应该让每一步都有产物、每个数字能复现、每个结论有来源、每个角色有边界。DeepAgents 的价值,正是在这条从"能跑"到"可控"的路上,减少了大量重复基础设施工作。