ByteNoteByteNote

字节笔记本

2026年8月29日

开源 DeepResearch:Plan-Search-Analysis 流程怎么跑

API中转
¥120

想自己跑一套「深度研究」时,很多人会把两件事混在一起:一个是带引用的搜索前端(Scira 那条线),另一个是能递归往下挖、最后吐出长报告的 Agent。后者就是 OpenAI Deep Research 火了之后,社区里最先被拆开的那套 Plan → Search → Analysis 循环。dzhng/deep-research 把这套循环压到大约 500 行 TypeScript,读源码比读论文快。

这篇文章只讲这条流水线怎么在本机跑起来。搜索前端的部署见站内另一篇 开源 DeepSearch:企业端深度搜索怎么部署

它在干什么

仓库目标写得很清楚:做一个容易看懂、方便改的 Deep Research Agent,不要框架堆砌。核心就四个函数,全在 src/deep-research.ts

  1. generateSerpQueries:根据用户问题和已有 learnings,生成一批互不重复的搜索词,每个词还带一条 researchGoal
  2. processSerpResult:把 Firecrawl 搜回来的 Markdown 压到 2.5 万字符以内,抽 learnings 和 follow-up questions。
  3. deepResearch:按 breadth / depth 递归。depth 减 1,breadth 减半,带着上一轮目标和新问题再搜。
  4. writeFinalReport / writeFinalAnswer:learnings 攒够了,写成 report.md(长报告加 Sources)或 answer.md(短答案)。

模型调用走 Vercel AI SDK 的 generateObject,输出用 Zod schema 卡住。源码里的 Function Call 就是这个:让模型按 schema 吐 JSON,没有另写一套工具协议。提示词几乎全在 src/prompt.ts 和这几个函数的字符串里,改行为主要就是改这几段。

入口 src/run.ts 会先问三个东西:研究问题、breadth(默认 4)、depth(默认 2)。选 report 模式时,还会先跑 generateFeedback,让模型补 1 到 3 个澄清问题,你答完才开搜。这就是 Plan 那一步。

本机跑起来

环境要求:Node.js 22(package.jsonengines 写死了 22.x),再加两把 Key。

bash
git clone https://github.com/dzhng/deep-research
cd deep-research
npm install
cp .env.example .env.local

.env.local 最小集合:

bash
FIRECRAWL_KEY=YOUR_FIRECRAWL_KEY
OPENAI_KEY=YOUR_OPENAI_KEY
CONTEXT_SIZE=128000

Firecrawl 负责搜和抽正文。没有它,后面的 Analysis 没有材料。OpenAI 这边默认模型是 o3-mini,并且开了 structuredOutputs。本地或兼容接口把 OPENAI_KEY 注释掉,改成:

bash
OPENAI_ENDPOINT=http://localhost:1234/v1
CUSTOM_MODEL=llama3.1

想用 DeepSeek R1,填 Fireworks 的 Key 即可,代码会自动切过去(src/ai/providers.ts 里 R1 优先于 o3-mini):

bash
FIREWORKS_KEY=YOUR_FIREWORKS_KEY

然后:

bash
npm start

交互顺序是:问题、breadth、depth、report 或 answer;report 模式下还要回答澄清问题。跑完当前目录会多一个 report.mdanswer.md

容器方案也有。把 .env.example 改成 .env.local 之后:

bash
docker compose up -d
docker exec -it deep-research npm run docker

容器里的 docker 脚本不读 --env-file,环境变量要在 compose 里注入。

深度和宽度怎么设

这两个数字决定账单和报告厚度。

参数含义README 建议代码默认(CLI)
breadth每一层并发生成多少条 SERP query3-104
depth递归层数1-52

每一层结束后,下一层的 breadth 变成 Math.ceil(breadth / 2),depth 减 1。所以默认 4x2 大约是:第一层 4 次搜索,第二层每条再拆 2 次。Firecrawl 每次 search 带 limit: 5 并且直接要 markdown,一次循环就会打不少页。

并发由 FIRECRAWL_CONCURRENCY 控制,默认 2。免费档容易 429,先保持 1 或 2。付费或自建可以往上加:

bash
FIRECRAWL_BASE_URL=http://localhost:3002
FIRECRAWL_CONCURRENCY=4

自建时 Key 可以留空字符串,客户端仍然能连上 apiUrl。

试跑建议 depth=1、breadth=2。先确认模型 structured output 和 Firecrawl 都通,再加层。depth=5 加上 breadth=8 会把 token 和搜索次数乘上去,报告不一定更准,只是更贵。

挂成 HTTP 服务

src/api.ts 起了一个 Express,默认端口 3051:

bash
npm run api

两个接口:

bash
# short answer
curl -s http://localhost:3051/api/research \
  -H 'Content-Type: application/json' \
  -d '{"query":"LangGraph vs CrewAI","depth":2,"breadth":3}'

# long report
curl -s http://localhost:3051/api/generate-report \
  -H 'Content-Type: application/json' \
  -d '{"query":"open deep research compare","depth":2,"breadth":3}'

/api/research 会 JSON 返回 answer、learnings、visitedUrls。/api/generate-report 源码里 return report 少写了把结果写回响应的那一步,直接当 HTTP 接口用会拿到裸 Markdown 或空响应。自己接生产前把这一行补上,或者 CLI 出 report.md 更省事。

API 路径没有 CLI 那步澄清问答,query 要自己写完整。把受众、时间范围、要不要对比竞品写进 query,效果接近手动答完 follow-up。

源码里值得改的三处

真要拿它当骨架,这三处比加新框架有用。

1. 提示词。 src/prompt.ts 是全局人设(专家研究员、今天的日期、不要简化)。搜索词、learnings、报告各自的指令写在对应函数里。要中文报告,在 writeFinalReport 的 prompt 末尾加一句「用中文撰写,专有名词保留英文」就行,不必换模型。

2. 搜索后端。 搜和抓都绑死 Firecrawl。换成 Tavily、Exa 或自建 SERP,改 firecrawl.search(...) 那一段,把返回值映射成带 url 和 markdown 的 data 列表。processSerpResult 不关心搜索引擎是谁。

3. 模型。 getModel() 的优先级是 CUSTOM_MODEL,然后 Fireworks R1,再然后 o3-mini。国内中转只要是 OpenAI 兼容并且支持 structured outputs,设 OPENAI_ENDPOINT 和 CUSTOM_MODEL 即可。不支持 JSON schema 的模型会在 generateObject 处直接炸掉,这是这条流水线的硬依赖。

Python 移植在 Finance-LLMs/deep-research-python,逻辑同一套,方便嵌进现有 Python Agent。

和更重的实现怎么选

langchain-ai/open_deep_research 是另一条线:LangGraph、多模型分工(摘要 / 研究 / 压缩 / 写报告)、Tavily 默认搜索、MCP、Deep Research Bench 上有分数。要评测、要接 MCP、要 Studio 可视化,走它。

只想看懂 Plan → Search → Analysis,或给自己的产品塞一个递归研究内核,dzhng 这个仓库更合适。它没有文档入库、没有 HITL 审批计划、也没有长期记忆。报告质量取决于你选的模型和 Firecrawl 抓到的页面,不要拿它跟 ChatGPT 付费 Deep Research 对打。

维护节奏也要心里有数:主循环从 2025 年初定型之后改动不多。当骨架 fork 一份,把搜索和模型换成自己的,比追 upstream 更实际。

分享: