数据截至 (上游 commit 1b7d2e80db9f)
Open Deep Research — 总览
30 秒导读: 你给它一个问题(比如"对比 2025 年主流推理芯片的性价比"),它自己拆题、并行派多个"子研究员"上网查资料、逐个压缩、最后合成一份带引用的长报告。整个流程是一张用 LangGraph 搭的三层嵌套状态图,四个环节各自可以换用不同的大模型。这是 LangChain 官方开源、可跨模型 / 跨搜索 / 跨 MCP 配置的深度研究 agent。
本章只做全景与路由:讲清它是什么、顶层怎么转、三层图怎么嵌套、主线走一遍,以及各章去哪读。具体实现细节留给后面四章,本章不重复。
1. 这是什么(零基础也能懂)
一句话定义: Open Deep Research 是一个开源、可配置、跨模型 / 搜索工具 / MCP 服务器的深度研究 agent——输入一个问题,输出一份带引用的长报告(README.md:1-19)。
解决什么问题 / 给谁用。 "深度研究"(deep research)指的是那种你问一个宽泛问题、需要 agent 去网上查很多资料、交叉验证、再写成一篇结构化长文的任务。手动做要几小时,这个项目把它自动化。它的定位是简单、完全开源、可换任何模型/搜索后端,性能对标市面上流行的 deep research 产品(README.md:5)。
它能做什么:
- 先判断你的问题够不够清楚,不清楚就反问一句再开工(可关闭)。
- 把问题拆成若干子课题,并行派多个子研究员各查各的。
- 每个子研究员是一个 ReAct 循环(搜索 → 反思 → 再搜索),查完把一大堆原始资料压缩成干净摘要。
- 最后把所有摘要合成一份带引用的最终报告。
- 搜索后端可选 Tavily / OpenAI 原生联网 / Anthropic 原生联网 / 无(
configuration.py:78-93),还能接 MCP 外部工具(configuration.py:214-233)。
用起来什么样。 它作为一张 LangGraph 图部署,入口在 langgraph.json 里登记(langgraph.json:3-5):
"graphs": {
"Deep Researcher": "./src/open_deep_research/deep_researcher.py:deep_researcher"
}
起本地服务后,在 LangGraph Studio 的 messages 输入框里问一个问题、点 Submit,就能看到它跑完整个研究流程(README.md:43-58)。
一句话直觉 / 类比。 把它想成一个研究团队:一位主管(supervisor)拿到选题后拆活、分派;若干研究员(researcher)各自上网查、各写一段;主管收齐后交给一位主笔(final report)统稿成文。这个"主管带一群并行研究员"的结构,就是整套系统的灵魂。
2. 顶层全景(它大概怎么转)
2.1 三段式主干
主图(main graph)是一条几乎笔直的流水线,只有开头一处会分叉(需要澄清就直接结束、等用户回话)。建图代码在 deep_researcher.py:699-719:
用户问题
│
▼
┌────────────────────┐ need_clarification?
│ clarify_with_user │──── 是 ──▶ END(抛出一个澄清问题,等用户)
│ 判断要不要反问 │ deep_researcher.py:60-115
└─────────┬──────────┘
│ 否 / 关闭澄清
▼
┌────────────────────┐
│ write_research_brief│ 把对话浓缩成一句"研究纲要"research_brief
│ 写研究纲要 │ 并初始化 supervisor 的消息上下文
└─────────┬──────────┘ deep_researcher.py:118-175
▼
╔════════════════════╗
║ research_supervisor ║ ← 这是一个【子图】,内部是 supervisor 循环
║ (supervisor 子图) ║ 它会 fan-out 出多个并行 researcher 子图
╚═════════╤══════════╝ deep_researcher.py:710
▼
┌────────────────────┐
│final_report_generation│ 把所有 notes 合成最终报告(含 token 超限重试)
│ 写终稿 │ deep_researcher.py:607-697
└─────────┬──────────┘
▼
END(final_report)
四条主边非常直白(deep_researcher.py:714-716):START → clarify_with_user,research_supervisor → final_report_generation,final_report_generation → END;而 clarify_with_user 和 write_research_brief 的去向用 Command(goto=...) 在节点内部动态决定(deep_researcher.py:77、106-115、163-164)。
2.2 supervisor 如何 fan-out 出并行 researcher
research_supervisor 不是一个普通节点,而是一个子图(deep_researcher.py:710 把 supervisor_subgraph 当节点挂上去)。它内部是一个"想 → 干"的循环(deep_researcher.py:353-363 建图):
┌──────────────┐
START ─▶│ supervisor │ 调研究模型,决定调用哪些工具
└──────┬───────┘ deep_researcher.py:178-223
▼
┌──────────────┐ ResearchComplete / 超迭代 / 无工具调用 ─▶ END
│supervisor_ │
│ tools │ ConductResearch × N ──┐
└──────┬───────┘ deep_researcher.py:225-349
│ │ asyncio.gather 并行
│ 还要继续研究 ▼
│ ┌───────────────────────┐
└────────回到 supervisor │ researcher 子图 × N │
│ (每个是独立 ReAct 循环)│
│ deep_researcher.py:589-605
└───────────────────────┘
关键在 supervisor_tools:主管每轮可以发出多个 ConductResearch 工具调用,代码把它们截到 max_concurrent_research_units 个(deep_researcher.py:291),然后用 asyncio.gather 同时 invoke 多份 researcher_subgraph(deep_researcher.py:295-305)——这就是并行 fan-out。每个子研究员返回压缩后的研究结果,汇回主管消息里(deep_researcher.py:308-313),循环继续直到主管喊停。
具体的委派逻辑、ReAct 循环、并行细节见 02-supervisor-researcher-loop.md;收尾的压缩与终稿容错见 03-compression-token-limits.md。
2.3 部件一句话职责
| 部件 | 干什么 | 在哪(文件:符号) |
|---|---|---|
clarify_with_user | 判断问题是否需要反问澄清,不需要就放行 | deep_researcher.py:60 |
write_research_brief | 把对话浓缩成结构化研究纲要,初始化 supervisor 上下文 | deep_researcher.py:118 |
supervisor | 主管:读纲要,决定用 think_tool 规划、还是 ConductResearch 派活 | deep_researcher.py:178 |
supervisor_tools | 执行主管的工具调用;并行 fan-out researcher 子图;判断收尾 | deep_researcher.py:225 |
researcher | 单个研究员:带工具跑一轮,产生工具调用 | deep_researcher.py:365 |
researcher_tools | 执行研究员的搜索/MCP 工具调用,决定继续还是去压缩 | deep_researcher.py:435 |
compress_research | 把一个研究员的所有原始资料压成干净摘要 | deep_researcher.py:511 |
final_report_generation | 汇总所有 notes 写最终报告,带 token 超限重试 | deep_researcher.py:607 |
3. 三层 StateGraph 嵌套 + 四个 LLM 角色
3.1 三层图是"图套图"
整个系统由三张 StateGraph 嵌套而成——外层图把内层图当作一个节点直接挂进去。这是理解全局最关键的一张结构图:
┌─────────────────────────────────────────────────────────┐
│ 主图 deep_researcher (state: AgentState) │
│ deep_researcher.py:701-719 │
│ clarify → write_brief → [research_supervisor] → report │
│ │ │
│ 把子图当一个节点挂进来(第 710 行) │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ supervisor 子图 (state: SupervisorState) │ │
│ │ deep_researcher.py:353-363 │ │
│ │ supervisor ⇄ supervisor_tools │ │
│ │ │ gather 并行 invoke 多份 │ │
│ │ ▼ │ │
│ │ ┌───────────────────── ────────────────────────┐ │ │
│ │ │ researcher 子图 (state: ResearcherState) │ │ │
│ │ │ deep_researcher.py:589-605 │ │ │
│ │ │ researcher ⇄ researcher_tools → compress │ │ │
│ │ └─────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
每层图有自己独立的 state 类型,互不污染(定义在 state.py):
| 层 | State 类型 | 关键字段 | 定义处 |
|---|---|---|---|
| 主图 | AgentState | messages / research_brief / notes / final_report | state.py:65-72 |
| supervisor 子图 | SupervisorState | supervisor_messages / research_iterations / notes | state.py:74-81 |
| researcher 子图 | ResearcherState | researcher_messages / tool_call_iterations / compressed_research | state.py:83-90 |
层与层之间靠 Command(update=...) 显式传值:比如 write_research_brief 把 research_brief 塞进 supervisor 上下文(deep_researcher.py:163-174),supervisor_tools 又给每个 researcher 子图喂一个 research_topic(deep_researcher.py:296-301)。多个并行分支往同一字段(如 notes、raw_notes)写值时,用 override_reducer 决定是覆盖还是累加(state.py:55-60)。这套状态流转是下一章 01-orchestration-graph.md 的主题。
3.2 四个可独立配置的 LLM 角色
系统不是"一个模型干到底",而是把工作切成四个角色,每个角色都能在配置里单独换模型(configuration.py),让你把便宜模型用在量大的活、把强模型用在关键的活:
| 角色 | 配置字段 | 默认模型 | 用途 | 定义处 |
|---|---|---|---|---|
| 摘要 | summarization_model | openai:gpt-4.1-mini | 压缩单条搜索结果网页 | configuration.py:121 |
| 研究 | research_model | openai:gpt-4.1 | 驱动 supervisor 和 researcher(拆题、 搜索决策) | configuration.py:153 |
| 压缩 | compression_model | openai:gpt-4.1 | 把一个研究员的全部发现压成摘要 | configuration.py:173 |
| 终稿 | final_report_model | openai:gpt-4.1 | 合成最终报告 | configuration.py:193 |
所有节点都通过同一个 configurable_model(一个 configurable_fields 可运行时改的 init_chat_model,deep_researcher.py:56-58)+ 各自的 .with_config({"model": ...}) 来切换到对应角色的模型。除了模型,还有并发上限 max_concurrent_research_units(默认 5,configuration.py:64)、主管迭代上限 max_researcher_iterations(默认 6,configuration.py:94)、单研究员工具调用上限 max_react_tool_calls(默认 10,configuration.py:107)等关键旋钮。
4. 主线走一遍(高层,不进代码)
跟着一个问题走完全程,只讲"经过谁、传了什么",细节留给后续章节:
-
进澄清关。 用户问题进
clarify_with_user。若配置关了澄清、或模型判断问题够清楚,直接放行到写纲要;否则抛出一个反问、结束本轮等用户补充(deep_researcher.py:75-115)。 -
写研究纲要。
write_research_brief用研究模型把整段对话浓缩成一句聚焦的research_brief,并把它作为HumanMessage、连同主管系统提示一起塞进supervisor_messages,交棒给 supervisor 子图(deep_researcher.py:149-175)。 -
主管拆题派活。 supervisor 读纲要,决定调用哪些工具:
think_tool做规划反思、ConductResearch派子课题、ResearchComplete收工(deep_researcher.py:202)。 -
并行研究。
supervisor_tools把本轮所有ConductResearch截到并发上限,用asyncio.gather并行跑多个 researcher 子图(deep_researcher.py:291-305)。每个研究员在自己的 ReAct 循环里搜索、反思,查完由compress_research压成摘要返回。 -
回主管,循环或收尾。 主管把各研究员的压缩结果收进消息继续下一轮;当喊
ResearchComplete、超过迭代上限、或没有工具调用时,子图结束,把汇总的notes交回主图(deep_researcher.py:255-262)。 -
写终稿。
final_report_generation把所有notes拼起来,用终稿模型生成最终报告;若 token 超限就按模型上限渐进截断 findings 重试(deep_researcher.py:634-697)。产出final_report,主图结束。
5. 巧妙之处(可带走的精华)
-
"图套图"的三层嵌套 + 每层独立 state。 子图直接被当节点挂进外层(
deep_researcher.py:710),LangGraph 负责跨层传值,让"主管—研 究员"这种层级团队天然映射成代码结构,而每层只关心自己的字段(state.py:65-90)。 -
fan-out 用
asyncio.gather,一句话实现并行子研究员。 不需要专门的并发框架,列表推导出多份子图 invoke、gather 收齐即可(deep_researcher.py:295-305),并用切片做并发上限保护(deep_researcher.py:291-292)。 -
一份
configurable_model,四个角色随处切换。 用init_chat_model(configurable_fields=...)(deep_researcher.py:56-58)+ 各节点.with_config就能给不同环节配不同模型,兼顾成本与质量(configuration.py:121-212)。 -
override_reducer:同一字段既能累加又能覆盖。 通过在写入值里带{"type": "override"}标记,同一个 reducer 支持两种语义——重置 supervisor 上下文时覆盖、并行分支汇总 notes 时累加(state.py:55-60,用例见deep_researcher.py:167-172、622)。 -
"够清楚就别问"的前置澄清。 用结构化输出
ClarifyWithUser让模型自己判断要不要反问,可一键关闭(deep_researcher.py:75、state.py:30-41),在自动化程度和交互质量之间留了开关。 -
压缩与终稿两处都内建 token 超限重试。 研究员压缩时按需丢弃旧消息重试(
deep_researcher.py:565-574),终稿生成时按模型上限渐进截断 findings(deep_researcher.py:663-683)——把 LLM 最常见的失败模式(上下文爆掉)当一等公民处理。详见 03-compression-token-limits.md。
6. 阅读地图
建议顺序(由浅入深):
| 顺序 | 章节 | 讲什么 | 什么时候读 |
|---|---|---|---|
| 0 | index.md(本章) | 全景 / 主线 / 路由 | 先读,建立心智模型 |
| 1 | 01-orchestration-graph.md | 三层 LangGraph 怎么建、state 与 reducer 怎么流转 | 想搞懂"图套图"和状态传递 |
| 2 | 02-supervisor-researcher-loop.md | supervisor 委派 + researcher ReAct + 并行 fan-out | 想搞懂核心研究引擎 |
| 3 | 03-compression-token-limits.md | 研究压缩、token 超限重试、终稿生成 | 想搞懂容错与收尾 |
| 4 | 04-tools-search-mcp.md | 搜索抽象、Tavily 摘要管线、MCP 集成与鉴权 | 想搞懂工具装配与外部集成 |
7. 代码地图(导航索引)
| 主题 | 文件 | 符号 |
|---|---|---|
| 图入口登记 | langgraph.json | graphs["Deep Researcher"] → deep_researcher |
| 主图建图 | src/open_deep_research/deep_researcher.py | deep_researcher_builder / deep_researcher |
| 澄清节点 | src/open_deep_research/deep_researcher.py | clarify_with_user |
| 写研究纲要 | src/open_deep_research/deep_researcher.py | write_research_brief |
| supervisor 子图 | src/open_deep_research/deep_researcher.py | supervisor_builder / supervisor_subgraph |
| 主管逻辑 / 派活 | src/open_deep_research/deep_researcher.py | supervisor / supervisor_tools |
| researcher 子图 | src/open_deep_research/deep_researcher.py | researcher_builder / researcher_subgraph |
| 研究员 ReAct | src/open_deep_research/deep_researcher.py | researcher / researcher_tools |
| 研究压缩 | src/open_deep_research/deep_researcher.py | compress_research |
| 终稿生成 | src/open_deep_research/deep_researcher.py | final_report_generation |
| 可配置模型句柄 | src/open_deep_research/deep_researcher.py | configurable_model |
| 四个 LLM 角色 + 旋钮 | src/open_deep_research/configuration.py | Configuration(research_model / summarization_model / compression_model / final_report_model) |
| 三层 state | src/open_deep_research/state.py | AgentState / SupervisorState / ResearcherState |
| 覆盖/累加 reducer | src/open_deep_research/state.py | override_reducer |
| 结构化输出模型 | src/open_deep_research/state.py | ClarifyWithUser / ResearchQuestion / ConductResearch / ResearchComplete |