AX 爱鲜报

综合 / 人工智能 · 2026-10-27 10:40 · 1 阅读 · 0 赞

一个主编、三个工种、两本手册:用 DeepAgents 搭一支 AI 调研队

用 DeepAgents 组装多 Agent 深度调研系统:主编负责协调与起草,调研员、分析师、编辑各司其职,配合技能手册与中间件,把团队编制变成配置项。

写在前面:前两篇我们学了零件——中间件的四个钩子(上篇)、三个开箱即用的中间件(中篇)。这篇是收官:用这些零件组装一个真正能干活的多 Agent 系统。 它是一个"深度调研助手":给它一个主题,它自己规划、派人联网调研、做数据分析、起草报告、审稿、定稿。readme 在 cli.mjs 里写了一行很有意思的注释——"除了开发者,面向其他 Agent 的调用"。这句话点明了这套东西的定位:它不只是一个给人用的工具,更是一个可以被上层编排调用的能力单元。以下所有代码均来自课堂真实文件。


一、先看编制表:一个主编 + 三个工种

整个系统的骨架在 agent.mjs 里:

return createDeepAgent({
    model,
    systemPrompt: orchestratorPrompt,
    subagents: [researchSubAgent, analystSubAgent, editorSubAgent],
    memory: [path.join(projectDir, "AGENT.md")],
    middleware: [todoListMiddleware()],
    backend,
    skills: ["/skills/"]
})

六个参数,就是一个团队的完整编制:

参数 作用 类比
systemPrompt 主 Agent 的职责说明 主编的岗位职责
subagents 三个子 Agent 三个工种的员工
memory 项目记忆文件 公司的知识库
middleware 待办清单能力 任务看板
backend 文件系统后端 共享的稿件盘
skills 技能目录 两本岗位手册

注意 createDeepAgent 和上次那个 createAgent 的区别——多出来的这几个参数(subagents、memory、skills)正是 DeepAgents 相对基础 Agent 的"高阶封装"。

上次说它是"半成品"——现在能看到具体半成品在哪了:它把"团队编制"这件事做成了配置项。

还有一处细节:

const projectDir = path.resolve(
    path.dirname(fileURLToPath(import.meta.url)),
    ".."
)

".." ——往上一级。 因为 agent.mjs 在 src/deepagents/ 目录里,而工作区的根应该在整个项目的根目录。这样 skills: ["/skills/"] 才能指到项目根的 skills/ 目录。


二、主编:只做协调,但有一条活必须自己干

主 Agent 的 systemPrompt 是整个系统里最长的(五十多行),结构很清晰。

第一段:语言要求

## 语言要求

- **所有输出必须使用中文**:对话回复、write_todos 任务列表、文件内容、搜索关键词
- write_todos 中每条 todo 的 content 必须用中文描述,例如「撰写调研计划」「委派调研员调研 LangGraph」
- 搜索时优先使用中文关键词;英文专有名词(如 LangGraph、AutoGen)可保留
- 报告、调研笔记、计划文件全部用中文撰写    

为什么第一段要花四行讲"用中文"?

因为这件事在系统里会层层放大:主 Agent 用英文写计划 → 子 Agent 看到英文计划 → 用英文搜索 → 检索到英文资料 → 写出英文报告。源头定错了,整个链条都错。

所以它把"中文"要求写得非常具体——不是笼统的"用中文",而是点名了四个地方:对话回复、任务列表、文件内容、搜索关键词。 还给了例子、还说清了例外(英文专有名词可保留)。

"要求要具体到能被执行" ——这是写 Agent prompt 的一个基本原则。

第二段:职责与边界

## 你的职责

协调调研员、分析师和编辑完成报告。不要亲自完成所有调研——将专业工作委派给子 Agent。

一句话定边界:"你是协调者,不是全干的那个人。"

为什么要特意强调?因为 LLM 有个倾向——能自己干就自己干了。 尤其是它看到"调研"这个任务,很可能直接开始搜索,把子 Agent 晾在一边。

所以这条职责说明其实是在"纠正模型的默认行为"。

第三段:标准流程六步

