deerflow-code/.zcode/plans/plan-sess_bc89c1c7-ec32-43b9-886f-70fa2ae83a13.md
2026-09-07 18:24:55 +08:00

6.7 KiB
Raw Blame History

深度研究第三轮:检索稳定性修复 + 参考文献确定性 + 页面对齐 AI 写作改造

A. 两个 bug 的排查结论与修复

A1. 第二次检索「网络波动」——确认是真问题(DDG 限流 + 错误语义混淆 + 零重试)

排查结论(用户判断"确实是网络波动"基本正确,但代码层面放大了它):

  • 「网络波动」文案不存在于任何代码中——是收集员 LLM 看到 {"error": "No results found"} 信封后自己编的解释
  • 真实根因:DDG html 端点对同 IP 快速连续请求限流(403/429 挑战页)→ DDGSException → ddg_search/tools.py::_search_text 把异常吞成 [] → 上层把「被限流」和「真没结果」混成同一个错误信封;工具层完全没有重试

修复(packages/harness/deerflow/community/ddg_search/tools.py):

  1. _search_text 对异常加 2 次重试 + 指数退避(约 1.5s / 3s + 抖动)
  2. 错误信封区分场景:真无结果 → no results;多次尝试仍失败 → web search failed after N attempts (rate limited or network), 换一组检索词稍后重试——让模型不再瞎猜「网络波动」
  3. 收集员 SOUL.md 行为准则加一条(同步改种子资产 + 已安装副本 .deer-flow/agents/deep-research-collector/):搜索返回 error 信封时先换检索词重试一轮,而不是直接告知用户网络异常

A2. 参考文献缺失——代码确有隐患(当前是模型自发行为,时有时无)

排查结论:后端没有任何「参考来源」追加逻辑;四个 runner 的报告 prompt 只要求内联 [N] 引用。出现过的参考列表是模型看到上下文头 [N] 标题(URL) 后自发补写的——弱模型下不可靠,与"信息够不够"无必然关系。

修复(prompt 指示 + 确定性兜底双保险):

  1. 四个 runner(basic / detailed / deep / multi_agent)的报告 prompt 各加一条:报告最后附「## 参考来源」,按 [N] 标题. URL 列出正文实际引用过的来源
  2. basic.py 新增共享函数 ensure_reference_section(report, context_list, materials):报告缺少参考小节时,解析正文引用的 [N] 编号,按压缩上下文顺序生成编号列表追加(无编号时列全部来源,封顶 20 条;已有参考小节则不重复追加)。在各 runner 最终 write_text("report.md", ...) 前调用(约 7 个调用点,跳过零材料 fallback)
  3. 新增测试 tests/test_deep_research_references.py:helper 单测(正常追加/编号映射/不重复追加/无 URL 来源)+ runner 集成断言

B. 页面改造(核心需求):左列复用 AI 写作对话形态,右列改真沙箱

B1. 左列 = AI 写作式对话收集面板(仿 WritingSetupChat)

  • 空状态欢迎屏(无消息时隐藏头部,展示欢迎卡片,WritingWelcome 式)+ 有消息后 MessageList(沿用现有绑 collector 线程的接线)+ 首 token 前「正在整理…」占位条
  • 输入框换成白卡片 PromptInput 套件(与 AI 写作完全同款组件):卡片内一行「当前对话模型」Select(沿用 ModelNameSelect)+ 高级配置入口;下方 PromptInputTextarea + PromptInputFooter + PromptInputSubmit(status ready/streaming,流式中变停止按钮)。经典模式入口保留在高级配置对话框
  • 配置卡片进对话流(对标 AI 写作「识别出意图后出现表单」):
    • message-list.tsx 最小扩展(约 15 行):新增分组 assistant:research-approval(识别 additional_kwargs.setup_research_approval)+ 可选 prop researchApprovalSlot?: ReactNode;不传 slot 或无该载荷时零行为变化——普通对话/AI 写作完全不受影响
    • workbench:收集回合结束(awaiting_report,即"意图识别完成")时向 displayThread 尾部注入一条虚拟 ai 消息(WritingSetupChat 的 displayThread 同款手法),slot 传现有 ReportConfigCard → 卡片作为助手下一轮出现在对话流里
  • 写作进度卡、完成卡、摘要气泡、追问列表保留在列表下方卡片区(报告正文移到右侧沙箱)

B2. 右列 = 工作区真沙箱(Step2SandboxLayout 的 stub 线程模式)

  • 新建 deep-research/ResearchSandboxPanel.tsx(约 100 行,含关闭/重开)+ useReportSandbox.ts hook(仿 useStep3Report 的 stub 机制):
    • threadStub:messages = [{type:'ai', tool_calls:[{name:'write_file', args:{path:'/mnt/user-data/outputs/report.md', content: <流式报告>}}]}],isLoading = job 运行中 && phase !== 'summarizing'
    • report_delta 首帧 → openArtifact(write-file: 虚拟URL) 自动展开右栏;run.report 每帧 upsert stub(带防抹空守卫);刷新/切换会话用 session.reportMarkdown 重建
    • ArtifactFileDetail 自带能力全部免费获得:流式期 code 视图自动滚底 → 报告写完 isLoading 翻 false 自动切 preview + 脉冲提示(正是「生成完直接切预览」)、Streamdown 渲染的 Markdown(比现在的好看)、另存 Word/复制/下载、默认走展示层脱敏
  • 布局:ResizablePanelGroup chat 60 / sandbox 40(对齐 ChatBox/圆桌),沙箱可关闭、头部按钮重开
  • 删除自定义 ResearchSandbox 组件;legacy 会话与经典模式共用同一右栏(run.report 数据源相同,行为一致)
  • 已知小限制(可接受):报告配图模式下沙箱内图片可能无法经 artifacts API 解析(配图默认关闭;真需要时后续把产物落 thread artifacts)

B3. 验证

  • 后端:PYTHONPATH=. uv run --no-sync pytest tests/ -k deep_research -q --ignore=tests/test_notification_templates.py 全绿 + ruff;改完配置/SOUL 后 touch 触发 dev 服务重载
  • 前端:pnpm typecheck 零新增错误(基线 51)
  • 手动走查:连续两次检索不再误报、弱模型报告末尾必带参考来源、页面全流程(欢迎屏 → 对话收集 → 流内配置卡 → 沙箱流式 → 自动预览 → 摘要/追问)

涉及文件

后端:community/ddg_search/tools.py、deep_research/runners/{basic,detailed,deep,multi_agent}.py、收集员 SOUL.md(种子 + 已安装副本)、tests/test_deep_research_references.py(新) 前端:components/workspace/messages/message-list.tsx(可选 slot 分组)、pages/deep-research/DeepResearchWorkbench.tsx(大改)、pages/deep-research/ResearchSandboxPanel.tsx(新)、pages/deep-research/useReportSandbox.ts(新)

明确不动

  • AI 写作 / 普通对话现有组件行为(message-list.tsx 仅加可选分组,缺省零变化)
  • 报告写作管线的写作逻辑本身(prompt 加一条要求 + 末尾确定性追加参考小节,均为附加式)
  • 深度研究后端会话/任务/事件模型(本轮不动)