跳到主要内容

Open Deep Research (dzhng) — 架构与原理

30 秒导读: 你给它一句话题目(外加广度、深度两个旋钮),它就会自己不停地"想搜索词 → 搜网页 → 读结果 → 提炼要点 → 顺着要点再想下一批搜索词",像一棵树一样把问题层层展开,最后把攒下来的所有要点交给 LLM 写成一篇带来源链接的 Markdown 报告。全项目刻意压在 500 行以内,是理解「deep research agent」最干净的样本。

1. 这是什么(零基础也能懂)

一句话定义: 一个"会自己深挖"的 AI 研究助手——你只出一个题目,它自动做多轮网络搜索、层层深入,最后产出一份报告。

解决什么问题 / 给谁用:

假设你想研究"2025 年固态电池的商业化进展"。普通做法是你自己去搜索引擎敲十几个关键词、点开几十个网页、边读边记笔记,读到新名词又回头再搜。这个项目就是把"敲词 → 读 → 记 → 再敲词"这个人肉循环自动化:LLM 负责想搜索词和提炼要点,搜索引擎(Firecrawl)负责真的去搜真的去抓网页正文。

给谁用:想要一份"比一次性问 ChatGPT 更深、有真实网页来源"的调研报告的人;以及想学"deep research agent 到底怎么写"的工程师。

它能做什么:

  • 迭代式深挖:一轮的发现会喂给下一轮,越挖越具体。
  • 广度/深度两个旋钮:控制"每层铺多宽"和"往下钻多少层"。
  • 先问清楚再动手:报告模式下先反问你几个澄清问题。
  • 两种产出:长报告(report.md)或一句话精确答案(answer.md)。
  • 并发搜索:同一层的多个搜索词并行跑。

用起来什么样:

一段真实的命令行交互(来自 src/run.ts:32-115):

$ npm start
Using model: o3-mini
What would you like to research? 固态电池 2025 商业化进展
Enter research breadth (recommended 2-10, default 4): 4
Enter research depth (recommended 1-5, default 2): 2
Do you want to generate a long report or a specific answer? (report/answer, default report): report
Creating research plan...

To better understand your research needs, please answer these follow-up questions:

你更关心哪个地区的量产进度(中国/日本/欧美)?
Your answer: 中国和日本
...
Starting research...

之后它就自己跑起来,最后把报告存到 report.md

一句话直觉/类比: 把它想成"一棵会自己长的搜索树"——树根是你的题目,每个节点先让 LLM 想出几个搜索词(这是分叉,宽度由 breadth 决定),搜到结果提炼出要点后,再顺着要点往下长一层(这是加深,层数由 depth 决定),到达底层就停。所有节点攒下的要点汇到一起,就是报告的原料。

2. 顶层全景(它大概怎么转)

部件一句话职责

部件干什么在哪个文件
交互入口 run()命令行问答:收题目、广度、深度、报告/答案模式src/run.ts:32
HTTP 入口 app把同样的能力包成 REST 接口(/api/researchsrc/api.ts
澄清器 generateFeedback动手前反问用户几个问题,把答案拼进题目src/feedback.ts:7
递归引擎 deepResearch全项目的心脏:展开广度×深度的搜索树src/deep-research.ts:176
想词 generateSerpQueries让 LLM 根据题目+已有要点生成一批搜索词src/deep-research.ts:40
搜索 firecrawl.search真去搜索引擎搜、抓网页正文(markdown)src/deep-research.ts:222
提炼 processSerpResult把网页正文喂给 LLM,吐出 learnings + 后续问题src/deep-research.ts:81
汇总 writeFinalReport / writeFinalAnswer把所有 learnings 写成长报告 / 一句话答案src/deep-research.ts:120 / :149
模型层 getModel按环境变量在 o3-mini / DeepSeek-R1 / 自定义模型间切换src/ai/providers.ts:48
截断 trimPrompt把过长的正文按 token 上限递归切短,防止超上下文src/ai/providers.ts:65
提示词 systemPrompt统一的"你是专家研究员"系统提示src/prompt.ts:1

主线走一遍(高层,不进代码)

你的一句话题目 + breadth + depth

[报告模式] generateFeedback 反问几个澄清问题
│ 把问答拼进题目

┌───────────── deepResearch(query, breadth, depth) ─────────────┐
│ │
│ ① generateSerpQueries → 得到 breadth 个搜索词(每个带研究目标) │
│ │ │
│ 并发(受 ConcurrencyLimit 限制)地对每个搜索词: │
│ ▼ │
│ ② firecrawl.search → 抓 5 个网页正文 + 收集 URL │
│ ▼ │
│ ③ processSerpResult → LLM 提炼出 learnings + 后续问题 │
│ ▼ │
│ ④ depth-1 > 0 ? │
│ ├─ 是 → 用「研究目标 + 后续问题」当新题目, │
│ │ breadth 减半、depth 减一,递归回到 ① │
│ └─ 否 → 收工,返回累积的 learnings + URLs │
└─────────────────────────────────────────────────────────────────┘
│ 所有分支的 learnings/URLs 去重合并

writeFinalReport / writeFinalAnswer → LLM 汇总 → report.md / answer.md

怎么读这张图:从上到下是一次研究的时间线;中间方框内的 ①→④ 是一个树节点做的事,④"是"分支就是往下长一层、回到 ①,形成递归。

记住三个关键词就懂了大盘:

  • breadth(广度):这一层分几个搜索词(叉几支)。
  • depth(深度):还要往下钻几层。
  • learnings(要点):一路攒下来的提炼结果,既是下一层想词的原料,也是最后写报告的原料。

3. 深入各机制(阅读地图)

这个项目虽小,但有四个值得单独讲清的机制,按由浅入深排:

  1. 01-recursion-engine.md — 核心引擎 deepResearch。广度×深度的递归树到底怎么展开、breadth/2depth-1 的收敛、pLimit 并发、learnings 如何滚雪球、错误怎么被吞。先读这章。
  2. 02-prompts-and-structured-output.md — 五个 LLM 调用点(想词/提炼/写报告/写答案/澄清)的 prompt 设计,以及用 generateObject + zod schema 逼模型吐 JSON 的套路。
  3. 03-providers-and-trimming.mdgetModel 的多提供商切换,和 trimPrompt 那个"按 token 估算字符、递归切短"的巧妙截断算法。
  4. 04-deep-dive-and-map.md — 边界与局限、巧妙之处提炼、横向对比、完整代码地图。

建议顺序:index(本页)→ 01 → 02 → 03 → 04。只想拿走精华就跳 04。