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

70 KiB
Raw Permalink Blame History

GPT Researcher 接入 DeerFlow 的可实施开发方案

文档状态:持续实现中;本次已补齐报告实时写作、对话工作台与浏览器端 Word 导出 编写日期:2026-08-14
GPT Researcher 核对基线: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 的生产代码主要在:

其中使用的 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. 总体架构

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 上游本地源码定位

上游代码已克隆到:

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、序列化约束

多智能体流程保持如下语义:

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 层

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 持久化层

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 层

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 页面目录一致的轻量实现:

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。

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 增加:
<Route path="qa/deep-research" element={<DeepResearchPage />} />
  • 在 frontend-web/src/core/page-layout/sidebar-menu.ts 的「问答管理」下增加:
{
  id: "qa-deep-research",
  label: "深度研究",
  icon: Telescope,
  path: "/page/strategy/qa/deep-research",
}

该菜单继续经过现有动态菜单覆盖、权限过滤和敏感词展示逻辑,不单独实现另一套侧边栏。


8. 核心领域模型

8.1 研究配置 DeepResearchConfig

文件:deerflow/agents/deep_research/config.py

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

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} 更完整。写入上游上下文时至少保留:

{
    "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 材料提供协议

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 研究输入与输出

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 复用入口

复用当前:

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

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: ...

内部映射:

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,保持上游调用接口但不引入旧依赖。

建议接口:

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

以下上游写法可在实现阶段通过兼容性测试确认并尽量保留:

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:

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)

网关恢复任务:

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

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 包含:

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 的角色和图语义,执行方式改为:

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,避免内部线程出现在聊天页。

固定文件名建议:

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 状态机

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 阶段枚举

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 统一事件包

{
  "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

请求:

{
  "query": "分析国内工业大模型在设备预测性维护中的落地情况",
  "title": "工业大模型预测性维护研究",
  "config": {
    "mode": "deep",
    "language": "zh-CN",
    "tone": "analytical",
    "deepBreadth": 4,
    "deepDepth": 2,
    "curateSources": true,
    "includeHumanFeedback": false,
    "allowedMaterialChannels": ["web_search", "knowledge"]
  }
}

响应:

{
  "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

请求:

{
  "requestId": "client-generated-uuid"
}

响应:

{
  "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 请求示例:

{
  "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

请求:

{
  "message": "报告中哪些案例已经进入规模化生产?",
  "allowNewResearch": false
}

响应使用 SSE,引用返回 sourceIds。allowNewResearch=false 时只允许使用 session 报告和来源。

16.7 导出

POST /api/deep-research/sessions/{session_id}/export

{"format": "html"}
  • Markdown:总是支持。
  • HTML:总是支持,输出前清洗危险标签和链接。
  • PDF/DOCX:若 capability 未启用返回 422 + EXPORT_FORMAT_UNAVAILABLE,前端不展示对应按钮。
  • 可增加 GET /api/deep-research/capabilities 返回模式、模型、材料渠道和导出格式。

17. 前端页面设计

17.1 路由

/page/strategy/qa/deep-research    创建页、历史列表和研究工作台

当前版本使用单页会话选择,不需要再维护一套嵌套路由:

<Route path="qa/deep-research" element={<DeepResearchPage />} />

17.2 创建页

主表单:

  • 课题/问题,必填。
  • 模式:标准、详细、深度、多智能体。
  • 输出语言和语气。
  • 模型:默认使用系统配置,可展开选择 fast/smart/strategic。
  • 材料渠道:只显示后端 capabilities 返回的渠道。
  • 高级设置:每次结果数、最大子主题、深度、宽度、并发、来源策展、人工审核、图片。
  • 创建并开始。

历史区:

  • 标题、模式、状态、进度、来源数、更新时间。
  • 支持状态筛选、重命名、删除、继续查看。
  • 运行中的会话进入后自动连接 SSE。

17.3 工作台布局

桌面端建议三栏:

┌──────────────┬────────────────────────────────┬──────────────────┐
│ 历史/阶段    │ 计划、进度时间线、报告预览     │ 来源库/来源详情  │
│              │                                │                  │
│ 新建研究     │ 底部:停止/批准/导出/报告追问  │ 引用定位         │
└──────────────┴────────────────────────────────┴──────────────────┘

移动端改为 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 至少定义:

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 增加可选组,而不是主依赖:

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 单元测试

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 测试

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 依赖,不需要再维护一套旧运行时。