deerflow-code/docs/gpt-researcher-deerflow-集成开发方案.md
2026-09-07 18:24:55 +08:00

1746 lines
70 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# GPT Researcher 接入 DeerFlow 的可实施开发方案
> 文档状态:持续实现中;本次已补齐报告实时写作、对话工作台与浏览器端 Word 导出
> 编写日期:2026-08-14
> GPT Researcher 核对基线:[`5d84d2f5553e70a2765a8ff3a0d2672d60437ce8`](https://github.com/assafelovic/gpt-researcher/tree/5d84d2f5553e70a2765a8ff3a0d2672d60437ce8)
> 上游本地代码:`F:\react01\deeflow-code\gpt-researcher`(当前 HEAD 与上述基线一致)
> DeerFlow 基线:当前工作区代码
> 本文只描述新增的「深度研究」功能,不改造、不依赖现有 AI 写作功能。
## 1. 文档目标
本文回答以下工程问题:
1. GPT Researcher 的哪些功能可以在 DeerFlow 中实现。
2. 哪些上游代码适合直接复用,哪些代码只能复用设计,哪些代码不应接入。
3. 如何让复用代码运行在 DeerFlow 当前的 LangGraph 1.x、模型、检索、MCP、技能、用户体系和网关体系中。
4. 后端需要新增哪些包、类、数据表、API、后台任务和测试。
5. 前端新页面需要新增哪些路由、组件、状态和交互。
6. 如何分阶段交付,并用明确验收标准避免“页面有了,但能力对不上”。
本文是实现级设计,不是简单功能介绍。文中提到的文件名、类名、接口和状态均作为后续开发的默认方案;实际编码时如需调整,应同步更新本文。
---
## 2. 结论摘要
### 2.1 可行性结论
可以做,而且不需要安装 GPT Researcher 锁定的旧版 LangGraph,更不需要把 DeerFlow 降级。
GPT Researcher 当前真正直接使用 LangGraph 的生产代码主要在:
- [`multi_agents/agents/orchestrator.py`](https://github.com/assafelovic/gpt-researcher/blob/5d84d2f5553e70a2765a8ff3a0d2672d60437ce8/multi_agents/agents/orchestrator.py)
- [`multi_agents/agents/editor.py`](https://github.com/assafelovic/gpt-researcher/blob/5d84d2f5553e70a2765a8ff3a0d2672d60437ce8/multi_agents/agents/editor.py)
其中使用的 `StateGraph`、`END`、`add_node`、`add_edge`、`add_conditional_edges`、`compile`、`ainvoke` 在 DeerFlow 当前 LangGraph 1.x 中仍有对应能力。需要适配的重点不是图 API,而是:
- 把上游阻塞式人工反馈改成 LangGraph 1.x 的 `interrupt()` 与 `Command(resume=...)`。
- 给图注入 DeerFlow checkpointer 和线程配置。
- 把上游 LLM Provider 替换为 DeerFlow 的 `create_chat_model()`。
- 把上游搜索、抓取、MCP 和文档加载全部替换为 DeerFlow 的“研究材料提供器”。
- 把内存中的运行过程改为 DeerFlow 可恢复、可取消、可多实例接管的后台任务。
### 2.2 推荐接入方式
不建议把整个 GPT Researcher 仓库复制进来,也不建议以子进程、独立服务或 Git submodule 的形式运行。
推荐方式是:
1. 将经过筛选的上游核心代码 vendoring 到 DeerFlow 的独立 `deep_research` 包。
2. 保持上游研究流程和报告生成方法的主体结构,减少功能偏差。
3. 在四个边界上使用 DeerFlow Adapter:模型、研究材料、事件、产物。
4. 搜索结果一律由 DeerFlow 提供完整正文和元数据,使 GPT Researcher 不再自行联网、抓取或调用 MCP。
5. 新建独立前端入口 `/page/strategy/qa/deep-research`,不进入 `open-canvas`,不修改 AI 写作状态机。
### 2.3 第一版的能力范围
第一版建议支持:
- 普通研究报告。
- 详细报告,按子主题并行研究和合并。
- Deep Research,按宽度和深度递归研究。
- 多智能体报告编审,包括计划、研究、写作、事实核查、修订、发布。
- DeerFlow 搜索、知识库、技能和 MCP 作为统一信息来源。
- 研究计划、实时进度、来源库、引用、Markdown/HTML 报告。
- 后台执行、刷新恢复、断线重连、取消、重试、人工审核后继续。
- 历史会话、报告追问、产物下载。
PDF 自动导出仍建议做成可选能力。DOCX 已改为复用前端现有 `docx` 依赖在浏览器本地生成:完成态报告导出为 Word 时会附带收集到的参考来源,不新增 Python 或 LangGraph 依赖。私有报告配图暂不嵌入 Word;若后续需要嵌图,应通过鉴权产物接口取得 Blob 后写入 `ImageRun`。
---
## 3. 范围与非目标
### 3.1 本期范围
- 新增独立「深度研究」功能和页面。
- 复用 GPT Researcher 的研究规划、上下文处理、报告生成和多智能体编审逻辑。
- 使用 DeerFlow 当前的信息收集能力,不使用 GPT Researcher 自带检索链路。
- 支持 LangGraph 1.x。
- 接入 DeerFlow 当前登录用户、权限、模型配置、持久化、后台作业、SSE 和产物体系。
- 上游代码以固定 commit 为基线,保留来源、许可证和本地补丁说明。
### 3.2 明确非目标
- 不复用或改造 AI 写作页面、上下文、状态机和接口。
- 不启动 GPT Researcher 自带 FastAPI/Next.js 服务。
- 不接入 GPT Researcher 自带搜索 Provider、scraper、MCP、向量库和本地文档加载器。
- 不在运行期动态 `pip install` 模型 Provider。
- 不为兼容上游而降低 DeerFlow 的 LangGraph 或 LangChain 版本。
- 不使用上游 AG2 目录的另一套多智能体实现。
- 不在第一期提供任意用户自定义 MCP 配置或任意 URL 抓取,以免绕过 DeerFlow 安全边界。
---
## 4. 目标功能清单
### 4.1 研究模式
| 模式 | 用户用途 | GPT Researcher 基础 | DeerFlow 中的实现 |
|---|---|---|---|
| 快速研究 | 快速得到带来源的短答案 | `quick_search()` | 可同步或短后台任务,仍由 DeerFlow 收集材料 |
| 标准报告 | 一次规划、检索、压缩、成文 | `GPTResearcher.conduct_research()` + `write_report()` | 第一阶段主路径 |
| 详细报告 | 总体研究后按子主题并行展开 | `DetailedReport` | 子主题任务并行,最终合并引言和结论 |
| 深度研究 | 按 breadth/depth 递归探索 | `DeepResearchSkill` | 有界递归、分支事件、统一去重和取消 |
| 多智能体编审 | 有计划、复核、修订、事实核查 | `ChiefEditor` + `EditorAgent` | LangGraph 1.x 图,支持暂停和恢复 |
| 自定义报告 | 用户给定报告结构或提示 | upstream custom report/prompt | 已支持:受限 `custom_outline` 保存到配置快照;标题/编号列表决定详细报告和多智能体章节,其他模式作为受保护的写作结构约束 |
### 4.2 用户可见能力
1. 创建研究课题。
2. 选择报告模式、语言、语气、模型和研究强度。
3. 查看自动生成的研究计划。
4. 在开启人工审核时,修改或批准研究计划。
5. 实时查看:当前阶段、搜索问题、材料数量、子主题、递归分支、写作和核查进度。
6. 查看来源列表、来源正文摘要、来源类型、时间、相关度和被引用情况;研究停止后可调整后续追问使用的来源集合。
7. 查看流式报告预览。
8. 下载 Markdown、HTML 和浏览器本地生成的 Word;PDF 仍按部署能力决定是否开放。
9. 研究完成后继续针对本次报告和材料追问。
10. 取消正在运行的任务;刷新页面后恢复状态;服务重启后自动续跑或重新领取。
11. 查看、重命名、删除历史研究会话。
### 4.3 管理和运维能力
- 并发、最大搜索次数、材料正文长度、递归深度和宽度限制。
- 每用户同时运行任务数限制。
- 模型调用和 Token 使用记录。
- 任务租约、心跳、失败原因和重试次数。
- 来源可追溯,报告引用能回到具体材料。
- 不可用能力的显式降级,例如未安装 PDF 导出依赖时只展示 MD/HTML。
---
## 5. 总体架构
```mermaid
flowchart LR
UI["深度研究新页面"] --> API["Deep Research Gateway API"]
API --> SS["Session Store"]
API --> JS["Durable Job Store"]
API --> SSE["SSE 事件流"]
JS --> DISP["Job Dispatcher"]
DISP --> EXEC["Job Executor"]
EXEC --> ENGINE["DeepResearchEngine"]
ENGINE --> BASIC["GPT Researcher 核心流程"]
ENGINE --> MULTI["LangGraph 1.x 多智能体图"]
BASIC --> LLM["DeerFlow LLM Adapter"]
MULTI --> LLM
BASIC --> MATERIAL["DeerFlow Material Provider"]
MULTI --> MATERIAL
MATERIAL --> SEARCH["现有 web_search"]
MATERIAL --> KB["现有知识库/技能"]
MATERIAL --> MCP["现有 MCP 工具"]
MATERIAL --> FETCH["现有正文获取工具"]
ENGINE --> EVENT["Event Sink"]
EVENT --> ES["Event Store"]
ES --> SSE
ENGINE --> ART["Artifact Adapter"]
ART --> OUTPUTS["DeerFlow thread outputs"]
```
核心原则是“研究算法可以复用,基础设施必须由 DeerFlow 接管”。上游算法不能直接持有网络、模型、文件系统或 WebSocket,它只能依赖明确的协议接口。
---
## 6. 上游代码复用清单
### 6.0 上游本地源码定位
上游代码已克隆到:
```text
F:\react01\deeflow-code\gpt-researcher
```
当前版本固定为 `5d84d2f5553e70a2765a8ff3a0d2672d60437ce8`。本章所有 `gpt_researcher/...`、`multi_agents/...` 和 `backend/...` 路径均相对于该目录;实施时以该本地目录作为 vendoring 的唯一来源,不从浮动分支重新下载文件。
当前实现的可执行代码是对这些控制流的 DeerFlow 适配,而非直接 import 上游包(上游 Provider、检索/抓取和 MCP 栈不能越过 DeerFlow 的安全边界)。固定 commit、许可证、逐文件映射、不可突破的适配约束和后续直接 vendoring 的补充义务,见 `offline-backend-20260512/backend/packages/harness/deerflow/agents/deep_research/UPSTREAM.md`。
最关键的源码位置如下:
| 用途 | 本地文件 |
|---|---|
| 研究总入口 `GPTResearcher` | `F:\react01\deeflow-code\gpt-researcher\gpt_researcher\agent.py` |
| 研究规划、并行子查询、上下文聚合 | `F:\react01\deeflow-code\gpt-researcher\gpt_researcher\skills\researcher.py` |
| 报告、引言、结论和子主题写作 | `F:\react01\deeflow-code\gpt-researcher\gpt_researcher\skills\writer.py` |
| breadth/depth 递归深度研究 | `F:\react01\deeflow-code\gpt-researcher\gpt_researcher\skills\deep_research.py` |
| 来源策展 | `F:\react01\deeflow-code\gpt-researcher\gpt_researcher\skills\curator.py` |
| 上游 LLM 调用总入口(需改成 DeerFlow Adapter) | `F:\react01\deeflow-code\gpt-researcher\gpt_researcher\utils\llm.py` |
| 多智能体总图 | `F:\react01\deeflow-code\gpt-researcher\multi_agents\agents\orchestrator.py` |
| 多智能体子主题编审图 | `F:\react01\deeflow-code\gpt-researcher\multi_agents\agents\editor.py` |
| 标准报告调用顺序 | `F:\react01\deeflow-code\gpt-researcher\backend\report_type\basic_report\basic_report.py` |
| 详细报告调用顺序 | `F:\react01\deeflow-code\gpt-researcher\backend\report_type\detailed_report\detailed_report.py` |
后续复制到 `deerflow/agents/deep_research/vendor/` 时,必须在 `vendor/manifest.json` 保存上表中每个源文件的相对路径、上游 commit 和 SHA-256,保证可追溯和可升级。
### 6.1 直接 vendoring 后适配的代码
下表中的“复用”表示复制到 DeerFlow 仓库并保留主体算法,不表示原文件零修改。
| 上游路径 | 复用内容 | 本地适配点 |
|---|---|---|
| `gpt_researcher/agent.py` | `GPTResearcher` 总调度、研究、报告、子主题、引用、图像入口 | 构造函数注入 LLM、材料、事件、产物和取消令牌;移除环境变量驱动 |
| `gpt_researcher/skills/researcher.py` | `ResearchConductor` 的规划、子查询并行、正文处理、摘要和上下文聚合 | 搜索和抓取改调 `MaterialProvider`;保留完整 DeerFlow 来源元数据 |
| `gpt_researcher/skills/writer.py` | `ReportGenerator` 的报告、引言、结论、子主题和标题生成 | 调 DeerFlow LLM Adapter;事件化输出;引用绑定 source id |
| `gpt_researcher/skills/deep_research.py` | 搜索计划、递归深挖、学习点合并、访问 URL 去重 | 修复空查询变量初始化问题;注入 Adapter;增加全局预算和取消检查 |
| `gpt_researcher/skills/curator.py` | 来源筛选和排序 | 使用 DeerFlow 元数据;策展前后均记录选择原因 |
| `gpt_researcher/skills/context_manager.py` | 上下文管理接口与调用关系 | 压缩器换成 DeerFlow 实现 |
| `gpt_researcher/prompts.py` | 报告类型、研究和引用提示词族 | 保留上游语义;配置默认中文;禁止请求直接覆盖系统安全提示 |
| `gpt_researcher/actions/agent_creator.py` | 研究代理角色生成 | LLM 调用换 Adapter |
| `gpt_researcher/actions/query_processing.py` | 查询生成、子主题和查询清洗 | LLM 调用换 Adapter,输出做 Pydantic 校验 |
| `gpt_researcher/actions/report_generation.py` | 报告段落、完整报告和结论生成 | LLM 调用换 Adapter,支持分块事件 |
| `gpt_researcher/actions/markdown_processing.py` | Markdown 整理、引用和链接处理 | 加 source id 绑定,过滤危险 HTML |
| `gpt_researcher/utils/enum.py` | 报告类型、语气等枚举 | 转换成 DeerFlow 配置可序列化枚举 |
| `gpt_researcher/utils/rate_limiter.py` | 限流算法 | 接入每任务/每用户预算,不在全进程使用隐式全局 |
| `gpt_researcher/utils/workers.py` | 并发任务辅助 | 接收统一 semaphore 和取消令牌 |
| `gpt_researcher/utils/validators.py` | 部分文本和 URL 校验 | 与 DeerFlow 安全规则合并 |
| `backend/report_type/basic_report/basic_report.py` | 标准报告的调用顺序 | 改成 `BasicResearchRunner` |
| `backend/report_type/detailed_report/detailed_report.py` | 子主题拆分、并行报告、总报告拼接 | 改成 `DetailedResearchRunner`,并发受任务预算限制 |
### 6.2 多智能体可复用代码
| 上游路径 | 复用内容 | 必改内容 |
|---|---|---|
| `multi_agents/agents/orchestrator.py` | 总图节点和条件路由 | LangGraph 1.x checkpointer、thread id、暂停恢复、事件输出 |
| `multi_agents/agents/editor.py` | 子主题 research-review-revise 图 | 1.x 编译、并发上限、取消和错误传播 |
| `multi_agents/agents/researcher.py` | 子主题研究代理 | DeerFlow 材料提供器 |
| `multi_agents/agents/reviewer.py` | 草稿审阅 | DeerFlow LLM Adapter,结构化输出校验 |
| `multi_agents/agents/reviser.py` | 根据评审修改 | 版本化草稿和事件记录 |
| `multi_agents/agents/writer.py` | 章节和全文写作 | DeerFlow LLM Adapter,引用校验 |
| `multi_agents/agents/fact_checker.py` | 事实核查及回环判断 | 来源必须来自 session source store;限制最大回环次数 |
| `multi_agents/agents/visualizer.py` | 图表/图片建议和位置 | 第一阶段可关闭;启用时调用 DeerFlow 图像能力 |
| `multi_agents/agents/publisher.py` | 最终拼接和发布 | 产物路径换成 Artifact Adapter |
| `multi_agents/agents/human.py` | 人工审核所处流程位置 | 不复用阻塞输入实现,重写为 `interrupt()` |
| `multi_agents/memory/research.py` | 研究状态结构 | 改成 TypedDict/Pydantic 可持久化状态 |
| `multi_agents/memory/draft.py` | 草稿和修订状态结构 | 加 revision、source ids、序列化约束 |
多智能体流程保持如下语义:
```mermaid
flowchart TD
B["浏览/初始材料"] --> P["生成研究计划"]
P --> H{"需要人工审核?"}
H -->|修改计划| P
H -->|批准| R["研究各子主题"]
H -->|关闭人工审核| R
R --> W["写作"]
W --> F{"事实核查通过?"}
F -->|否且未超过回环上限| W
F -->|是| V["图表与插图"]
V --> PUB["发布报告与产物"]
subgraph SUB["每个子主题内部"]
SR["Researcher"] --> REV["Reviewer"]
REV --> OK{"通过?"}
OK -->|否| FIX["Reviser"]
FIX --> REV
end
```
### 6.3 只复用协议或思路,不复制实现
| 上游路径 | 可借鉴内容 | 不直接复制原因 |
|---|---|---|
| `gpt_researcher/retrievers/custom/custom.py` | `{url, raw_content}` 的预取材料契约 | 使用同步 `requests` 和环境变量 endpoint,缺少认证、元数据和取消 |
| `gpt_researcher/actions/retriever.py` | 检索器工厂的边界概念 | 内含大量上游 Provider 分支,与 DeerFlow 重复 |
| `gpt_researcher/context/compression.py` | 分块、相似度筛选、上下文压缩顺序 | 依赖当前 DeerFlow 未声明的 `langchain_classic`、`langchain_text_splitters` |
| `gpt_researcher/skills/image_generator.py` | 生成图片提示和插入位置 | 图片执行应走 DeerFlow 已配置工具,不能直接持有上游 Provider |
| `backend/utils.py` | MD、PDF、DOCX 转换步骤 | 原始输出目录与依赖不适合 DeerFlow 部署 |
| `multi_agents/agents/utils/file_formats.py` | 输出格式转换概念 | 需要统一路径防护和能力探测 |
### 6.4 明确不复用的代码
| 上游目录 | 原因 |
|---|---|
| `gpt_researcher/retrievers/*` | 信息收集由 DeerFlow 负责,避免 Provider 重复和配置分裂 |
| `gpt_researcher/scraper/*` | 抓取、反爬、SSRF 和网络策略必须由 DeerFlow 控制 |
| `gpt_researcher/mcp/*` | DeerFlow 已有 MCP 生命周期、缓存、认证和工具白名单 |
| `gpt_researcher/document/*` | 本地文件和网页材料统一进入 DeerFlow 材料协议 |
| `gpt_researcher/vector_store/*` | 第一阶段不引入第二套向量存储;需要时接 DeerFlow 知识库 |
| `gpt_researcher/llm_provider/generic/base.py` | 存在动态 import/安装 Provider 的设计,不适合离线可重复部署 |
| `backend/server/app.py`、`server_utils.py`、`websocket_manager.py` | 是上游演示服务,认证、多租户、持久化和并发模型均不符合 DeerFlow 网关 |
| 上游 Next.js 前端 | 当前项目是 Vite + React 19,直接复制会引入第二套路由和组件体系 |
| `multi_agents/ag2/*` | 与 LangGraph 实现重复,并引入额外 Agent 框架和依赖 |
| 上游 Docker Compose | DeerFlow 已有离线部署与统一启动脚本 |
---
## 7. 建议新增目录和文件
### 7.1 后端 Harness 层
```text
offline-backend-20260512/backend/packages/harness/deerflow/agents/deep_research/
├── __init__.py
├── config.py
├── types.py
├── events.py
├── cancellation.py
├── engine.py
├── runners/
│ ├── __init__.py
│ ├── basic.py
│ ├── detailed.py
│ ├── deep.py
│ └── multi_agent.py
├── adapters/
│ ├── __init__.py
│ ├── llm.py
│ ├── material.py
│ ├── context.py
│ ├── image.py
│ ├── artifacts.py
│ └── event_sink.py
└── vendor/
├── UPSTREAM.md
├── PATCHES.md
├── LICENSE
├── manifest.json
├── gpt_researcher/
│ └── ...仅选定文件...
└── multi_agents/
└── ...仅 LangGraph 版本选定文件...
```
职责边界:
- `engine.py`:只决定使用哪个 runner,并组装 Adapter。
- `runners/*`:把统一输入映射到上游流程,不能直接访问 FastAPI 或数据库。
- `adapters/*`:隔离 DeerFlow 基础设施和上游代码。
- `vendor/*`:尽量保留上游算法,所有 DeerFlow 特有代码放在 vendor 外。
- Harness 层严禁 `import app.*`,必须继续通过 `tests/test_harness_boundary.py`。
### 7.2 持久化层
```text
offline-backend-20260512/backend/packages/harness/deerflow/persistence/deep_research/
├── __init__.py
├── models.py
└── sql.py
```
同时需要:
- 在 `deerflow/persistence/models/__init__.py` 导入新 ORM model,保证 `create_all` 可发现。
- 为正式部署增加 Alembic migration;不能只依赖开发环境的 `create_all`。
- 表字段使用项目已有的 `PortableLongText`、`BeijingDateTime` 等兼容类型。
### 7.3 App/Gateway 层
```text
offline-backend-20260512/backend/app/gateway/
├── routers/deep_research.py
├── deep_research_job_executor.py
└── deep_research_job_dispatcher.py
```
修改点:
- `app/gateway/app.py`:注册 `deep_research.router`。
- `app/gateway/deps.py`:创建 session/job/event/source/message repository,注册 dispatcher 和 executor 生命周期。
- 如使用隐藏 runtime thread:线程列表接口需要排除 `metadata.hidden == true` 的内部线程。
### 7.4 前端
当前 Phase 2 已落地为与现有 Vite + React 页面目录一致的轻量实现:
```text
frontend-web/src/pages/DeepResearchPage.tsx
frontend-web/src/strategy-components/api/deep-research.ts
frontend-web/src/pages/StrategyRoutes.tsx
frontend-web/src/core/page-layout/sidebar-menu.ts
```
其中 `DeepResearchPage` 同时承载创建表单、历史会话、对话式 SSE 研究过程、流式报告草稿、来源库、报告预览和 MD/HTML/Word 下载。后续在页面复杂度明显增加时,可按下方的目标目录拆分;不能为了目录形式而重复实现另一套状态或 API client。
```text
frontend-web/src/deep-research/
├── routes/DeepResearchRoutes.tsx
├── pages/DeepResearchHomePage.tsx
├── pages/DeepResearchSessionPage.tsx
├── api/deep-research.ts
├── lib/types.ts
├── lib/event-reducer.ts
├── hooks/useDeepResearchSession.ts
├── hooks/useDeepResearchStream.ts
└── components/
├── ResearchSetupPanel.tsx
├── SessionHistoryPanel.tsx
├── ResearchProgressTimeline.tsx
├── ResearchPlanPanel.tsx
├── SourceLibraryPanel.tsx
├── SourceDetailDrawer.tsx
├── ReportPreview.tsx
├── ArtifactDownloads.tsx
└── ResearchChatPanel.tsx
```
路由修改:
- 当前实现已在 `frontend-web/src/pages/StrategyRoutes.tsx` 增加:
```tsx
<Route path="qa/deep-research" element={<DeepResearchPage />} />
```
- 在 `frontend-web/src/core/page-layout/sidebar-menu.ts` 的「问答管理」下增加:
```ts
{
id: "qa-deep-research",
label: "深度研究",
icon: Telescope,
path: "/page/strategy/qa/deep-research",
}
```
该菜单继续经过现有动态菜单覆盖、权限过滤和敏感词展示逻辑,不单独实现另一套侧边栏。
---
## 8. 核心领域模型
### 8.1 研究配置 `DeepResearchConfig`
文件:`deerflow/agents/deep_research/config.py`
```python
class DeepResearchConfig(BaseModel):
mode: Literal["quick", "basic", "detailed", "deep", "multi_agent"]
language: str = "zh-CN"
tone: Literal["objective", "formal", "analytical", "persuasive"] = "objective"
report_format: Literal["markdown", "apa", "mla", "chicago"] = "markdown"
fast_model: str | None = None
smart_model: str | None = None
strategic_model: str | None = None
thinking_enabled: bool = False
max_search_results_per_query: int = 8
max_iterations: int = 3
max_subtopics: int = 8
deep_breadth: int = 4
deep_depth: int = 2
max_concurrency: int = 4
max_context_words: int = 24_000
curate_sources: bool = True
generate_images: bool = False
include_human_feedback: bool = False
max_revision_loops: int = 2
allowed_material_channels: list[str] = ["web_search"]
allowed_domains: list[str] = []
custom_outline: str | None = None
```
约束:
- 每个任务开始时把完整配置保存为 `config_snapshot`,任务运行中不读取可变全局配置。
- 所有数值字段设置服务端上下限,前端限制不能代替服务端限制。
- 模型名只允许来自 DeerFlow 已注册模型列表。
- `allowed_material_channels` 只允许管理员配置的工具组,不接受任意 MCP server URL。
- 不使用 `os.environ` 保存每次请求的模型、报告类型或检索器设置。
### 8.2 统一研究材料 `ResearchMaterial`
文件:`deerflow/agents/deep_research/types.py`
```python
class ResearchMaterial(BaseModel):
id: str
query: str
title: str
url: str | None = None
raw_content: str
snippet: str | None = None
source: str
source_type: Literal["web", "knowledge", "skill", "mcp", "file", "other"]
published_at: datetime | None = None
relevance_score: float | None = None
rec_uuid: str | None = None
content_hash: str
metadata: dict[str, Any] = {}
```
该类型比上游 `{url, raw_content}` 更完整。写入上游上下文时至少保留:
```python
{
"id": material.id,
"title": material.title,
"url": material.url,
"raw_content": material.raw_content,
"source": material.source,
"source_type": material.source_type,
"published_at": material.published_at,
"relevance_score": material.relevance_score,
"recUuid": material.rec_uuid,
"metadata": material.metadata,
}
```
必须修改上游 `ResearchConductor._search_relevant_source_urls()`:当前上游识别到 `raw_content` 后只保留 URL 和正文,会丢失标题、来源和业务记录 ID。DeerFlow 版本不得丢弃这些字段,否则报告引用无法回到当前信息收集系统。
### 8.3 材料提供协议
```python
class MaterialProvider(Protocol):
async def search(
self,
query: str,
*,
domains: list[str] | None,
limit: int,
user_id: str,
session_id: str,
cancellation: CancellationToken,
) -> list[ResearchMaterial]: ...
```
`DeerFlowMaterialProvider` 的第一版实现步骤:
1. 复用 `deerflow/community/configurable_search/tools.py` 中的 `ConfigurableSearchSettings`、`execute_search()` 和结果规范化结构。
2. 将现有结果字段 `recUuid/content/title/url/source/time/publish_time` 映射成 `ResearchMaterial`。
3. 如果 `content` 是完整正文,直接作为 `raw_content`,GPT Researcher 不再抓取此 URL。
4. 如果只有简短 snippet,调用 DeerFlow 当前被允许的正文获取工具补齐;该步骤仍发生在 Adapter 内。
5. 如配置允许知识库、技能或 MCP,则通过 `deerflow/tools/tools.py:get_available_tools()` 获取白名单工具,分别调用后归一化。
6. 按优先级使用 `rec_uuid`、canonical URL、`content_hash` 去重。
7. 写入 `deep_research_sources`,再把材料交给上游算法。
第二阶段可把材料渠道做成 registry,但不要让 vendor 代码知道渠道名称或工具实现。
### 8.4 研究输入与输出
```python
class DeepResearchRequest(BaseModel):
session_id: str
user_id: str
query: str
config: DeepResearchConfig
request_id: str
class DeepResearchResult(BaseModel):
report_markdown: str
report_html: str | None
source_ids: list[str]
artifact_paths: dict[str, str]
usage: dict[str, Any]
plan: dict[str, Any] | None
```
---
## 9. DeerFlow 模型适配
### 9.1 复用入口
复用当前:
```text
offline-backend-20260512/backend/packages/harness/deerflow/models/factory.py
└── create_chat_model(...)
```
不要复用 GPT Researcher 的 `GenericLLMProvider`。后者按 Provider 动态加载依赖,不符合本项目离线和锁定依赖的方式。
### 9.2 Adapter 设计
文件:`deerflow/agents/deep_research/adapters/llm.py`
```python
class DeerFlowCompletionBackend:
def __init__(self, config: DeepResearchConfig, event_sink: EventSink): ...
async def complete(
self,
*,
model_role: Literal["fast", "smart", "strategic"],
messages: list[dict[str, str]],
temperature: float | None = None,
response_schema: type[BaseModel] | None = None,
operation: str,
) -> CompletionResult: ...
```
内部映射:
```python
model_name = resolve_model_name(config, model_role)
model = create_chat_model(
name=model_name,
thinking_enabled=config.thinking_enabled,
)
response = await model.ainvoke(to_langchain_messages(messages))
```
适配上游的最佳切入点是 `gpt_researcher/utils/llm.py`:保留 `get_llm()`、`create_chat_completion()`、`construct_subtopics()` 等方法签名,使大多数上游 action/skill 不需要修改;其内部委托给当前请求上下文中的 `DeerFlowCompletionBackend`。
### 9.3 上下文注入
不得通过模块全局变量保存当前用户、任务或模型。可选方案:
- 首选:显式构造函数注入到 `GPTResearcher`、`ResearchConductor`、`ReportGenerator`。
- 对不适合大范围改签名的上游函数:使用只在当前 async task 生效的 `contextvars.ContextVar` 保存 Adapter bundle。
如果使用 `ContextVar`,必须提供 `bind_adapters()` context manager,并在 executor 的 `finally` 中 reset,避免协程复用造成串任务。
### 9.4 Token 与费用
- 优先读取 LangChain `AIMessage.usage_metadata`。
- 记录 prompt、completion、total tokens 和模型名,不记录完整系统提示或敏感材料。
- GPT Researcher 的静态价格表只作为缺省估算;DeerFlow 没有价格配置时显示 Token,不伪造金额。
- 每次调用产生 `model_usage` 事件,同时累加到 session 和 job snapshot。
### 9.5 重试规则
- 只保留一个明确的重试层,避免 DeerFlow Provider 重试和上游重试相乘。
- 结构化输出解析失败可进行一次“修复 JSON”模型调用;不新增 `json-repair` 强制依赖。
- 取消请求、权限错误、配置错误不重试。
---
## 10. 上下文压缩适配
GPT Researcher 上游压缩器依赖本项目未声明的旧式拆分包,因此第一版新增 `DeerFlowContextCompressor`,保持上游调用接口但不引入旧依赖。
建议接口:
```python
class DeerFlowContextCompressor:
async def async_get_context(
self,
query: str,
max_results: int = 10,
cost_callback: Callable | None = None,
) -> list[dict[str, Any]]: ...
```
算法:
1. 按标题、段落和字符上限分块,不破坏 Markdown 代码块。
2. 对每个分块保留 source id,不把多个来源拼成无法追溯的大块。
3. 若部署已配置 embedding,按 query 相似度初筛。
4. 没有 embedding 时,使用关键词/BM25 风格分值和来源相关度进行初筛。
5. 超过上下文预算时,再用 `fast_model` 逐来源摘要。
6. 摘要结果必须带 source id,禁止产生没有出处的新事实。
7. 最终按来源多样性、发布时间和相关度组合,避免一个域名占满上下文。
上下文预算应同时考虑字符数、估算 token 和模型窗口,不只使用单一 word count。
---
## 11. LangGraph 1.x 适配方案
### 11.1 可以保留的图 API
以下上游写法可在实现阶段通过兼容性测试确认并尽量保留:
```python
graph = StateGraph(ResearchState)
graph.add_node("planner", planner)
graph.add_edge("browser", "planner")
graph.add_conditional_edges("fact_checker", route_after_fact_check)
graph.set_entry_point("browser")
compiled = graph.compile(checkpointer=checkpointer)
```
如果项目统一风格要求,可将 `set_entry_point` 改为 `START -> browser`,但不是功能必要条件,不应为了重写而扩大补丁。
### 11.2 人工审核的必要改造
上游 `HumanAgent.review_plan()` 会等待 WebSocket 或控制台 `input()`,不能在 FastAPI 后台任务中使用。改造后节点应返回 LangGraph interrupt:
```python
from langgraph.types import interrupt
async def human_review_node(state: MultiAgentState):
decision = interrupt({
"kind": "research_plan_review",
"plan": state["research_plan"],
"allowed_actions": ["approve", "revise", "cancel"],
})
return apply_review_decision(state, decision)
```
网关恢复任务:
```python
from langgraph.types import Command
await graph.ainvoke(
Command(resume=validated_feedback),
config={"configurable": {"thread_id": graph_thread_id}},
)
```
规则:
- `thread_id` 使用稳定的 `deep-research:{session_id}:{job_id}`。
- graph 必须注入 `deerflow.runtime.checkpointer` 当前配置的 checkpointer。
- 进入 interrupt 时,job 状态更新为 `awaiting_input`,写入 `awaiting_input` 事件,释放执行租约。
- `/resume` 验证用户是 session owner、job 正处于等待状态、action 在本次 interrupt 白名单中。
- 恢复请求使用 `request_id` 幂等,重复点击不能执行两次。
### 11.3 最大回环与取消
- `planner -> human -> planner` 有最大修改次数。
- `writer -> fact_checker -> writer` 受 `max_revision_loops` 限制。
- 子主题内部 `reviewer -> reviser -> reviewer` 同样受限制。
- 每个节点开始、每次外部调用前后都检查 `CancellationToken`。
- LangGraph recursion limit 必须由服务端计算并设置,不能完全接受前端参数。
### 11.4 兼容性测试
新增 `tests/test_deep_research_langgraph_1x.py`:
1. 用 fake planner/researcher/writer 编译总图。
2. 运行到 interrupt,断言 checkpoint 已写入。
3. 使用 `Command(resume=...)` 恢复并完成。
4. 测试 fact-check 回环和最大回环。
5. 在测试中断言运行环境的 LangGraph 主版本为 1,不允许测试通过隐式旧虚拟环境。
该测试是 Phase 0 的硬门槛。静态核对表明上游核心图 API 可迁移,但必须用当前项目锁定环境实际执行一次 contract test 后才能合并 vendor 代码。
---
## 12. 研究引擎与 Runner
### 12.1 统一入口
文件:`deerflow/agents/deep_research/engine.py`
```python
class DeepResearchEngine:
async def run(
self,
request: DeepResearchRequest,
*,
adapters: AdapterBundle,
cancellation: CancellationToken,
) -> DeepResearchResult:
runner = self._runners[request.config.mode]
return await runner.run(request, adapters, cancellation)
```
`AdapterBundle` 包含:
```python
class AdapterBundle(NamedTuple):
llm: DeerFlowCompletionBackend
materials: MaterialProvider
context: DeerFlowContextCompressor
artifacts: ArtifactWriter
events: EventSink
images: ImageProvider | None
```
### 12.2 Basic runner
顺序:
1. `phase_changed(planning)`。
2. 构造注入 Adapter 的 `GPTResearcher`。
3. `conduct_research()`。
4. 研究过程中把每个 query 和 material 写入事件/数据库。
5. `phase_changed(writing)`。
6. `write_report()`。
7. Markdown 引用后处理和引用完整性校验。
8. 写 Markdown/HTML 产物。
9. `report_completed` 和 `artifact_created`。
### 12.3 Detailed runner
复用 `DetailedReport` 的主要步骤:
- `_initial_research`
- `_get_all_subtopics`
- `_generate_subtopic_reports`
- `_get_subtopic_report`
- `_construct_detailed_report`
适配要求:
- 子主题任务共享 session 来源库和全局 URL 去重集。
- 子主题并发受统一 semaphore 限制,不为每个子主题再创建无限并发。
- 单个子主题失败时记录 warning;是否允许部分成功由配置决定。
- 合并时对重复引用进行稳定编号,不能每个章节独立编号后直接拼接。
### 12.4 Deep runner
复用 `DeepResearchSkill` 的:
- `generate_search_queries`
- `generate_research_plan`
- `process_research_results`
- `deep_research`
- `run`
必须包含的本地补丁:
1. 上游空 `serp_queries` 的提前返回分支在若干累计变量初始化前引用变量,先初始化累计变量再判断空查询。
2. 所有嵌套 `GPTResearcher` 实例继续传递同一个 LLM、MaterialProvider、EventSink、session scope 和 CancellationToken。
3. breadth、depth、总 query 数、总材料字符数、总模型调用数同时受硬预算限制。
4. 每个分支产生 `deep_branch_started`、`deep_branch_completed` 或 `warning` 事件。
5. `visited_urls` 不只按原始 URL 比较,使用 canonical URL 和 content hash。
### 12.5 Multi-agent runner
复用 `ChiefEditor` 与 `EditorAgent` 的角色和图语义,执行方式改为:
```python
graph = build_multi_agent_graph(
adapters=adapters,
checkpointer=checkpointer,
config=request.config,
)
await graph.ainvoke(
initial_state,
config={
"configurable": {"thread_id": graph_thread_id},
"recursion_limit": computed_limit,
},
)
```
节点不得自行创建新的上游 LLM Provider 或 retriever。所有节点通过图构建函数闭包或状态依赖拿到 Adapter。
---
## 13. 持久化设计
### 13.1 `deep_research_sessions`
| 字段 | 类型建议 | 说明 |
|---|---|---|
| `id` | string/UUID PK | 会话 ID |
| `user_id` | string + index | 所有者 |
| `title` | string | 默认从 query 生成,可重命名 |
| `query` | long text | 原始课题 |
| `mode` | string | basic/detailed/deep/multi_agent |
| `status` | string + index | draft/running/awaiting_input/completed/failed/cancelled |
| `config_snapshot` | JSON/long text | 创建/启动时的完整配置 |
| `runtime_thread_id` | string unique | 复用 DeerFlow thread outputs 的内部线程 |
| `plan_snapshot` | JSON/long text | 最新研究计划 |
| `report_markdown` | long text | 最新报告 |
| `report_html` | long text nullable | 清洗后的 HTML |
| `source_count` | int | 来源数快照 |
| `usage_snapshot` | JSON/long text | Token/费用统计 |
| `active_job_id` | string nullable | 当前任务 |
| `error` | long text nullable | 面向用户的错误摘要 |
| `created_at/updated_at` | BeijingDateTime | 时间 |
### 13.2 `deep_research_jobs`
该表复用 `deerflow.persistence.roundtable_jobs` 的成熟算法和字段思路,但第一版不直接继承或修改正在运行的 roundtable 表。
关键字段:
- `id`, `session_id`, `user_id`
- `request_id`, `request_hash`
- `active_dedupe_key`,数据库唯一约束保证同一会话只有一个 active job
- `input_snapshot`
- `status`, `phase`, `progress`, `current_query`
- `lease_owner`, `lease_until`
- `attempt`, `version`
- `cancel_requested`, `cancel_requested_at`
- `error_code`, `error_message`
- `created_at`, `started_at`, `updated_at`, `finished_at`
需要复用/等价实现的方法:
- `compute_active_dedupe_key()`
- `try_create_or_get_active()`
- `claim_lease()` / `renew_lease()` / `release_lease()`
- 基于 `version` 的 CAS 状态更新
- `request_cancel()`
- 过期 lease 回收
如果后续多个领域都需要相同作业能力,可单独提取通用 `DurableJobRepository`;首期不要为了抽象而重构现有 roundtable 代码。
### 13.3 `deep_research_events`
| 字段 | 说明 |
|---|---|
| `id` | 数据库主键 |
| `job_id`, `session_id` | 索引 |
| `seq` | job 内严格递增序号,唯一约束 `(job_id, seq)` |
| `event_type` | 事件类型 |
| `phase` | 当前阶段 |
| `payload` | JSON/long text |
| `created_at` | 时间 |
事件必须先持久化再推送 SSE。SSE 客户端断线后用 `after=seq` 或 `Last-Event-ID` 补发,不能只依赖内存队列。
### 13.4 `deep_research_sources`
保存 `ResearchMaterial` 的完整字段,并增加:
- `session_id`, `job_id`
- `selected`:是否进入最终上下文
- `selection_reason`
- `citation_key`
- `first_seen_query`
- `created_at`, `updated_at`
正文可很大,使用 `PortableLongText`。如果后续规模需要对象存储,可保持数据库里元数据和正文 locator,但第一版先使用现有数据库能力。
### 13.5 `deep_research_messages`
用于研究完成后的追问:
- `id`, `session_id`, `user_id`
- `role`
- `content`
- `citation_source_ids`
- `usage_snapshot`
- `created_at`
追问只读取本 session 报告和来源;若允许再次搜索,必须通过一个显式 `allow_new_research` 参数创建新 job,不能静默联网。
### 13.6 产物存储
推荐为每个 session 创建一个隐藏的 DeerFlow runtime thread:
- `runtime_thread_id` 写入 session。
- thread metadata:`thread_type=deep_research`、`hidden=true`、`session_id=...`。
- 产物写到现有线程 sandbox 的 `user-data/outputs`。
- 下载复用 `app/gateway/routers/artifacts.py` 的用户鉴权、路径保护、列表和文件读取。
- 普通聊天记录列表必须过滤 `hidden=true`,避免内部线程出现在聊天页。
固定文件名建议:
```text
outputs/
├── report.md
├── report.html
├── sources.json
├── research-plan.json
├── report.pdf # 可选能力
└── report.docx # 可选能力
```
---
## 14. 后台任务与恢复
### 14.1 为什么必须后台执行
详细报告、Deep Research 和多智能体报告可能执行数分钟,不能把完整流程绑定在单个 FastAPI 请求生命周期中。必须复用 roundtable 作业的工程模式:数据库作业、dispatcher、lease、executor、轮询式 SSE。
### 14.2 状态机
```mermaid
stateDiagram-v2
[*] --> queued
queued --> running: dispatcher 领取
running --> awaiting_input: LangGraph interrupt
awaiting_input --> queued: 用户批准或修改
running --> completed: 报告与必要产物成功
running --> failed: 不可恢复错误
queued --> cancelled: 取消
running --> cancelled: 检查到取消令牌
awaiting_input --> cancelled: 取消
running --> queued: lease 过期且允许重试
completed --> [*]
failed --> [*]
cancelled --> [*]
```
`cancel_requested` 是内部标志,不是最终状态。executor 检查后清理并写成 `cancelled`。
### 14.3 阶段枚举
```text
initializing
planning
collecting
curating
compressing
deepening
writing
reviewing
exporting
done
```
### 14.4 Dispatcher
`deep_research_job_dispatcher.py` 负责:
1. 周期性扫描 `queued` 或 lease 过期的可恢复任务。
2. 用数据库 CAS 领取,不依赖单进程锁。
3. 设置 `lease_owner` 和 `lease_until`。
4. 把 job 交给本进程 executor。
5. 达到全局/每用户并发限制时保持 queued。
### 14.5 Executor
`deep_research_job_executor.py` 负责:
1. 读取不可变 `input_snapshot`。
2. 组装 repository、Adapter、checkpointer 和取消令牌。
3. 启动 lease heartbeat。
4. 调用 `DeepResearchEngine.run()`。
5. 持续更新 job phase/progress,但事件正文写入 event store。
6. 捕获 interrupt,保存 `awaiting_input` 状态。
7. 成功时原子更新 session、job 和最终事件。
8. 失败时分类:配置错误、模型错误、材料错误、预算超限、内部错误。
9. `finally` 中释放资源、重置 ContextVar、停止 heartbeat。
禁止把正在运行的 `asyncio.Task` 作为唯一真相保存在内存字典;内存映射只能用于本进程快速取消,数据库状态才是恢复依据。
---
## 15. 事件协议与 SSE
### 15.1 统一事件包
```json
{
"seq": 42,
"sessionId": "drs_xxx",
"jobId": "drj_xxx",
"type": "source_added",
"phase": "collecting",
"timestamp": "2026-08-14T10:00:00+08:00",
"payload": {
"sourceId": "src_xxx",
"title": "...",
"url": "https://...",
"sourceType": "web"
}
}
```
### 15.2 事件类型
| 类型 | 主要 payload |
|---|---|
| `job_status` | status、progress |
| `phase_changed` | from、to、message |
| `plan_created` | plan、subtopics |
| `query_started` | query、branch、iteration |
| `query_completed` | query、resultCount、duration |
| `source_added` | source 摘要 |
| `sources_curated` | selectedIds、removedIds、reason |
| `context_compressed` | before/after token、sourceCount |
| `deep_branch_started` | depth、breadth、query |
| `deep_branch_completed` | learnings、newQueries |
| `report_chunk` | sectionId、delta |
| `report_completed` | reportVersion、sourceCount |
| `artifact_created` | format、artifact path/name |
| `awaiting_input` | interruptId、kind、payload、allowedActions |
| `model_usage` | model、operation、token usage |
| `warning` | code、message、recoverable |
| `error` | code、message、retryable |
| `heartbeat` | server time |
不要把模型原始思维链写入事件或返回前端。进度说明只能是系统定义的操作摘要。
### 15.3 SSE 实现复用点
前端和后端分别参考:
- `app/gateway/routers/roundtable_jobs.py`:作业查询、取消、恢复和数据库轮询 SSE。
- `frontend-web/src/roundtable-planning/api/roundtable-jobs.ts`:`fetch()` body reader、`AbortController` 和流式解析。
新功能应复制通用模式或提取无领域依赖的流解析辅助函数,不能让 deep-research API import roundtable 领域类型。
---
## 16. Gateway API 设计
统一前缀:`/api/deep-research`
所有接口均使用 DeerFlow 当前认证方式,并校验 `session.user_id == current_user.id`。管理员也不默认跨用户读取报告,除非已有明确审计权限。
### 16.1 创建会话
`POST /api/deep-research/sessions`
请求:
```json
{
"query": "分析国内工业大模型在设备预测性维护中的落地情况",
"title": "工业大模型预测性维护研究",
"config": {
"mode": "deep",
"language": "zh-CN",
"tone": "analytical",
"deepBreadth": 4,
"deepDepth": 2,
"curateSources": true,
"includeHumanFeedback": false,
"allowedMaterialChannels": ["web_search", "knowledge"]
}
}
```
响应:
```json
{
"id": "drs_xxx",
"status": "draft",
"title": "工业大模型预测性维护研究",
"query": "...",
"config": {},
"createdAt": "..."
}
```
### 16.2 会话 CRUD
- `GET /api/deep-research/sessions?limit=20&cursor=...&status=...`
- `GET /api/deep-research/sessions/{session_id}`
- `PATCH /api/deep-research/sessions/{session_id}`:仅 draft 可修改影响运行的配置;运行后只允许改 title。
- `DELETE /api/deep-research/sessions/{session_id}`:运行中先拒绝或要求先取消;删除需要清理隐藏 thread 和产物。
列表使用游标分页,不返回完整 report 和 source 正文。
### 16.3 启动任务
`POST /api/deep-research/sessions/{session_id}/jobs`
请求:
```json
{
"requestId": "client-generated-uuid"
}
```
响应:
```json
{
"jobId": "drj_xxx",
"sessionId": "drs_xxx",
"status": "queued",
"reused": false
}
```
幂等规则:
- 同用户、同 session、同 requestId 和相同请求 hash 返回原 job。
- 同 session 已有 active job 时返回该 job 并标记 `reused=true`。
- 同 requestId 但请求 hash 不同返回 409。
### 16.4 Job 查询和流
- `GET /api/deep-research/jobs/{job_id}`
- `GET /api/deep-research/jobs/{job_id}/stream?after=42`
- `POST /api/deep-research/jobs/{job_id}/cancel`
- `POST /api/deep-research/jobs/{job_id}/resume`
resume 请求示例:
```json
{
"requestId": "uuid",
"interruptId": "int_xxx",
"action": "revise",
"payload": {
"plan": [
{"title": "市场现状", "questions": ["..."]}
]
}
}
```
### 16.5 来源接口
- `GET /api/deep-research/sessions/{session_id}/sources?selected=all&cursor=...`
- `GET /api/deep-research/sessions/{session_id}/sources/{source_id}`
- `PATCH /api/deep-research/sessions/{session_id}/sources/{source_id}`:只允许在人工计划/材料审核阶段切换 selected。
正文接口需要大小限制;列表只返回摘要。
### 16.6 报告追问
`POST /api/deep-research/sessions/{session_id}/chat`
请求:
```json
{
"message": "报告中哪些案例已经进入规模化生产?",
"allowNewResearch": false
}
```
响应使用 SSE,引用返回 `sourceIds`。`allowNewResearch=false` 时只允许使用 session 报告和来源。
### 16.7 导出
`POST /api/deep-research/sessions/{session_id}/export`
```json
{"format": "html"}
```
- Markdown:总是支持。
- HTML:总是支持,输出前清洗危险标签和链接。
- PDF/DOCX:若 capability 未启用返回 422 + `EXPORT_FORMAT_UNAVAILABLE`,前端不展示对应按钮。
- 可增加 `GET /api/deep-research/capabilities` 返回模式、模型、材料渠道和导出格式。
---
## 17. 前端页面设计
### 17.1 路由
```text
/page/strategy/qa/deep-research 创建页、历史列表和研究工作台
```
当前版本使用单页会话选择,不需要再维护一套嵌套路由:
```tsx
<Route path="qa/deep-research" element={<DeepResearchPage />} />
```
### 17.2 创建页
主表单:
- 课题/问题,必填。
- 模式:标准、详细、深度、多智能体。
- 输出语言和语气。
- 模型:默认使用系统配置,可展开选择 fast/smart/strategic。
- 材料渠道:只显示后端 capabilities 返回的渠道。
- 高级设置:每次结果数、最大子主题、深度、宽度、并发、来源策展、人工审核、图片。
- 创建并开始。
历史区:
- 标题、模式、状态、进度、来源数、更新时间。
- 支持状态筛选、重命名、删除、继续查看。
- 运行中的会话进入后自动连接 SSE。
### 17.3 工作台布局
桌面端建议三栏:
```text
┌──────────────┬────────────────────────────────┬──────────────────┐
│ 历史/阶段 │ 计划、进度时间线、报告预览 │ 来源库/来源详情 │
│ │ │ │
│ 新建研究 │ 底部:停止/批准/导出/报告追问 │ 引用定位 │
└──────────────┴────────────────────────────────┴──────────────────┘
```
移动端改为 tabs,不强制三栏挤压。
### 17.4 组件职责
- `ResearchProgressTimeline`:仅消费 event reducer 的 view model,不直接请求 API。
- `ResearchPlanPanel`:展示/编辑 interrupt 中的计划;批准、修改、取消均调用 resume/cancel API。
- `SourceLibraryPanel`:分页和筛选来源,区分 web/knowledge/skill/MCP。
- `SourceDetailDrawer`:显示受限正文、元数据、相关查询和引用章节。
- `ReportPreview`:渲染 Markdown,点击引用跳到 SourceDetailDrawer。
- `ArtifactDownloads`:根据 capabilities 和 artifact 事件显示下载。
- `ResearchChatPanel`:完成后追问,不复用 AI 写作 Composer 状态。
### 17.5 数据和流状态
- TanStack Query 管 session、job、sources 和 messages 的服务器状态。
- `useDeepResearchStream` 只负责连接、重连、last seq 和事件派发。
- `event-reducer.ts` 将事件转为 timeline、report delta、phase、progress、interrupt 等 UI 状态。
- `report_completed` 后以服务端 session 内容覆盖流式拼接内容,避免断线丢 chunk。
- SSE 断线采用有限退避;页面不可见时可以降低轮询,但不能取消后台 job。
- 刷新页面先 GET job/session,再从 `lastEventSeq` 补流。
### 17.6 前端类型
`lib/types.ts` 至少定义:
```ts
type ResearchMode = "quick" | "basic" | "detailed" | "deep" | "multi_agent";
type ResearchJobStatus =
| "queued"
| "running"
| "awaiting_input"
| "completed"
| "failed"
| "cancelled";
interface DeepResearchEvent<T = unknown> {
seq: number;
sessionId: string;
jobId: string;
type: DeepResearchEventType;
phase: ResearchPhase;
timestamp: string;
payload: T;
}
```
后端 snake_case 到前端 camelCase 的转换在 API client 层集中处理,不散落到组件。
---
## 18. 产物与导出
### 18.1 核心零新增导出依赖
- `report.md`:保存最终 Markdown。
- `report.html`:使用项目现有 Markdown 能力或轻量转换生成并清洗。
- 浏览器打印:前端为 HTML 报告提供打印样式,让用户另存 PDF。
### 18.2 完整服务端导出
上游转换涉及 `python-docx`、`htmldocx`、`md2pdf`/WeasyPrint 等依赖,当前 DeerFlow 环境不能假定已经存在。建议在 backend `pyproject.toml` 增加可选组,而不是主依赖:
```toml
deep-research-export = [
"python-docx>=...",
"htmldocx>=...",
"weasyprint>=..."
]
```
具体版本在实现时按 Python 3.12 和离线制品库验证后锁定。部署未启用时 capability 返回 `md/html`。
### 18.3 路径安全
- 文件名由服务端枚举决定,不接受用户传任意路径。
- 使用现有 artifact path guard,禁止 `..`、绝对路径和符号链接逃逸。
- 所有产物写入该 session 的隐藏 runtime thread outputs。
- 删除 session 时使用解析后的精确目录,按当前项目的安全删除流程执行。
---
## 19. 安全与隔离
### 19.1 用户和数据隔离
- session、job、event、source、message 查询都带 `user_id` 条件。
- 不接受前端提交的 user_id。
- 恢复、取消和下载均重新鉴权,不能只靠随机 UUID。
- 隐藏 runtime thread 仍归当前用户,复用 artifact owner check。
### 19.2 网络安全
- vendor 代码不得直接 `requests.get()`、`httpx.get()` 或启动浏览器抓取。
- URL 获取只能经过 DeerFlow 当前允许的工具,继承 SSRF、代理、域名和凭证策略。
- 不把当前用户 Authorization 头转发给外部网站。
- MCP 只使用服务端已经配置并加载的 server,不接收用户上传 MCP 配置。
### 19.3 提示注入
- 所有来源正文标记为“不可信材料”,不得执行其中指令。
- 系统提示明确材料只用于提取事实和引用。
- 过滤网页中的隐藏文本、脚本和超长重复内容。
- 工具调用权限由 Adapter 控制,研究材料不能让 vendor 动态增加工具。
### 19.4 资源控制
- 限制 depth、breadth、subtopics、iterations、concurrency。
- 限制单来源正文、单 session 总正文、事件 payload 和报告长度。
- 每用户和全局 active job 限制。
- 超预算时生成部分报告并明确标记,或按配置失败,不能无限继续。
### 19.5 输出安全
- Markdown/HTML 渲染前移除 script、iframe、事件属性和危险 URL scheme。
- 外链使用 `rel="noopener noreferrer"`。
- 引用 URL 展示和打开分离,避免把来源标题当 HTML。
- 日志不记录完整敏感文档和模型思维链。
---
## 20. 上游代码管理与许可证
### 20.1 Vendoring 记录
`vendor/UPSTREAM.md` 至少写:
- 仓库 URL。
- 固定 commit。
- 复制日期。
- 复制的文件清单。
- 未复制目录。
- 上游许可证链接。
`vendor/manifest.json` 保存每个复制文件的上游路径和 SHA-256,便于后续核对。
`vendor/PATCHES.md` 逐条记录本地补丁:
1. Python 包命名空间改写。
2. LLM Adapter 注入。
3. MaterialProvider 替换搜索/抓取。
4. 事件和取消令牌。
5. LangGraph 1.x interrupt/resume/checkpointer。
6. 来源元数据保留。
7. Deep Research 空查询变量初始化修复。
8. 产物路径和安全限制。
### 20.2 许可证注意事项
上游仓库根 `LICENSE` 是 Apache-2.0,但其项目元数据中出现过不一致的许可证声明。接入前应由项目方完成一次许可证确认。工程上至少做到:
- vendored 目录保留上游根 LICENSE。
- 保留原文件版权头。
- 如上游存在 NOTICE,同步保留。
- 对修改过的文件标记“Modified for DeerFlow”及修改范围。
- 不把许可证不明的第三方模板或资产一起复制。
### 20.3 升级流程
1. 选择新的上游 tag/commit。
2. 根据 manifest 对比选定文件,而不是覆盖整个 vendor 目录。
3. 重放 `PATCHES.md` 中的小补丁。
4. 运行 Adapter contract、LangGraph 1.x、引用和全模式测试。
5. 人工比较同一 fake 数据下报告章节、引用和事件是否出现能力回退。
不建议 Git submodule:当前项目是离线部署,submodule 容易造成制品不完整,也不利于维护本地补丁。
---
## 21. 测试方案
本项目后端要求 TDD。每个阶段先写 fake 驱动的单元/契约测试,再接真实模型或搜索。
### 21.1 Harness 单元测试
```text
tests/test_deep_research_langgraph_1x.py
tests/test_deep_research_material_provider.py
tests/test_deep_research_prefetched_content.py
tests/test_deep_research_material_metadata.py
tests/test_deep_research_llm_adapter.py
tests/test_deep_research_context.py
tests/test_deep_research_basic_runner.py
tests/test_deep_research_detailed_runner.py
tests/test_deep_research_deep_runner.py
tests/test_deep_research_multi_agent_runner.py
tests/test_deep_research_citations.py
```
重点断言:
- MaterialProvider 已返回 `raw_content` 时,vendor BrowserManager/scraper 从未被调用。
- DeerFlow 的 `recUuid/source/published_at` 经过研究流程后仍存在。
- 相同材料去重但不丢失命中它的多个 query。
- fake LLM 下 basic/detailed/deep/multi_agent 都产生预期章节和引用。
- 取消在搜索、压缩、递归和写作阶段均能停止。
- 不存在 `app.*` 从 Harness 导入。
### 21.2 Persistence/Job 测试
```text
tests/test_deep_research_sessions.py
tests/test_deep_research_jobs.py
tests/test_deep_research_job_leases.py
tests/test_deep_research_job_cancellation.py
tests/test_deep_research_event_replay.py
tests/test_deep_research_user_isolation.py
```
场景:
- 20 个并发启动请求只产生一个 active job。
- 相同 requestId 幂等,不同 payload 冲突。
- 两个 dispatcher 只有一个能领取 lease。
- executor 崩溃后 lease 过期,另一个实例接管。
- SSE 从 seq N 准确补发,无重复或前端 reducer 可幂等去重。
- 用户 A 无法获取、取消、恢复、下载用户 B 的任务。
### 21.3 Gateway 测试
- session CRUD 和校验。
- start/get/stream/cancel/resume。
- resume action 白名单和 interruptId 校验。
- source 正文大小和权限。
- export capability 降级。
- 隐藏 runtime thread 不出现在普通聊天列表。
### 21.4 前端测试
- 路由与侧边栏入口。
- 创建配置序列化。
- event reducer 对乱序、重复和补发事件幂等。
- SSE 断线重连使用 last seq。
- awaiting_input 正确显示审核按钮。
- source 引用点击定位。
- completed 时以 session 最终报告校正流式文本。
- 不支持 PDF/DOCX 时不展示按钮。
### 21.5 无外网验收夹具
建立固定 fake 材料集和 fake LLM 响应,使 CI 不联网也能验收:
- 至少 6 个来源,包含重复 URL、相同正文不同 URL、无 URL 知识库材料。
- 至少 3 个子主题。
- 一个事实核查失败后修订成功的路径。
- 一个人工计划修改后恢复的路径。
---
## 22. 分阶段开发计划
### Phase 0:兼容性尖峰与边界测试
目标:用最少代码消除最大技术风险,不做页面。
任务:
1. 添加 LangGraph 1.x fake graph contract test。
2. 添加预取 `raw_content` 不触发上游 scraper 的 contract test。
3. 验证 `create_chat_model()` 可通过 Adapter 完成上游一次结构化输出。
4. 核对并锁定 vendoring 文件清单和许可证。
验收:
- 当前 LangGraph 1.x 环境测试通过。
- 不安装 `langgraph<0.3`。
- fake MaterialProvider 能完成一次 basic research 最小闭环。
### Phase 1:标准报告后端 MVP
实现状态(2026-08-14):已完成领域配置、材料/LLM/上下文/产物 Adapter、Basic runner、五类持久化表、租约式 dispatcher/executor、Gateway API 和定向测试。当前 Basic runner 是 DeerFlow 原生实现;上游 GPT Researcher 的选定源码尚未 vendoring,仍应在 Phase 3/4 的详细报告、递归研究和多智能体实现中按本文第 6 章从本地固定 commit 引入。
任务:
1. 新增 config/types/cancellation/events。
2. vendor 核心 `gpt_researcher` 选定代码。
3. 实现 LLM、Material、Context、Artifact Adapter。
4. 实现 Basic runner。
5. 新增 session/source/job/event 表。
6. 实现 session、start、get、stream、cancel API。
7. 复用隐藏 runtime thread 产物。
验收:
- DeerFlow 当前 `web_search` 材料可以生成带引用报告。
- 上游任何 retriever/scraper/MCP 均未运行。
- 刷新页面/API 断线后能继续获取进度。
- 可取消,产出 MD/HTML。
### Phase 2:独立前端页面
实现状态(2026-08-14):已完成路由与菜单入口、创建表单(将所选 mode 写入后端 config)、会话历史、作业 SSE、取消/重新开始、实时来源刷新、报告预览和 MD/HTML 下载。本轮将右侧工作区改为对话式:原始课题、研究过程、流式报告草稿、最终报告与报告追问按对话顺序展示;报告 `report_chunk` 依赖持久化 SSE 重放,刷新时不重新调用模型。页面与 AI 写作页面、Context 和路由完全隔离。
任务:
1. 增加路由和菜单。
2. 创建页、历史列表和工作台。
3. SSE hook、event reducer、进度时间线。
4. 来源库、报告预览和产物下载。
验收:
- 不进入 AI 写作页面或 context。
- 可以完整创建、运行、刷新恢复、取消和下载。
- 菜单覆盖、权限和敏感词展示仍正常。
### Phase 3:详细报告与 Deep Research
实现状态(2026-08-14):已完成 `DetailedRunner` 和 `DeepRunner`,并由 `DeepResearchEngine` 分发;Gateway capabilities 与创建校验已开放 `detailed`、`deep`,前端模式选择与递归阶段事件也已接入。详细报告按本地上游 `gpt-researcher/backend/report_type/detailed_report/detailed_report.py` 的“子主题规划 → 分主题研究 → 章节汇编”控制流做 Adapter 化实现;递归模式按 `gpt-researcher/gpt_researcher/skills/deep_research.py` 的“分支 → learning → follow-up”控制流适配。二者不直接调用上游的模型、检索器或抓取器,而是只通过 DeerFlow 的 `AdapterBundle` 复用现有材料收集和 LangGraph 1.x 运行时。
任务:
1. 接入 Detailed runner。
2. 接入 Deep runner并修复已知空查询问题。
3. 增加全局预算、分支事件和来源去重。
4. 增加高级配置 UI。(已完成:仅暴露已由运行器使用的语言、语气、预算、并发、模式专属参数和 `custom_outline`。)
验收:
- 同一来源跨子主题稳定去重和引用。
- 并发不超过配置。
- depth/breadth 到限后确定性停止。
- 部分分支失败有明确告警,不造成任务永不结束。
### Phase 4:LangGraph 多智能体与人工审核
实现状态(2026-08-14):已完成 `MultiAgentRunner`。它按本地上游 `gpt-researcher/multi_agents/agents/orchestrator.py` 的 editor → researcher → writer → reviewer 结构,用当前已安装的 LangGraph 1.x `StateGraph` 编排,不引入上游的 retriever、scraper 或额外 agent 框架。初次运行产生 plan-review `awaiting_input` 事件;`RecordingEventSink` 将 interrupt、计划和租约状态写入既有 durable job,前端可批准或提交修订,`POST /api/deep-research/jobs/{id}/resume` 重新入队后从事件日志恢复原计划并继续执行。审阅不通过时按 `max_revision_loops` 有界回到改写节点。
任务:
1. vendor LangGraph 多智能体选定代码。
2. 改造状态、checkpointer、interrupt/resume。
3. 接事实核查与修订回环。
4. 前端加入计划审核和修订交互。
验收:
- 服务重启后仍可恢复等待中的人工审核。
- 重复批准不会执行两次。
- 所有回环有上限。
- 多智能体同样只使用 DeerFlow 材料和模型。
### Phase 5:追问、图片和完整导出
实现状态(2026-08-14):图片生成和报告追问子项已完成。由于当前 DeerFlow 部署本身只有识图、没有可直接复用的文生图 Provider,深度研究新增了部署级 `deep_research.image_generation`(OpenAI 兼容 `POST {base_url}/images/generations`)适配器;默认关闭且不保存任何密钥到 session/job。开启后 capabilities 才向前端开放「为报告生成配图」开关。四种 Runner 在文本报告完成后共用 `runners/images.py`:最多四张、优先报告二级章节、请求 `b64_json`,图片保存为隐藏 runtime thread 的受限 `research-image-01..04.(png|jpg|webp)` 产物,并以 `deep-research://` 私有 URI 插入 Markdown。前端经携带现有鉴权头的会话产物接口下载成 Blob 再预览,因此不会泄露 hidden thread id,也不依赖可能过期的第三方 URL;图片服务未配或单张失败只产生可恢复 warning,文字报告照常完成。完成态报告还支持 owner-scoped `GET /messages`、`POST /chat`:问题与回答写入既有消息表,模型仅接收本报告、已选来源和近期消息;回答中的 `[[source:id]]` 仅在 id 属于该会话来源时保留并写入 citation ids。用户可在研究任务停止后以 `PATCH /sessions/{id}/sources/{source_id}` 调整“已选来源”,该集合会立即约束后续追问但不会倒改已完成的报告;运行中返回 409,以免与自动策展写入竞态。`allow_new_research=true` 明确返回 422,不会暗中重新搜集信息。DOCX 已通过浏览器端 `docx` 生成,无需部署新离线 wheel;PDF 与含私有图片嵌入的服务端文档导出仍是后续可选子项。
任务:
1. 报告追问与消息持久化。(已完成)
2. DeerFlow ImageProvider 适配和图片插入。(已完成)
3. 可选 PDF/DOCX 导出依赖与 capability。
4. 完善配额、监控和管理配置。
验收:
- 追问引用来源可回溯。
- 图片能力未配置时不影响报告主流程。
- 导出依赖未安装时优雅降级。
---
## 23. 代码级实施顺序
建议按以下提交顺序实施,便于评审和回退:
1. `test: add deep research adapter and langgraph contracts`
2. `feat: add deep research domain types and adapters`
3. `vendor: add selected gpt-researcher core at pinned commit`
4. `feat: add basic deep research runner`
5. `feat: add deep research persistence and durable jobs`
6. `feat: add deep research gateway APIs and SSE`
7. `feat: add deep research frontend route and setup page`
8. `feat: add research workbench, sources and report preview`
9. `feat: add detailed and recursive deep research modes`
10. `feat: adapt multi-agent graph to langgraph 1.x interrupts`
11. `feat: add report follow-up and optional exports`
每个提交都应保持:
- `uv run --no-sync pytest` 相关测试通过。
- `uv run --no-sync ruff check` 通过。
- `pnpm typecheck` 通过(涉及前端时)。
- 不修改或降级 LangGraph 依赖。
- 不引入 Harness → App 反向依赖。
---
## 24. 功能对齐检查表
为防止“复用了代码但功能对不上”,开发完成后逐项确认:
| GPT Researcher 能力 | DeerFlow 对齐结果 |
|---|---|
| 自动生成研究问题 | query processing 复用,结构化校验 |
| 自动选择/创建研究角色 | agent creator 复用 |
| 多来源并行研究 | ResearchConductor 复用,来源由 DeerFlow 提供 |
| 网页抓取 | 不复用;由 DeerFlow 当前信息收集完成 |
| 来源压缩 | 接口复用,压缩器适配 |
| 来源策展 | SourceCurator 复用并保留 DeerFlow 元数据 |
| 标准报告 | Basic runner |
| 详细报告 | Detailed runner |
| 递归深度研究 | Deep runner |
| 子主题并行 | 复用,增加统一并发预算 |
| 多智能体研究、审阅、修订 | LangGraph 1.x Multi-agent runner |
| 人工计划审核 | `interrupt/Command(resume)` |
| 事实核查回环 | 复用,增加最大回环和来源约束 |
| 引言、正文、结论 | ReportGenerator 复用 |
| 引用和参考链接 | Markdown processing 复用并绑定 source id |
| 报告完成后的追问 | 已支持:仅报告/已选来源/近期消息,持久化 source-id 引用,不会隐式重新搜索;停止后可人工调整已选来源 |
| 图片生成与插入 | 已支持:部署级 OpenAI 兼容 ImageProvider,可选开关、受鉴权产物、报告内预览;未配置时优雅跳过 |
| Markdown | 核心支持 |
| HTML | 核心支持并清洗 |
| PDF/DOCX | 可选依赖能力 |
| 实时进度 | EventSink + DB events + SSE |
| 历史任务 | DeerFlow session/job 持久化 |
| 中断、恢复、取消 | DeerFlow durable job + LangGraph checkpointer |
| 模型切换 | DeerFlow `create_chat_model()` |
| 搜索 Provider | 不暴露上游 Provider,使用 DeerFlow 渠道 |
| MCP | 使用 DeerFlow 已配置 MCP,不复用上游 MCP |
| 前端 | 新建 DeerFlow React 页面,不复用上游 Next.js UI |
---
## 25. 已知风险与处理
### 25.1 上游升级造成大补丁
处理:vendor 只保留核心文件,Adapter 放在 vendor 外,用 manifest 和 contract test 管理升级。
### 25.2 材料正文质量不一致
处理:MaterialProvider 区分 snippet 和正文;低于正文阈值时由 DeerFlow 正文工具补齐;记录 material quality,报告不得把 snippet 当完整证据。
### 25.3 递归研究费用不可控
处理:depth/breadth 之外增加总 query、总正文、总模型调用和 Token 硬预算;预算快到上限时先合成已有结论。
### 25.4 SSE 消息量过大
处理:正文不放事件;report chunk 做节流/合并;事件只存摘要和 locator;支持 seq 补发。
### 25.5 多实例重复执行
处理:数据库唯一 active key、lease、heartbeat、version CAS;产物写入使用临时文件后原子替换;最终状态更新幂等。
### 25.6 上游阻塞代码卡住事件循环
处理:优先异步化;无法立即改造的纯 CPU/同步转换使用 `asyncio.to_thread`;网络请求不得用同步 requests。
### 25.7 导出依赖影响离线部署
处理:MD/HTML 为核心能力,PDF/DOCX 是显式可选依赖和 capability,不因为导出阻塞核心功能上线。
### 25.8 许可证元数据不一致
处理:合入 vendor 前法律/项目方确认;保留根 LICENSE、版权头、修改说明,不复制无关资产。
---
## 26. 开发前最终确认项
以下选择不会阻塞本文方案,但 Phase 1 开工前应在配置层确认:
1. 第一批允许的材料渠道是仅 `web_search`,还是同时包含知识库、技能、MCP。
2. 默认模型角色映射:fast/smart/strategic 是否都使用当前默认模型,还是管理员分别配置。
3. session 删除是否同步删除隐藏 runtime thread 和所有产物。
4. 默认最大研究预算和每用户并发数。
5. 是否在首个版本就启用人工计划审核;即使不启用,后端状态和表结构仍应预留。
6. PDF/DOCX 是否作为首发硬需求;如果是,需要提前准备离线系统依赖和 Python wheel。
推荐默认值:第一版先开放 `web_search` 和已存在的内部知识材料;模型角色都映射到当前默认模型;删除会话同步删除内部产物;每用户最多 2 个 active job;人工审核随 multi-agent 阶段上线;首发只保证 MD/HTML。
---
## 27. 最终判断
该功能可以在当前 DeerFlow 上实现,且总体风险可控。最重要的工程决策不是“是否复用 GPT Researcher”,而是把复用边界划清:
- 复用其已经成熟的研究规划、递归研究、报告生成和多智能体编审算法。
- 不复用其与 DeerFlow 重叠且治理较弱的搜索、抓取、MCP、模型 Provider、演示服务和前端。
- 用 Adapter 保证研究算法只消费 DeerFlow 提供的材料、模型、事件和产物能力。
- 用 LangGraph 1.x 原生 interrupt/checkpointer 和 DeerFlow durable job 解决暂停、恢复、多实例与刷新续接。
- 用独立页面和独立领域包避免影响现有 AI 写作和聊天主链路。
按本文 Phase 0 到 Phase 4 实施后,可以覆盖 GPT Researcher 的主要生产功能,同时保留 DeerFlow 当前信息收集体系和 LangGraph 1.x 依赖,不需要再维护一套旧运行时。