1. **规划** — 用 write_todos 拆解任务(中文)。将用户问题保存到 /workspace/sources/question.txt
2. **调研** — 按 web-research 技能:写 research_plan.md,委派调研员子 Agent(可并行)
3. **分析** — 若涉及数字对比或数据表,委派分析师子 Agent
4. **起草** — **由你亲自**按 report-writer 技能撰写,用 write_file 写入 /workspace/reports/draft_[主题].md
5. **审阅** — 委派编辑子 Agent 审稿,根据反馈修订一次
6. **定稿** — 保存最终报告到 /workspace/reports/report_[主题]_[日期].md

六步,每步都写清楚了三件事:做什么、用什么、产出放哪。

注意第 4 步那个加粗的"由你亲自"——这跟第二段的"不要亲自做调研"形成了呼应:

环节 谁干 为什么
调研 委派 可以并行,分工更专业
分析 委派 需要专门的代码执行能力
审阅 委派 独立视角,避免自己审自己
起草 主 Agent 亲自 这是不可拆分的核心工作

"起草由主编亲自写,调研交给记者,审稿交给编辑"——这就是一套人类编辑部的工作流。

为什么起草不能委派? 因为报告是最终交付物,它需要统合所有 findings 和 analysis 的视角。如果派给一个子 Agent,那个子 Agent 只能看到自己那部分上下文,写出来的东西是碎的。

第四段:最容易踩的坑——"技能不是子 Agent"

## task 工具(子 Agent 委派)

**仅**以下 subagent_type 合法:researcher、analyst、editor、general-purpose。

- web-research、report-writer 是**技能**(写作指南),**不是**子 Agent,禁止作为 subagent_type 调用
- 报告起草、修订、定稿由**主 Agent 自己**用 write_file / edit_file 完成,不要委派 task

这一段是整份 prompt 里最"实战"的部分——它在防一个具体的错误。

系统里同时存在两样东西:

类别 名字 性质
子 Agent researcher、analyst、editor 能干活的,可以用 task 委派
技能 web-research、report-writer 说明书,只能"看",不能"派"

名字都是英文小写加连字符,长得还很像——模型很容易搞混,试图去 task 一个叫 web-research 的子 Agent。

所以 prompt 里用加粗的"仅"和"不是"强调了两遍。这是被实际运行教出来的经验——不写这段,八成会翻车。

第五段:委派规则——一堆"最多"

## 委派规则

- 每个调研员只负责一个聚焦的子主题
- **每份报告最多 3 个调研员**——只选最相关的子主题
- 框架对比类任务:优先调研用户明确点名的框架;否则选最重要的 3 个
- 最多并行启动 3 个调研员,已有 3 份 findings 文件后不再新增调研员
- 仅在确实需要数值计算时使用分析师
- 每份报告只调用编辑一次(草稿完成后)
- 调研完成后直接进入起草 → 审阅 → 定稿,不要额外开调研轮次

七条规则,六条在限制。

规则 限制什么
每个调研员只负责一个子主题 防止范围重叠
最多 3 个调研员 防止发散
已有 3 份 findings 后不再新增 用"已有产出"当停止条件
仅在确实需要时用分析师 防止滥用
只调用编辑一次 防止无限返工
不要额外开调研轮次 防止回到调研阶段打转

为什么要限制得这么细?因为 Agent 有个通病——"过度勤劳"。

它会不停搜索、不停派活、反复审稿,看起来很努力,实际上是在空转烧钱。而 token 和时间都是真金白银。 所以这些"最多"其实是在给 Agent 装护栏。

尤其是最后一条"不要额外开调研轮次"——它防的是"绕回去"。流程本来是线性的六步,但 Agent 可能在审阅后觉得"资料不够,再查一轮吧",然后回到第 2 步,再来一遍。这条规则明确封死了这条路。

第六段:文件约定

## 文件约定

- 计划与原始资料:/workspace/sources/
- 草稿与终稿:/workspace/reports/
- 同一时间只编辑一个文件,避免冲突  

光有流程还不够,还得规定"东西放哪"。

两个目录分工明确:

/workspace/
├── sources/     ← 中间产物:问题、计划、findings、分析
└── reports/     ← 交付物:草稿、终稿

为什么必须约定路径?因为这是子 Agent 之间唯一的通信方式。 后面会看到——子 Agent 各跑各的,靠的就是"你去读 /workspace/sources/ 下的文件"。

"同一时间只编辑一个文件,避免冲突" 这条也很关键——多 Agent 并行时,两个子 Agent 同时写一个文件会互相覆盖。 这条规则本质是个简易的并发控制。

第七段:完成后要交代什么

## 完成时告知用户

- 最终报告保存路径
- 2-3 句话的核心发现摘要
- 调研中的局限或信息缺口

三条,最后一条最有意思——要求主动交代"哪里没搞清楚"。

这跟前面 RAG 课学的"允许模型说不知道"是同一个思路。一个只报喜的系统,你没法判断它的结论能不能信。主动说明局限,反而提高了整份报告的可信度。


三、三个工种:每个都写清了"只做什么"

三个子 Agent 的定义都不长,但每一个都在用力的地方很讲究。

调研员 researcher:一份"防失控"的岗位说明

const researchSubAgent = {
    name: "researcher",
    description: "通过联网搜索调研单一子主题。每次只分配一个子主题;多个独立子主题可并行启动多个调研员。",
    systemPrompt:dedent`
        你是一名专业的调研员,负责调研**一个**分配给你的子主题,并写入**一份**调研结果文件。
        
        ## 工作流程(严格遵守,静止空转循环)
        1. **可选**:用 write_todos 列出最多3条中文执行步骤(例如[搜索官方文档]
        [搜索社区评价] [整理并写入finds]),然后按照步骤执行。
        2.最多调用3次 web_search(硬性上限,绝不超过)
        3.将搜索结果整理为结构化摘要,包含关键事实与来源URL。
        4.调用 write_file **一次**,保存到任务指定的路径
        (必须在/workspace/sources/findings_*.md)
        5.用一句话确认已完成,然后**立即停止**,不要再搜索、写入或者更新 todo
        ...
    `,
    tools: [webSearch],
}

这是三个子 Agent 里 prompt 最长的一个,因为它承担的风险最大——联网搜索是"停不下来"的重灾区。

看那五步流程里的关键词:

步骤 约束
1 可选、最多 3 条
2 最多调用 3 次(硬性上限,绝不超过)
4 调用 write_file 一次
5 立即停止,不要再搜索、写入或者更新 todo

"最多 3 次搜索、一次写入、写完立即停止" ——这三条凑成了同一个目的:防止它陷入"再搜一下""再补充一点"的循环。

这个担心不是多余的。 联网搜索这种工具,模型很容易上瘾——搜到一个结果,觉得不够全,再搜;搜到新关键词,再搜。没有上限的话,一个子主题能烧掉几十次调用。

第 5 步那句"不要再搜索、写入或者更新 todo"尤其细致——它逐个点名了三种可能的行为。写"及时停止"太笼统,模型不知道该停什么;点名到具体动作,它才知道该收手。

description 那条也很关键:

每次只分配一个子主题;多个独立子主题可并行启动多个调研员。

这句是写给主 Agent 看的——它是"调用说明书"。告诉主 Agent:"我是单线程的,想并行就多开几个我。"

注意 description 面向的是"调度者",systemPrompt 面向的是"执行者自己"。 两个 audience 不同,写法也不同:一个讲"我适合干什么",一个讲"你该怎么干"。

还有个细节值得注意:

- 其他人只能看到你写入的文件,内容必须完整、自洽

这句话揭示了多 Agent 协作的一个硬约束——子 Agent 之间没法直接聊天,只能通过文件交流。

所以它必须提醒调研员:你脑子里的东西不算数,写进文件的才算数。 而且写的时候要"完整、自洽"——因为读者(主 Agent 或另一个子 Agent)看不到你的思考过程,只看到这个文件。

这就是"上下文隔离"的代价: 隔离让每个子 Agent 不被无关信息干扰(前面 Harness 那节课讲过),但也意味着它必须把成果"显式地交出来",不能指望别人知道。

分析师 analyst:不许猜数字

