s-blog

Claude Code 的 Web Search 实现:一个服务端 server-tool 的客户端封装

它不自己发搜索请求,真正的搜索发生在 Anthropic API 端

ssssmy · 2026-06-11 · 4 min · LLM

接《深扒 Claude Code 源码》系列(工具与 Bash 安全篇)。这篇单独拆一个常被误解的点:Claude Code 的 WebSearch 到底是不是客户端自己去搜的。代码全在 src/tools/WebSearchTool/WebSearchTool.ts

一句话结论

不是客户端搜的。 WebSearch 是对 Anthropic 服务端 server-side tool 的封装——真正的搜索发生在 API 端,在单次 API 调用内由服务端自动完成;客户端只负责:把官方搜索工具的 schema 注入一次内部子查询、解析服务端回传的结果块、再把结果文本化喂回主对话。

怎么判定是「服务端工具」

三条硬证据:

  1. schema 是官方 server tool 类型makeToolSchemaWebSearchTool.ts:76-84)返回的是 { type: 'web_search_20250305', name: 'web_search', allowed_domains, blocked_domains, max_uses: 8 },类型 BetaWebSearchTool20250305 直接来自 @anthropic-ai/sdk 的 beta messages(:1-4)。
  2. call() 不发任何搜索 HTTP。它在 :268-291 发起一个内部 LLM 流式请求 queryModelWithStreaming,把这个 server tool 经 extraToolSchemas:[toolSchema] 注入(:284),而常规 tools:[] 留空(:276)。prompt 里明说 “Searches are performed automatically within a single API call”(prompt.ts:12)。
  3. 结果来自服务端专属内容块。客户端直接读 server_tool_use / web_search_tool_result 块(:103-139),用量也计入 usage.server_tool_use.web_search_requestsclaude.ts:2947-2950)——这是 server-side tool 才有的字段。

完整数据流

用户 query
  └─ WebSearchTool.call()
       ├─ 起一个内部子查询(queryModelWithStreaming)
       │    system: "You are an assistant for performing a web search tool use"
       │    tools: [],但 extraToolSchemas: [web_search server tool]
       │    (命中 A/B gate tengu_plum_vx3 时切小快模型 + 强制 tool_choice=web_search)
       ├─ 流里监听 server_tool_use   → 抽出 search id / query,发 query_update
       ├─ 流里监听 web_search_tool_result → 发 search_results_received
       ├─ makeOutputFromSearchResponse(:86-150) 把结果映射成 {title, url}[]
       └─ mapToolResultToToolResultBlockParam(:401-434)
            把结果文本化成 tool_result,强制追加一句:
            "You MUST include the sources above using markdown hyperlinks"

关键点:原始的 server tool 内容块只在工具内部那次子查询里流转,主对话循环最终只拿到文本化的「标题 + 链接」列表,外加那句强制要求模型用 markdown 超链接标注来源的指令。

input / 权限 / provider

输入 schema:25-37):

  • query: string(≥2 字符,必填)
  • allowed_domains?: string[](只搜这些域名)
  • blocked_domains?: string[](永不含这些域名)
  • 两个域名名单互斥validateInput:235-253)会拒绝同时传(errorCode 2);max_uses 硬编码为 8。

权限:200-222):isReadOnly()=trueisConcurrencySafe()=truecheckPermissions 返回 passthrough默认放行、不弹窗),并附带一条 behavior:'allow' 的规则建议。

provider 差异isEnabled:168-193)——这点很现实:

provider 是否支持 说明
firstParty(官方端点) ✅ 始终可用 服务端原生支持该 tool type,无需 beta header
Vertex ⚠️ 仅 opus-4 / sonnet-4 / haiku-4 需带 web-search-2025-03-05 header
Foundry ✅ 始终 带 header
Bedrock ❌ 不支持 isEnabled 直接返回 false

beta 常量 WEB_SEARCH_BETA_HEADER = 'web-search-2025-03-05'betas.ts:9),应用逻辑在 utils/betas.ts:345-352。另外 WebSearch 默认是延迟加载(shouldDefer:true:156——模型一开始只看到名字+提示,得先调 ToolSearch 才能完整调用它。

和 WebFetch 的分工

两个工具互补,别搞混:

WebSearch WebFetch
干什么 服务端搜索找链接 客户端抓页面正文
在哪执行 Anthropic API 端 本地 HTTP 抓取 + Haiku 摘要
输入 query + 域名名单 url + prompt
默认权限 passthrough(放行) ask(需批准)+ 域名白名单
计费字段 server_tool_use.web_search_requests web_fetch_requests

典型用法是先 WebSearch 找到 URL,再 WebFetch 抓那个页面的正文。两者共性:都 isReadOnlyisConcurrencySafemaxResultSizeChars=100_000shouldDefer


小结:WebSearch 看着像个"联网工具",本质却是把 Anthropic 服务端的 web_search server-tool 包了一层——客户端做的是"发起子查询 → 解析服务端块 → 文本化回灌 + 强制标注来源"。这也解释了为什么它只在 first-party / Vertex(部分) / Foundry 上可用,Bedrock 上直接没有。

证据均指向从 source map 还原的源码树,可逐一核对。

原文链接:https://www.ssssmy.com/notes/claude-code-websearch-impl