Claude Code 的 Web Search 实现:一个服务端 server-tool 的客户端封装
它不自己发搜索请求,真正的搜索发生在 Anthropic API 端
接《深扒 Claude Code 源码》系列(工具与 Bash 安全篇)。这篇单独拆一个常被误解的点:Claude Code 的 WebSearch 到底是不是客户端自己去搜的。代码全在
src/tools/WebSearchTool/WebSearchTool.ts。
一句话结论
不是客户端搜的。 WebSearch 是对 Anthropic 服务端 server-side tool 的封装——真正的搜索发生在 API 端,在单次 API 调用内由服务端自动完成;客户端只负责:把官方搜索工具的 schema 注入一次内部子查询、解析服务端回传的结果块、再把结果文本化喂回主对话。
怎么判定是「服务端工具」
三条硬证据:
- schema 是官方 server tool 类型。
makeToolSchema(WebSearchTool.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)。 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)。- 结果来自服务端专属内容块。客户端直接读
server_tool_use/web_search_tool_result块(:103-139),用量也计入usage.server_tool_use.web_search_requests(claude.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()=true、isConcurrencySafe()=true,checkPermissions 返回 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 抓那个页面的正文。两者共性:都 isReadOnly、isConcurrencySafe、maxResultSizeChars=100_000、shouldDefer。
小结:WebSearch 看着像个"联网工具",本质却是把 Anthropic 服务端的 web_search server-tool 包了一层——客户端做的是"发起子查询 → 解析服务端块 → 文本化回灌 + 强制标注来源"。这也解释了为什么它只在 first-party / Vertex(部分) / Foundry 上可用,Bedrock 上直接没有。
证据均指向从 source map 还原的源码树,可逐一核对。
原文链接:https://www.ssssmy.com/notes/claude-code-websearch-impl