Harness 的 web_search Tool
今天尝试理解了 web_search 在 Agent Harness 中是如何调用的。
"body": {
"id": "072c2cfe7fdde5e95bae19eaeef015d8",
"type": "message",
"role": "assistant",
"content": [
{
"type": "server_tool_use",
"id": "call_atk6nJiZMtZNEKbsIrCkoamC",
"name": "web_search",
"input": {
"query": "Claude Code WebSearch tool implementation how it works server side"
}
},
{
"type": "web_search_tool_result",
"tool_use_id": "call_atk6nJiZMtZNEKbsIrCkoamC",
"content": [
{
"type": "web_search_result",
"title": "Intrusion Detection Systems",也就是模型在想要进行网络搜索的时候,会发起一个 Web Search 的 Tool Call。由 Harness 完成搜索,把结果塞到响应里面继续循环。这个 WebSearch 的 Tool 是如何实现的,则是由 Harness 由代码定义的。Claude Code 就会连接到 Server Tool,当接入第三方模型的时候,如果他们也支持这个 Server Tool,就能直接使用。OpenCode 就会走到内建的几个大的 Search API 提供平台。而这些 Search API 平台都会提供对应的 MCP,则是一方面是给不支持 Server to Code 的平台使用,另一方面也可以固定使用某个平台的查询 API 来固定搜索行为。
特别是对 MiniMax,在控制台用量界面区分了两个工具 web_search 和 coding-plan-search,又在 coding-plan-search 下区分了来源。实际上从实测和 网络搜索 MCP 以及 服务端工具(Server Tools) 文档可知,搜索功能分为客户端和服务端实现,不论是 Tool Call 还是 MCP 最终都会在某个环境中调用具体的搜索服务 API。使用 Server Tools 和使用 MCP 实际上都一样,走到的都是服务端搜索,只是在用量账单和来源标记上有区别。
像 Exa、Firecrawl、Parallel、Tavily、TinyFish 以及最新的 DeepSeek 都属于提供 Search API 的服务商。如果 Harness 工具没有内建接入方式到自己的 web_search 中,就需要靠 MCP。所以 OpenCode 想要接入 MiniMax / DeepSeek 提供的 Search API 就需要靠 MCP。
七个搜索 API 的横向对比
今天把手头能用的搜索通道都拉出来跑了一遍:Tavily、Parallel、Firecrawl、Exa、MiniMax 的 CLI 和 MCP 两路,再用 Harness 内建的 WebSearch (Claude Code + MiniMax) 当基线。同一套查询分别喂给七个引擎,横向差异比预期大得多。
| 引擎 | 官方一手命中 | 噪声率 | 招牌能力 | 主要短板 |
|---|---|---|---|---|
| Parallel | 最高(≥70%) | 最低(10–15%) | llms.txt 全量索引,一次 extract 枚举整个文档站 | 英文查询偏弱,跨产品串味(同名论文、竞品) |
| Firecrawl | 高 | 低 | 抓取零失败;--country CN 与 US 双跑(两轮各 5 条只重叠 1 条);developer 模式能挖官方仓库与 issue | highlights 基本为空,结论得靠二次 scrape;官方 changelog 在普通搜索里排名极低 |
| Exa | 高 | 中 | objective 可控,highlights 直接带原文证据,省一次抓取 | 镜像双域不去重;对「官方到底有没有明文表态」这类否定型问题最弱 |
| Tavily | 中高 | 高 | 中文召回最高(72.7%)、去重率最低 | 容易跑到同名主题;--json 必须放子命令前,批量 extract 易 exit 4 |
| MiniMax CLI | 官方优先 | 中高 | 官方中文 changelog 抓得全 | 精确术语召回差,得靠泛词 + 抓 changelog 反推官方命名 |
| MiniMax MCP | 中 | 中 | 唯一能直接进官方开源仓库源码(api.github.com tree + raw),机制类问题能从代码回答 | 官方文档站多级跳转最终 403,正文只能靠 raw;tracking 参数不去重、不区分 fork |
| 内置 WebSearch | 最低 | 最高 | 结果最多 | 官方一手常为 0,自带摘要会编精确数字,必须回抓原文核实 |
推荐优先级:
- 中文长尾:Tavily 打召回 → Parallel 保准确 → MiniMax 系补中文一手与源码
- 英文官方文档:Firecrawl → Parallel → Exa
- 通用组合:Exa 或 Parallel 主检索,Firecrawl 与 MiniMax MCP 补一手源码,Tavily 补中文长尾
只能留一个的话选 Exa MCP。它的 objective 能把「要哪类文档、排除什么、抽哪些事实」写死,同一套查询在中英文场景都可控;更关键的是 highlights 直接带原文证据,能省一轮抓取——而官方文档站普遍有 404/403/多级跳转,二次抓取经常直接失败。Runner-up 是 Parallel,官方命中率和噪声控制都最好,只是英文查询容易泛。
几条通用经验:官方文档站几乎都会暴露 llms.txt 和每页的 .md 原文,抓正文优先走这两个入口,HTML 页经常 404;GitHub 用 raw 加 api.github.com contents 逐级下钻能读到源码;Firecrawl 的 --country 得中文英文各跑一遍再取并集;Exa 的 objective 里写具体字段名和版本号能显著提高 issue 命中率;Tavily 抓不了中文镜像站,但中文召回反而最高。
基于此,我会给 Claude Code 添加 Exa MCP,并且关闭自带的 Web Search,来避免使用 MiniMax 模型的时候顶向 MiniMax 的后端,或者直接改为使用 OpenCode。
Windows 系统没有 readline 导致 tvly 的 REPL 崩溃
tavily-cli 0.1.8 在 Windows 上敲 tvly 报 ModuleNotFoundError: No module named 'readline'。readline 是 Unix 专属的 CPython 标准库模块,Windows 版 Python 不提供(本机是 vfox 装的 3.14.3),而 tavily_cli/repl.py:5 无条件 import 它,包自己没做保护。上游没修,PyPI 上 0.1.8 已经是最新版,gnureadline 又没有 Windows wheel,能补的只剩 pyreadline3。
解决办法是让 uv 带额外依赖重装:
uv tool install --with pyreadline3 tavily-cli装上的是 pyreadline3 3.5.6,纯 Python wheel(py3-none-any),由它提供 readline 模块。用 --with 而不是手动 uv pip install,是因为 uv 会把额外依赖写进 uv-receipt.toml 的 requirements,uv tool upgrade 重新解析时不会丢,tvly update 走的也是 uv tool upgrade。这里用 uv 装是因为缺的是 Python 模块,winget 和 bin 都覆盖不到。
装完裸 tvly 在 Git Bash、cmd、PowerShell 三边都能进 REPL 并正常退出,tvly search、tvly extract、tvly map、tvly --status 都正常。