const analystSubAgent = {
    name: "analyst",
    description:
        "使用 eval REPL 进行数值计算与结构化数据分析。适用于计算、排名、同比对比或JSON/CSV 分析。",
    systemPrompt:dedent`
        你是一名数据分析师,所有计算必须通过 eval REPL 完成 **禁止** 猜测数字。

        ## 工作流程

        1.从/workspace/sources/ 读取数据文件(或从调研结果中提取数字)。
        2.在 REPL 中编写并允许 JavaScript ,计算总和、均值、排名、增长率等
        3.将分析结果保存到/workspace/sources/analysis_*.md ,包含计算逻辑与结论

        必须展示计算过程,结论可以从 REPL 输出复现。所有输出使用中文。
    `,
    middleware: [createCodeInterpreterMiddleware()]
}

这个 Agent 的组合方式跟前两个不一样——它靠的是"中间件给的能力"。

middleware: [createCodeInterpreterMiddleware()]

注意:它没有 tools 字段,角色能力全在中间件里。

这正好呼应上一篇讲的——中间件可以给 Agent 送工具。 这里更进一步:createCodeInterpreterMiddleware(来自 @langchain/quickjs)提供的是一个代码执行环境(REPL),比单个工具复杂得多。

为什么分析师需要 REPL,而不是让模型直接算?

因为 LLM 算数是不可靠的。 它擅长语言和推理,但做多位数运算、累加、百分比时经常出错,而且错得很自信。

所以 prompt 里那句才那么硬:

所有计算必须通过 eval REPL 完成 **禁止** 猜测数字

"禁止猜测数字"——它把"算数"这件事从"模型推理"改成了"代码执行"。

方式 可靠性
模型心算 不可靠,可能悄悄算错
写代码执行 确定性的,可复现

而且它还要求"必须展示计算过程,结论可以从 REPL 输出复现"——这不只是要求答案对,还要求"答案能被验证"。 跟前面文件系统那篇里"用断言验证权限"是同一个工程习惯:别让我信你,让我能查你。

编辑 editor:只审不改

const editorSubAgent = {
    name: "editor",
    description:"审阅报告草稿的准确性、结构与完整性。在/workspace/sources/draft_*.md ",
    systemPrompt:dedent`
        你是一名资深情报编辑,负责**审阅**报告草稿 **不要**亲自改写报告。

        ## 阅读材料

        - 原始问题:/workspace/sources/question.txt
        - 待审核草稿:任务中指定的路径
        - 支撑材料:/workspace/sources/ 下的调研文件(如需要)

        ## 审阅要点

        - 报告是否直接回答了原始问题?
        - 章节结构是否清晰、段落是否充实(而非只有bullet 列表)
        - 是否引用了来源,并在【参考资料】章节列出?
        - 是否有遗漏、无依据的断言或缺失的视角?
        - 语言是否为中文,表述是否专业?

        ## 输出

        返回简洁的审阅意见和具体、可操作的修改建议。
        **不要**写入报告文件,只提供反馈。所有输出使用中文。
    `,
}

这个 Agent 的关键词是"审"——它被明确禁止改稿。

负责**审阅**报告草稿 **不要**亲自改写报告
...
**不要**写入报告文件,只提供反馈

为什么要这么严格地禁止它动手?

因为**"审"和"改"是两种不同的立场:**

立场 倾向
审稿人 挑毛病,找出问题
改稿人 把事情做完,倾向于"差不多就行"

如果让同一个 Agent 又审又改,它会倾向于"小修一下就过" ——因为自己改自己的稿,心理上会不自觉地放松标准。分工的价值就在于立场独立。

而且"不改"还有个技术上的好处:避免两个 Agent 同时写一个文件(呼应主 prompt 里"同一时间只编辑一个文件"那条规则)。

它的审阅要点也设计得很具体,五条全是"可判断"的问题:

要点 检查什么
是否直接回答了原始问题? 回归原题(要不要读 question.txt 的原因)
章节结构是否清晰、段落是否充实(而非只有 bullet 列表) 形式质量
是否引用了来源,并在【参考资料】章节列出? 可追溯性
是否有遗漏、无依据的断言或缺失的视角? 内容质量
语言是否为中文,表述是否专业? 基本规范

五条覆盖了"对不对、好不好、有没有据、全不全、专不专业"——这是一份真正的审稿清单。

