6.7 KiB
6.7 KiB
深度研究第三轮:检索稳定性修复 + 参考文献确定性 + 页面对齐 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):
_search_text对异常加 2 次重试 + 指数退避(约 1.5s / 3s + 抖动)- 错误信封区分场景:真无结果 →
no results;多次尝试仍失败 →web search failed after N attempts (rate limited or network), 换一组检索词稍后重试——让模型不再瞎猜「网络波动」 - 收集员 SOUL.md 行为准则加一条(同步改种子资产 + 已安装副本
.deer-flow/agents/deep-research-collector/):搜索返回 error 信封时先换检索词重试一轮,而不是直接告知用户网络异常
A2. 参考文献缺失——代码确有隐患(当前是模型自发行为,时有时无)
排查结论:后端没有任何「参考来源」追加逻辑;四个 runner 的报告 prompt 只要求内联 [N] 引用。出现过的参考列表是模型看到上下文头 [N] 标题(URL) 后自发补写的——弱模型下不可靠,与"信息够不够"无必然关系。
修复(prompt 指示 + 确定性兜底双保险):
- 四个 runner(basic / detailed / deep / multi_agent)的报告 prompt 各加一条:报告最后附「## 参考来源」,按
[N] 标题. URL列出正文实际引用过的来源 basic.py新增共享函数ensure_reference_section(report, context_list, materials):报告缺少参考小节时,解析正文引用的[N]编号,按压缩上下文顺序生成编号列表追加(无编号时列全部来源,封顶 20 条;已有参考小节则不重复追加)。在各 runner 最终write_text("report.md", ...)前调用(约 7 个调用点,跳过零材料 fallback)- 新增测试
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(
statusready/streaming,流式中变停止按钮)。经典模式入口保留在高级配置对话框 - 配置卡片进对话流(对标 AI 写作「识别出意图后出现表单」):
message-list.tsx最小扩展(约 15 行):新增分组assistant:research-approval(识别additional_kwargs.setup_research_approval)+ 可选 propresearchApprovalSlot?: ReactNode;不传 slot 或无该载荷时零行为变化——普通对话/AI 写作完全不受影响- workbench:收集回合结束(awaiting_report,即"意图识别完成")时向
displayThread尾部注入一条虚拟 ai 消息(WritingSetupChat 的 displayThread 同款手法),slot 传现有ReportConfigCard→ 卡片作为助手下一轮出现在对话流里
- 写作进度卡、完成卡、摘要气泡、追问列表保留在列表下方卡片区(报告正文移到右侧沙箱)
B2. 右列 = 工作区真沙箱(Step2SandboxLayout 的 stub 线程模式)
- 新建
deep-research/ResearchSandboxPanel.tsx(约 100 行,含关闭/重开)+useReportSandbox.tshook(仿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/复制/下载、默认走展示层脱敏
- threadStub:
- 布局: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 加一条要求 + 末尾确定性追加参考小节,均为附加式)
- 深度研究后端会话/任务/事件模型(本轮不动)