多Agent深度调研工程实战:DeepAgents可控工作流
摘要
说实话,搭建一个支持联网搜索的 Agent 本身门槛不高。给大模型挂上一个 web_search 工具,
说实话,搭建一个支持联网搜索的 Agent 本身门槛不高。给大模型挂上一个 web_search 工具,让它检索几条网页,再把返回的摘要拼凑成回答——这一套操作下来,确实能跑出一个像模像样的 Demo。

可一旦任务换成正经的“深度调研”,所有短板都会暴露。模型会反复搜索同一个关键词,搜到的资料没人整理归档,遇到数据计算全靠瞎猜,写出来的报告引用七零八落。更要命的是,随着上下文越堆越长,到最后连模型自己都搞不清哪一步出了岔子。更棘手的是,单个 Agent 要身兼数职——规划、搜索、算数、写报告、审稿全包——结果输出活像一篇“搜索结果改写版”,根本不可能当成可直接交付的调研简报。
先把本文的核心结论亮出来:
createDeepAgent 的价值,远不止帮你少写几行 middleware 配置。它把深度调研 Agent 所需的规划能力、文件工作区、Skill、长期记忆、子 Agent、代码执行和上下文压缩,全都整合成一个可运行的工程框架。真正的关键不在于 API 有多短,而在于你有没有把角色边界、工具权限、数据流和验收机制设计清楚。
为什么深度调研不能只靠一个 Agent
先看一个典型的任务需求。
表面上这只是个搜索问答,实际至少包含 6 类动作:
- 明确问题本身和输出口径。
- 从官方来源、媒体汇总、专业数据库这几个渠道搜集资料。
- 把资料归档到可随时复查的文件里。
- 提取关键数字并进行计算。
- 起草报告,并列出所有来源。
- 审阅报告,修正遗漏、结构问题和引用错误。
如果全交给同一个 Agent,它会遇到几个内生麻烦。
第一,搜索和写作的目标天然冲突。搜资料时需要发散,把网撒开;写报告时却要收敛,形成结构化结论。在同一个上下文里同时干两件事,模型很容易一边搜一边写,资料还没沉淀够,结论就仓促下了。
第二,计算这件事不能交给自然语言推理。GDP 加总、算占比、排增速,都应该由可复现的代码执行。靠模型“心算”,小数、百分比、排名这些细节随时出错,结果不可控。
第三,报告质量需要一个独立的审稿角色。写作者总会下意识维护自己的叙事逻辑,但审稿需要从外部视角审视:“到底有没有回答用户问题?来源够不够充分?有没有无依据断言?”这两个角色最好分开。
第四,长任务需要一个过程状态。没有 todo、文件归档和流式事件,你只能看到一个答案,根本不知道 Agent 搜过什么、写了什么、卡在了哪一步。
因此,一个正经的深度调研助手,默认架构不应该是“全能 Agent + 搜索工具”的短平快组合,而应该是“主 Agent 做编排 + 专业子 Agent 负责执行 + 文件系统传递状态 + Skill 约束流程 + 可观测事件做跟踪”。
createDeepAgent 在系统里的位置
如果你用过 LangChain 和 LangGraph,那这三者之间的关系可以这么理解:
- LangChain 提供模型、消息、工具、middleware 这些基础能力。
- LangGraph 提供状态图、循环、持久化和流式执行能力。
- DeepAgents 则在这一层之上,专门为长任务提供了一套 Agent 运行时框架,把常见运行设施都预装好了。
createDeepAgent 是 DeepAgents 里更高级别的入口。比起你手动组合 createAgent、createFilesystemMiddleware、createSkillsMiddleware、createSubAgentMiddleware、createMemoryMiddleware、createSummarizationMiddleware,它直接编排好了一批默认中间件和提示词。你需要关心的,主要是配置模型、后端、长期记忆、Skill 目录、子 Agent 和自定义系统提示词。
当前 Demo 的核心创建函数是 createIntelligenceDeskAgent(),职责非常清晰:把一个“深度调研助手”的运行环境组装出来。
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 的系统提示词,是整个架构的“流程合同”。它规定了语言、职责、标准流程、委派规则、文件约定,以及何时给用户反馈。
下面是经过压缩后的关键结构:
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 或 README 里,模型每次不一定能稳定遵守。系统提示词里集中定义主流程,更适合需要重复运行的 Agent 应用。
researcher:搜索工具要有硬上限和写入约束
联网搜索工具本身很简单,当前 Demo 用 Bocha API 封装了一个 web_search:
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:
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:
const analystSubAgent = {
name: "analyst",
description:
"使用 eval REPL 进行数值计算与结构化数据分析。适用于计算、排名、同比对比或 JSON/CSV 分析。",
systemPrompt: dedent`
你是一名数据分析师,所有计算必须通过 eval REPL 完成,禁止猜测数字。 1. 从 /workspace/sources/ 读取数据文件
2. 在 REPL 中编写并运行 Ja vaScript
3. 将分析结果保存到 /workspace/sources/analysis_*.md
`,
middleware: [createCodeInterpreterMiddleware()],
}
这段设计背后有一个非常明确的工程判断:计算能力不应该混在自然语言推理里,而应该变成工具调用。
QuickJS 的优势是轻量、嵌入方便,适合用来运行 Ja vaScript 片段做局部计算。对于这个 Demo 来说,它足够处理 GDP 加总、占比计算、排序等任务。如果换成更复杂的数据分析——比如读取大型 CSV、做统计建模、画图——那可能就需要 Python 沙箱或专门的数据处理服务了。
本地 analysis_results.md 中已经有可复查的结果:
## 基础数据
- 全国 GDP 总量:1260583 亿元
- 广东:135673.2 亿元,增速 4.8%
- 江苏:128204.7 亿元,增速 5.8%## 计算结果
### 六省 GDP 总和
559978.1 亿元### 各省占全国 GDP 比重
- 广东:10.76%
- 江苏:10.17%
这里仍然有一个可以改进的点:报告给出了计算结果,但理想的分析文件应该同时保留实际运行的 Ja vaScript 代码。这样,审阅者不仅能看到结果,还能复现公式。在技术博客里最好点出这一点:eval 工具解决了可执行性的问题,但“保存计算代码”仍然要靠 Prompt 或额外的规范来约束。
editor:审稿不改稿,避免责任混乱
很多多 Agent 设计会让编辑子 Agent 直接去改写报告,看起来省事,实际上容易引入两个问题:一是编辑会打乱主报告的叙事结构,二是主 Agent 没办法很好地解释最终修改来自哪里。
Demo 的设计要稳妥得多:editor 只审阅,不写文件。
const editorSubAgent = {
name: "editor",
description:
"审阅报告草稿的准确性、结构与完整性。在 /workspace/reports/draft_*.md 写好后使用。",
systemPrompt: dedent`
你是一名资深情报编辑,负责审阅报告草稿,不要亲自改写报告。 审阅要点:
- 是否直接回答原始问题?
- 章节结构是否清晰?
- 是否引用来源并列出参考资料?
- 是否有遗漏、无依据断言或缺失视角?
- 语言是否为中文,表述是否专业? 返回具体、可操作的修改建议。
`,
}
这个模式适用于绝大多数文档生成场景:生成者负责写,审阅者负责指出问题,最后的修订仍然由主 Agent 统一执行。它牺牲了一点点自动化程度,但换来的是更清晰的责任边界。
如果在生产系统里继续演进,可以把 editor 的输出结构化,比如:
severity:critical、major、minorsection:对应报告的哪个章节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。当前系统提示词里写得很清楚:
这句话非常实用。Agent 在运行的时候,经常会把所有看起来像能力名称的东西都当作可调用对象。如果不说清楚,模型可能会去调用一个不存在的 report-writer 子 Agent,流程就必然失败。
工程实践上,更推荐把 Skill 当作“可复用工作手册”来理解,把子 Agent 当作“有独立上下文和工具的执行角色”。两者可以协作,但不能混淆。
Todo:复杂任务需要显式进度状态
createDeepAgent 内置了 todo 规划能力,底层来自 LangChain 的 todoListMiddleware。项目里还有一个独立的示例 src/todo-middleware-test.mjs,用普通的 createAgent 来验证 todo 能力:
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,它做了一个非常重要的工作:把流式更新、子图执行、工具调用和输出文件都列了出来。
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:
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 有计算结果,但没有保留 Ja vaScript 计算片段。更好的 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,你完全可以让人工智能搜完就直接回答;但如果要做可发布、可复查、可迭代的调研系统,那就必须让每一步都有产物、每个数字都能复现、每个结论都有来源、每个角色都有边界。DeepAgents 的价值,正是在这条从“能跑”到“可控”的路上,减少了大量重复的基础设施工作。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。