本页目录(6 节)

联网搜索

Spark Work 内置联网搜索 spark_search:web_search / fetch_url 工具、免密默认链(Bing / DuckDuckGo / 百度)、keyed 后端(博查 / Tavily / Serper)、配置项与降级策略。

阅读约 4 分钟最后核对 2026-06-29

SDK 自带的 WebSearch / WebFetch 是 Anthropic 第一方服务端工具, 一旦会话走第三方 OpenAI 兼容供应商就会被剥离。Spark Work 因此内置了独立的 spark_search MCP server:它在本地 Node 子进程里自己发 HTTP,与模型供应商完全解耦, 对普通会话与团队成员默认挂载,零配置即可用。

1. 工具与返回结构

命名空间是 mcp__spark_search__,只有两个工具(SDK 允许清单里的名字就是下面这两个):

工具参数说明
web_searchquery(必填)· count(1~20,默认 8)· time_range(day / week / month / year / all,默认 all)· site(限定域名)联网搜索,返回排序结果,命中解析后自动去重
fetch_urlurl(必填,仅 http/https)· max_chars(默认 8000,最大 50000)抓取网页并清洗成可读正文,替代失效的 WebFetch

time_range 只对 keyed 后端(bocha / tavily / serper)精确生效;免密 HTML 引擎会忽略它。site 是拼进查询串的(site:example.com 之类写法同样可用)。

web_search 的返回对象:

{
  provider: "bocha" | "tavily" | "serper" | "bing" | "duckduckgo" | "baidu",
  query: "...",
  results: [{ title, url, snippet, source?, date?, score? }],
  warnings?: ["tavily: HTTP 429 ...", ...],   // 降级过程说明
  answer?: "..."                              // tavily / serper 的摘要(如果渠道给了)
}

fetch_url 的返回对象:

{
  url: "<跟随重定向后的最终地址>",
  title?: "<HTML 的 <title>,非 HTML 时没有>",
  contentType: "text/html; charset=utf-8",
  truncated: true | false,
  chars: 12000,
  text: "<正文;被截断时末尾会附加 [truncated, N chars total]>"
}

内容类型处理:JSON 原样返回;HTML/XML(或内容看起来像 HTML)会剥标签、解 HTML 实体、压缩空白后转成 文本;其它类型原样返回。正文超过 max_chars 会截断,参数被夹在 500~50000 之间。

2. 后端与降级链

免密默认链(零 key、零配置,顺序固定):

  1. bing → https://www.bing.com/search
  2. duckduckgo → https://html.duckduckgo.com/html/
  3. baidu → https://www.baidu.com/s(末级回退)

链式规则:某个引擎抛错或返回 0 条结果,就往下走,并把原因累积到 warnings 里返回。 三个都失败才抛错,错误信息形如 All keyless engines failed. bing: GET https://www.bing.com/search timed out [timeout] | ...。 请求会带桌面 Chrome 的 User-Agent 与 Accept-Language: zh-CN,zh;q=0.9,en;q=0.8。

keyed 后端(配了 key 自动优先,质量更高):

provider默认地址请求要点
bocha(博查,auto 时的默认 keyed 选择)https://api.bochaai.com(/v1/web-search)POST,Authorization: Bearer;time_range 映射成 freshness:oneDay / oneWeek / oneMonth / oneYear / noLimit
tavilyhttps://api.tavily.com(/search)POST,key 在 body 的 api_key;search_depth: basic、include_answer: true;day/week/month 会切 topic: news + days,year 不支持
serper(Google)https://google.serper.dev(/search)POST,X-API-KEY;time_range 映射成 tbs:qdr:d / qdr:w / qdr:m / qdr:y

免密引擎也可被单独指定:provider 填 bing / baidu / duckduckgo 时只用那一个,不再链式降级。

keyed 后端出错或 0 结果时不会直接失败,而是回落免密链,并在 warnings 里保留原因,所以「配置了 key 但结果看起来是白牌的」通常意味着 keyed 调用失败了。

3. 配置 keyed 后端

配置读的是 app_settings 表里分类为 webSearch 的三个键, 每次解析 MCP server 时实时读取:

键取值说明
providerauto(默认)/ bocha / tavily / serper / bing / baidu / duckduckgoauto = 有 apiKey 就走 keyed(默认 bocha),否则走免密链
apiKeystring仅 bocha / tavily / serper 需要;留空则不会走 keyed
baseUrlstring可选,覆盖 keyed 后端的 base url(自建代理、镜像时用)

当前版本没有对应的设置页面,最快的写法是让 Agent 调 mcp__spark_platform__settings_set:

settings_set({ category: "webSearch", key: "provider", value: "bocha" })
settings_set({ category: "webSearch", key: "apiKey", value: "<你的 key>" })
settings_set({ category: "webSearch", key: "baseUrl", value: "https://api.bochaai.com" })   # 可选

读取入口在子进程侧,对应这三个环境变量(key 只存在于该子进程内存):

SPARK_SEARCH_PROVIDER        auto | bocha | tavily | serper | bing | baidu | duckduckgo
SPARK_SEARCH_API_KEY         keyed 后端的 key
SPARK_SEARCH_BASE_URL        keyed 后端 base url 覆盖
SPARK_SEARCH_TIMEOUT_MS      单次请求超时,默认 15000(1000~60000)
SPARK_SEARCH_TOTAL_TIMEOUT_MS  单次搜索总时限,默认 20000(1000~90000)
SPARK_SEARCH_MAX_RETRIES     可重试错误的最大重试次数,默认 1(0~3)
SPARK_SEARCH_RETRY_BACKOFF_MS  退避基数,默认 250(1~2000)
SPARK_SEARCH_FETCH_MAX_CHARS fetch_url 默认正文上限,默认 8000(500~50000)
SPARK_SEARCH_BING_URL / SPARK_SEARCH_BAIDU_URL / SPARK_SEARCH_DUCKDUCKGO_URL  覆盖免密引擎地址

其中前三个变量由应用按 webSearch 设置实时注入;其余是部署级逃生口,只有你自己 在启动环境里设置时才生效。

4. 超时、重试与限额

项默认说明
单次请求超时15 s可配 1~60 s
单次搜索总时限20 s可配 1~90 s;HTML 正文读取也共享这个 deadline
可重试错误重试次数1可配 0~3;重试退避基数 250 ms,可配 1~2000 ms
结果条数8工具参数 1~20;keyed 渠道请求里也会夹到 20
fetch_url 正文上限8000 字符参数 500~50000

哪些错误算「可重试」:timeout、network、rate_limited(429)、server_error(5xx)。会读取响应里的 Retry-After(秒数或 HTTP 日期), 但退避上限 2000 ms。4xx(除 429)与 invalid_response(返回体不是 JSON)不重试。

错误信息里带方括号分类标签,便于快速判断:

GET https://www.bing.com/search timed out [timeout]
GET https://api.tavily.com/search failed: fetch failed [network]
POST https://google.serper.dev/search returned HTTP 429 [rate_limited]
POST https://api.bochaai.com/v1/web-search returned HTTP 500 [server_error]
POST https://api.bochaai.com/v1/web-search returned HTTP 401 [http_4xx]
Non-JSON response from https://... [invalid_response]
GET https://... exceeded request budget [budget_exhausted]

5. 在会话里怎么用

不需要任何配置,直接派活:

帮我搜一下最近一周关于 React Server Components 的最佳实践,整理成 5 条要点并给出链接。

Agent 会先 web_search 拿候选,再对关键页面 fetch_url 读全文,最后给出带 出处的结论。注入的 system prompt(WEB_SEARCH_SYSTEM_PROMPT)明确要求:时效性问题要 主动核实而不是靠模型记忆、优先一手来源、结论附近给 URL、不要把「没搜到」当成「不存在」。

挂载范围:

  • 宿主会话默认挂载,工具名会合并进 SDK 的允许清单。
  • 团队成员默认也挂载,与 Host 走同一套解析。
  • 例外:显式标记为只读的「原子」成员从空能力集开始,不会拿到 spark_search。
  • 脚本文件缺失时(web-search-mcp-server.mjs 找不到或缺少依赖)会记 warn 并跳过, 此时工具列表里不会出现这两个工具。

配套技能是内置技能 builtin:multi-search-engine,它的 requiredTools 就是这两个工具,内容是多角度检索、交叉验证、给出处的操作规范。

6. 实现位置与排查

  • MCP server:packages/agent-runtime/src/tools/web-search-mcp-server.mjs
  • HTTP/重试/deadline 工具:packages/agent-runtime/src/tools/web-search-http.mjs
  • 接线:SessionService.resolveWebSearchMcpServer()(读 webSearch 设置并注入 环境变量)+ 各轮次的 mcpServers.spark_search / allowedTools 合并
  • 工具允许清单:SEARCH_TOOL_NAMES(session-mcp-tooling-helpers.ts)
  • 提示词:WEB_SEARCH_SYSTEM_PROMPT(同文件)
  • 伴随技能:apps/desktop/resources/skills/multi-search-engine/
现象排查
Agent 说不能联网先确认工具列表里有没有 mcp__spark_search__web_search;没有就是 MCP 脚本没解析成功(脚本缺失或依赖不全),看主进程日志里的 warn。
结果都是白牌 / 质量低说明回落到了免密链。检查 app_settings.webSearch 的 provider 与 apiKey;返回结果里的 warnings 会写明细。
报 All keyless engines failed三个免密引擎都被限流或网络不通。换 keyed 后端,或确认所在网络能直连这几个域名。
time_range 没生效免密引擎不支持时间过滤;只有 bocha / tavily / serper 会翻译该参数(tavily 不支持 year)。
抓到的正文太短页面可能是 JS 渲染的空壳。fetch_url 不做渲染,这种页面请改用浏览器自动化。

速查表

MCP 与工具
spark_search:mcp__spark_search__web_search / mcp__spark_search__fetch_url
免密默认链
www.bing.com → html.duckduckgo.com → www.baidu.com(失败/0 结果自动降级并写入 warnings)
keyed 后端
bocha(api.bochaai.com/v1/web-search)· tavily(api.tavily.com/search)· serper(google.serper.dev/search)
配置位置
app_settings.webSearch.{provider, apiKey, baseUrl},provider 默认 auto
超时
单次 15s(1~60s)· 单次搜索总时限 20s(1~90s)· 可重试错误重试 1 次(0~3)
fetch_url 正文上限
默认 8000 字符,范围 500~50000
条件变量
SPARK_SEARCH_PROVIDER / _API_KEY / _BASE_URL / _TIMEOUT_MS / _TOTAL_TIMEOUT_MS / _MAX_RETRIES / _RETRY_BACKOFF_MS / _FETCH_MAX_CHARS

常见问题

展开 6 条常见问题
  • 为什么走第三方供应商后 WebSearch 失效?

    Anthropic 自带的 WebSearch / WebFetch 是服务端工具,第三方 OpenAI 兼容网关不会提供,所以会被剥离。spark_search 在本地子进程发 HTTP,与供应商解耦,任意供应商下都可用。

  • 需要 key 吗?

    不需要。免密链 Bing → DuckDuckGo → 百度 零配置可用。填 key 只是获得更高质量的结果与精确的 time_range 过滤。

  • key 会泄露给模型吗?

    不会。key 只作为环境变量注入搜索子进程内存,不写进提示词,也不随工具结果返回。

  • 团队成员也能用吗?

    默认可以,成员与 Host 走同一套解析。但显式标记为只读的原子成员从空能力集开始,不会挂载 spark_search。

  • 在哪儿填 key?

    配置存在 app_settings 的 webSearch 分类(provider / apiKey / baseUrl)。当前版本没有对应的设置页面,最方便的方式是让 Agent 调 mcp__spark_platform__settings_set 写入。

  • 搜索会返回几条?

    count 参数 1~20,默认 8;超过 20 会被夹到 20,keyed 渠道请求里也按 20 上限发送。