feat(agent): deepResearch 多跳网络检索子代理工具 - #108
Conversation
把「分解→并行搜索→并发抓取→逐页抽取压缩→充分性判断→改写再搜」的 多跳循环封进单个工具内部,主对话只收紧凑结论+引用,原始网页不进上下文 (对标 Claude Code subagent + 结果过滤)。内层 LLM 调用复用 result-summarizer 范式(generateText/generateObject + 超时 + 失败降级), 弱模型畸形输出循环不崩。接入对话(ai-handlers)、计划执行(plan-runner)、 fork-skill 三条路径,门控为 Tavily 凭证 + model 句柄。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
模型主要依据工具描述决定是否调用,系统提示里的 passive bullet 权重低、 常不命中。改为:deepResearch 描述采用「use FIRST / ALWAYS when / Do NOT / Examples」格式含中英正反例;webSearch/webFetch 反向交叉引用引导多跳问题 改用 deepResearch;系统提示对应段降级为指向工具描述的一行指针(L1/L2 对齐)。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
buildEvalToolRegistry 增 model/providerOptions;tavilyKey + model 齐备时 注册 deepResearch。内部传未包 result-cap 的原始 webSearch/webFetch(抽取 需看完整页面),注册副本仍包 cap 兜底。runner 先解析 model+adapter 再 构造工具表。便于用 gaia-eval 量化多跳命中率/正确率。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Introduces deepResearch, a sub-agent tool that encapsulates a full multi-hop web-research loop (decompose → parallel search → concurrent fetch → per-page extract → sufficiency check → reformulate) inside a single tool call so the main conversation only receives compact findings + citations. Inner LLM calls reuse the timeout/fallback pattern from result-summarizer.ts, and the tool reuses the existing webSearch / webFetch .execute implementations rather than re-implementing them. The tool is wired into all three execution paths (chat, plan-runner, fork-skill-runner) and into the GAIA eval registry, gated on Tavily credentials + a model handle.
Changes:
- New
src/main/core/agent/tools/deep-research.tswith hard-coded budget caps (12 fetches, 4 concurrent, 30s inner timeout, 6KB findings cap) andsafeGenerateObjectfallback wrapping - Registry wiring (
agent-tools.ts,ai-handlers.ts,plan-runner.ts,fork-skill-runner.ts,eval/gaia/tool-registry.ts,eval/gaia/runner.ts) with conditional registration onmodel+ Tavily availability - Tool-description cross-references in
web-search.ts/web-fetch.tsand a new "Web Research" rule insystem-prompt.tsto steer the model towarddeepResearchfor multi-hop questions
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/main/core/agent/tools/deep-research.ts | New multi-hop sub-agent tool implementation |
| src/main/core/agent/tests/deep-research.test.ts | 7 unit cases covering loop, dedupe, caps, fallback, abort |
| src/main/ipc/agent-tools.ts | Threads model/providerOptions, registers deepResearch |
| src/main/ipc/ai-handlers.ts | Passes model/providerOptions into the registry |
| src/main/ipc/plan-runner.ts | Passes model (no providerOptions) into the registry |
| src/main/ipc/fork-skill-runner.ts | Reuses one providerOptions; passes model into the registry |
| src/main/ipc/tests/agent-tools.test.ts | Gate-coverage tests for deepResearch registration |
| src/main/core/agent/tools/web-search.ts | Description now cross-links to deepResearch for multi-lookup |
| src/main/core/agent/tools/web-fetch.ts | Description now cross-links to deepResearch for multi-page reads |
| src/main/ipc/system-prompt.ts | Adds Web Research routing rule |
| src/eval/gaia/tool-registry.ts | Registers deepResearch when both tavilyKey and model present |
| src/eval/gaia/runner.ts | Builds model/adapter before the eval registry so model can be injected |
| src/eval/gaia/tests/tool-contract.test.ts | Conditional-registration assertions for deepResearch |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
GAIA runner 用的是独立 system prompt(与生产 OPERATING_PRINCIPLES 解耦), 其「Available capabilities」段未列出 deepResearch,导致弱模型 (Xiaomi MiMo)选不到它——尽管工具已注册、描述也已优化。 修复:将 deepResearch 加入 capabilities 列表并新增 Multi-step research strategy 段,明确指示「多跳问题先 deepResearch、超过两次 raw 检索未 收敛立即切换」。L2 c61d22de 题验证:修复后模型确实把 deepResearch 作为 FIRST tool call、参数为完整原题。 剩余优化空间(不属本次修复): - 内层 generateObject 在 MiMo 上偶尔解析失败,safeGenerateObject 已兜底; - 模型拿到 deepResearch 结论后仍倾向二次验证,导致触顶 maxStepsPerTurn。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
generateObject 对 OpenAI 兼容提供方(Xiaomi MiMo 经 DeepSeek 适配器)只能走
compat 模式,弱模型常把 JSON 包进 ``` 围栏或混入解释文字,导致每次内层调用都
抛 "could not parse the response"、退化为 fallback,分解/抽取质量受损。
改法(②A+B):
- safeStructured 用 generateText 取文本 → extractJsonObject 宽容提取首个平衡
{...}(剥 ``` 围栏 + 字符串转义感知的括号配平)→ zod 校验 → 失败回退;
- decompose / extract 提示词加 JSON 示例 few-shot。
GAIA L2 c61d22de 实测:内层解析失败从「每跑必现」降到 0 次,模型成功产出语义
正确答案(egalitarianism,仅词形未通过 exact-match)。新增围栏解析单测。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
软提示触发在弱模型上不可靠:L2×5 实测 MiMo 一道都没选 deepResearch、全部 陷入无界 raw webSearch/webFetch 循环并撞 5 分钟墙。本 flag 把"是否多跳"从 模型决策挪到管道:当 deepResearch 可注册(TAVILY_API_KEY + model)时,对外层 隐藏原始 webSearch/webFetch/webScrape,只暴露 deepResearch(其内部仍用原始 def 抓全页);deepResearch 不可注册时自动忽略并回退,避免外层无 web 能力。 系统提示在强制模式下同步切换为「deepResearch is your ONLY web tool」。 验证:同样 5 道 L2,强制后模型从「deepResearch 0/5」变为每题都走 deepResearch (q1=1 次、q2=2 次且为唯一工具)。新增 2 个注册门控单测。 注:本次正确率测量被 Xiaomi 端点的瞬时 TLS 断连(疑似限流)污染,待网络稳定 窗口重测。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
针对慢推理模型(Xiaomi MiMo)强制 deepResearch 时跑不完的问题,分层修复:
① 并行抽取:逐页 extract 从串行 for-await 改为 mapWithConcurrency(4)。弱模型单次
内层调用数十秒,串行 N 页吃光墙钟;并行后单跳抽取 N×t → ~t。
② 墙钟放宽:GAIA per-question 超时 5min → 10min。
内层超时 INNER_CALL_TIMEOUT_MS 30s → 90s(快模型 2-5s 碰不到;MiMo 带 thinking
常 >30s,30s 会把合法调用误判超时降级成垃圾)。
③ 结构化结论 + 信任 + 上限:
- synthesis 改为产出 { directAnswer, confidence, findings }(safeStructured 兜底);
- 同任务 deepResearch 调用硬上限 2 次,超出短路返回提示外层基于已有 findings 作答;
- 默认 maxHops/maxSubQueries 3→2,减少内层调用数;
- GAIA 提示:confidence 高/中时直接用 directAnswer 作答、最多调 2 次。
实测(L2 c61d22de,强制模式):内层超时/解析失败/TLS 全部归零,deepResearch
第 1 次调用即返回正确答案 directAnswer="egalitarian" confidence=high,硬上限在
第 3 次成功短路。残留瓶颈为外层 MiMo 不遵守"高置信即收口"指令、继续调工具至
墙钟耗尽——属弱模型指令遵循天花板,非工具问题。测试更新至 10 例。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
弱模型(MiMo)实测会在 deepResearch 第 1 次就拿到正确答案(directAnswer ="egalitarian" confidence=high)后仍继续调工具重研究、直到墙钟耗尽、从不 emit FINAL ANSWER。 新增 GAIA 专用 reflection 规则:本轮某次 deepResearch 返回 high/medium 置信 的非空 directAnswer、且尚无合格 FINAL ANSWER 时,retry + forceNoTools 并把 该 directAnswer 直接喂回,逼模型输出 `FINAL ANSWER: <directAnswer>`。排在 missingFinalAnswer 之前(给具体答案而非泛化提示)。规则为纯函数、7 个单测覆盖。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
官方现直接以 USD per 1M tokens 报价(不再需 CNY÷7 折算): - mimo-v2.5-pro: input(miss) 0.43→0.435 / output 0.86→0.87 / cache hit 0.0036 - mimo-v2.5: input 0.14 / output 0.29→0.28 / cache hit 0.0029→0.0028 注释与 pricing.test.ts 断言同步更新。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
新增
deepResearch—— 一个把「分解 → 并行搜索 → 并发抓取 → 逐页抽取压缩 → 充分性判断 → 改写再搜」整套多跳循环封进单个工具内部的子代理工具。主对话只收到压缩后的结论 + 引用,原始网页永不进入主 loop 上下文,对标 Claude Code subagent + 结果过滤模式。对标做法:Codex(缓存优先+抗注入)/ Claude CLI(agentic 链式 + 代码动态过滤)/ NotebookLM(管道内 RAG)。
关键设计:
result-summarizer.ts范式:generateText/generateObject+createTimeoutController(30s)+ 失败降级,弱模型(MiMo)畸形输出循环不崩;webSearch(Tavily)/webFetch(Readability)的.execute,不重写;MAX_TOTAL_FETCHES=12/FETCH_CONCURRENCY=4/maxHops≤5/maxSubQueries≤6;ai-handlers)、计划执行 (plan-runner)、fork-skill (fork-skill-runner);门控 = Tavily 凭证 + model 句柄;改动
src/main/core/agent/tools/deep-research.ts—— 新工具agent-tools.ts/ai-handlers.ts/plan-runner.ts/fork-skill-runner.ts—— 生产三路接线web-search.ts/web-fetch.ts/system-prompt.ts—— 描述与系统提示优化eval/gaia/tool-registry.ts/eval/gaia/runner.ts—— GAIA eval 接入Test plan
deep-research.test.ts:覆盖 sufficient 停止 / 多跳 / URL 去重 / fetch 上限钳制 / 内层畸形输出降级 / 预 abort / 输出紧凑性 7 个用例agent-tools.test.ts加注册门控断言tool-contract.test.ts加 deepResearch 注册条件断言tsc --noEmit0 errorgaia-eval对比启用前后多跳题正确率(注意 token 开销会上升)🤖 Generated with Claude Code