字节笔记本
2026年8月29日
开源 DeepResearch:Plan-Search-Analysis 流程怎么跑
想自己跑一套「深度研究」时,很多人会把两件事混在一起:一个是带引用的搜索前端(Scira 那条线),另一个是能递归往下挖、最后吐出长报告的 Agent。后者就是 OpenAI Deep Research 火了之后,社区里最先被拆开的那套 Plan → Search → Analysis 循环。dzhng/deep-research 把这套循环压到大约 500 行 TypeScript,读源码比读论文快。
这篇文章只讲这条流水线怎么在本机跑起来。搜索前端的部署见站内另一篇 开源 DeepSearch:企业端深度搜索怎么部署。
它在干什么
仓库目标写得很清楚:做一个容易看懂、方便改的 Deep Research Agent,不要框架堆砌。核心就四个函数,全在 src/deep-research.ts:
generateSerpQueries:根据用户问题和已有 learnings,生成一批互不重复的搜索词,每个词还带一条researchGoal。processSerpResult:把 Firecrawl 搜回来的 Markdown 压到 2.5 万字符以内,抽 learnings 和 follow-up questions。deepResearch:按 breadth / depth 递归。depth 减 1,breadth 减半,带着上一轮目标和新问题再搜。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.json 的 engines 写死了 22.x),再加两把 Key。
git clone https://github.com/dzhng/deep-research
cd deep-research
npm install
cp .env.example .env.local.env.local 最小集合:
FIRECRAWL_KEY=YOUR_FIRECRAWL_KEY
OPENAI_KEY=YOUR_OPENAI_KEY
CONTEXT_SIZE=128000Firecrawl 负责搜和抽正文。没有它,后面的 Analysis 没有材料。OpenAI 这边默认模型是 o3-mini,并且开了 structuredOutputs。本地或兼容接口把 OPENAI_KEY 注释掉,改成:
OPENAI_ENDPOINT=http://localhost:1234/v1
CUSTOM_MODEL=llama3.1想用 DeepSeek R1,填 Fireworks 的 Key 即可,代码会自动切过去(src/ai/providers.ts 里 R1 优先于 o3-mini):
FIREWORKS_KEY=YOUR_FIREWORKS_KEY然后:
npm start交互顺序是:问题、breadth、depth、report 或 answer;report 模式下还要回答澄清问题。跑完当前目录会多一个 report.md 或 answer.md。
容器方案也有。把 .env.example 改成 .env.local 之后:
docker compose up -d
docker exec -it deep-research npm run docker容器里的 docker 脚本不读 --env-file,环境变量要在 compose 里注入。
深度和宽度怎么设
这两个数字决定账单和报告厚度。
| 参数 | 含义 | README 建议 | 代码默认(CLI) |
|---|---|---|---|
| breadth | 每一层并发生成多少条 SERP query | 3-10 | 4 |
| depth | 递归层数 | 1-5 | 2 |
每一层结束后,下一层的 breadth 变成 Math.ceil(breadth / 2),depth 减 1。所以默认 4x2 大约是:第一层 4 次搜索,第二层每条再拆 2 次。Firecrawl 每次 search 带 limit: 5 并且直接要 markdown,一次循环就会打不少页。
并发由 FIRECRAWL_CONCURRENCY 控制,默认 2。免费档容易 429,先保持 1 或 2。付费或自建可以往上加:
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:
npm run api两个接口:
# 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 更实际。