注意第一条为什么需要读 question.txt——因为审稿人要独立核对"这份稿子有没有回答最初的问题",不能光看草稿自己写得漂不漂亮。

最后,"返回简洁的审阅意见和具体、可操作的修改建议"——"可操作"三个字很重要。审稿意见最怕"这段不够深入"这种评价,主 Agent 看了也不知道怎么改。要的是"第二节缺少 2024 年后的数据,建议补充 X 来源"。


四、两本手册:技能(Skills)

createDeepAgent 的参数里有一行:

skills: ["/skills/"]

指向项目根目录下的 skills/ 文件夹。里面放着两份 SKILL.md。

这是技能——前面反复强调"技能不是子 Agent",现在可以看清它到底是什么了。

技能长什么样

---
name: report-writer
description: 将调研结果整理为结构清晰、专业的中文情报报告
---

# 报告撰写技能

将调研 findings 综合为最终交付物时使用本技能。

> **注意**:本技能是主Agent的写作指南,不是子Agent。请主Agent 亲自用`write_file`撰写报告,**不要**通过`task`工具委派`report-writer`。

## 报告结构
...

结构很简单:YAML frontmatter(名字 + 描述)+ Markdown 正文。

部分 作用 给谁看
name + description 元信息,让 Agent 知道"有这个技能、什么时候用" 主 Agent 的"技能列表"
正文 具体的流程、规范、模板 主 Agent 决定用的时候读

这跟工具的定义方式很像——工具有 name + description + schema,技能有 name + description + 正文。都是"先让模型知道有什么,再让它决定用不用"。

两份技能,正好覆盖流程的两段

技能 描述里写的用途 对应流程
web-research "结构化多来源联网调研,支持并行委派调研员子 Agent" 第 2 步:调研
report-writer "将调研结果整理为结构清晰、专业的中文情报报告" 第 4 步:起草

主 Agent 的流程里那两个"按 xxx 技能"的引用,就指向这两个文件。

这就是"技能"的作用:把长流程的操作细节从主 prompt 里抽出来,变成"按需加载"的说明书。

想想看——如果把这两份文档的内容全塞进 orchestratorPrompt,那 prompt 会膨胀到一两百行,而且大部分内容在不需要的时候也占着上下文。

技能机制解决的就是这个矛盾:主 prompt 里只留一句"按 web-research 技能做",需要时再去读细节。

那两句警告:技能的定位问题

两份 SKILL.md 的开头,都有一句几乎一样的话:

> **注意**:本技能是主 Agent 的流程指南,不是子 Agent。
> 联网搜索请委派 `researcher` 子 Agent,**不要**将 `web-research` 作为 subagent_type 调用。
> **注意**:本技能是主Agent的写作指南,不是子Agent。
> 请主Agent 亲自用`write_file`撰写报告,**不要**通过`task`工具委派`report-writer`。

这是"防御性写作"——同一个坑,在三个地方各堵了一遍:

位置 怎么堵
orchestratorPrompt "web-research、report-writer 是技能,不是子 Agent"
web-research/SKILL.md "不要将 web-research 作为 subagent_type 调用"
report-writer/SKILL.md "不要通过 task 工具委派 report-writer"

为什么要在技能自己身上也写一遍? 因为技能被读取的那一刻,往往就是模型"正在考虑要不要用某个名字去派活"的时候——在最接近出错点的位置再提醒一次。

而且两份技能的警告还各自带了个"正面指引":

  • web-research 说"联网搜索请委派 researcher 子 Agent"(告诉你该派谁)
  • report-writer 说"请主 Agent 亲自用 write_file 撰写"(告诉你该谁干)

光说"别做什么"不够,还得说清"该做什么" ——不然模型只是排除了一个错误选项,接着去试下一个错误选项。

web-research 的流程:三步

### 1. 规划

1. 将用户问题写入 `/workspace/sources/question.txt`
2. 创建 `/workspace/sources/research_plan.md`,包含(**中文撰写**):
    - 主调研问题
    - 2-4 个互不重叠的子主题
    - 每个子主题的预期产出
    - 综合策略

### 2. 委派(可并行)

对每个子主题,用 `task` 工具启动 **researcher(调研员)** 子 Agent:

调研【具体子主题】。可用 write_todos 列出最多 3 条中文步骤(可选)。 使用 web_search 搜索(最多 10 次,关键词用中文)。 将 findings 保存到 /workspace/sources/findings_[子主题slug].md,写入后结束。


子主题相互独立时,最多并行 3 个调研员。**总数不超过 3 个。**

### 3. 综合

1. 读取所有 `/workspace/sources/findings_*.md`
2. 整合为连贯分析
3. 定稿前委派 **editor(编辑)** 子 Agent 审阅

三步:规划 → 委派 → 综合。

第 2 步里那段代码块很讲究——它是"派活时该说的话"的模板。

主 Agent 要把这段话发给子 Agent,里面包含了子 Agent 完成任务所需的所有信息:干什么、怎么干、上限多少、产出存哪、什么时候停。这就是多 Agent 系统里的"工单"。

但这里出现了一个不一致,值得留意:

位置 说的搜索次数上限
web-research/SKILL.md 的派活模板 "最多 10 次"
researcher 的 systemPrompt "最多调用 3 次 web_search(硬性上限,绝不超过)"

两个数字对不上——一个说 10 次,一个说 3 次。

这种情况在真实项目里很常见,也很危险:当两处指令冲突时,模型可能挑对自己"宽松"的那个遵守。 结果就是"以为限制了 3 次,实际跑了 10 次"。

经验:凡是数值型的约束(次数、长度、超时),都应该定义成常量并在所有地方引用同一个来源。 写在自然语言里的数字,迟早会对不上。

report-writer 的规范:报告长什么样

## 报告结构

1. **标题** -  `#[主题]:情报简报`
2. **执行摘要** - 3-5 条核心要点
3. **背景** - 主题背景与当前重要性
4. **核心发现** - 按主题组织,而非按来源堆砌
5. **结论** - 直接回答原始问题
6. **参考资料** - 标号列表,格式 `[标题](URL)`

六个部分,是一个标准的调研报告骨架。

第 4 条那个括号注释最有价值:

- **核心发现** - 按主题组织,而非按来源堆砌

"按主题组织,而非按来源堆砌" ——这句话点出了新手写调研报告最常见的毛病:

写法 结构 读者体验
按来源堆砌 "A 网站说……B 网站说……C 网站说……" 读了一堆,不知道结论是什么
按主题组织 "关于性能:……;关于生态:……" 每个问题都能找到答案

"堆砌"是资料搬运,"组织"才是分析。 这一句注释,把"报告撰写"和"资料汇编"区分开了。

写作规范里也有几条很实用的禁令:

- **全文使用中文** (专有名词可保留英文)
- 第三人称专业表述,禁止 [我调研了] [我发现] 等自述
- 关键论断 inline 引用 `[标题](URL)`
- 每节内容充实(多段落),避免一句带过
- 对比类报告:每项单独一节,再加综合对比节

"禁止「我调研了」「我发现」等自述"——因为这是给客户看的报告,不是 Agent 的工作日志。 报告里出现"我调研了某某网站",读起来就变成了一份个人笔记。

"关键论断 inline 引用" ——要求在正文里直接标出来源,而不是只在末尾列参考资料。这样读者能立刻知道"哪个说法有出处、哪个没有"。

文件命名也规定死了:

- 草稿:`/workspace/reports/draft_[主题slug].md`
- 终稿:`/workspace/reports/report_[主题slug]_[YYYY-MM-DD].md`

注意终稿的文件名带了日期。 这跟前面评估那篇讲的"数据集要带版本号"是同一个思路——同一个主题可能调研多次,带日期的文件名能区分批次,也能看出"这次比上次新在哪"。


五、那把"联网的钥匙":search.mjs

调研员能联网,靠的是这个工具:

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 检索互联网网页。输入中文或者中英结合的搜索关键词,
        可选count指定结果数量。 
        `,
        schema:z.object({
            query: z
                .string()
                .mi
...(截断)
标签 AI Agent Prompt DeepAgents

评论

登录后才可评论

0 条评论

  • 暂无评论,来写第一条吧。