Tools 工具组

WebSearchTool:联网搜索

这个工具到底做什么

WebSearchTool 负责让 Claude Code 查询互联网的最新信息。
它解决的核心问题不是“打开某个网页”,而是:

当主线程需要知道当前世界上最近发生了什么、某个产品最新文档是什么、某个问题有哪些公开资料时,如何安全地发起联网搜索。

所以它和 WebFetchTool 的区别一定要先记清:

  • WebSearchTool:找信息源
  • WebFetchTool:读指定页面

在真实使用里,这两个工具经常是一前一后配合。

它的 schema 很简单,但能力很强

tools/WebSearchTool/WebSearchTool.ts

const inputSchema = z.strictObject({
  query: z.string().min(2).describe('The search query to use'),
  allowed_domains: z.array(z.string()).optional(),
  blocked_domains: z.array(z.string()).optional(),
})

看起来只有 3 个字段,但已经涵盖了最关键的搜索控制:

  • 搜什么
  • 只搜哪些域名
  • 排除哪些域名

这意味着 Claude Code 的联网搜索不是“随便搜一下”,而是支持受约束的搜索。

它不是 Bash 调搜索引擎,而是接 Anthropic 的 Web Search 能力

源码里最关键的一段是这部分工具 schema 构造:

function makeToolSchema(input: Input): BetaWebSearchTool20250305 {
  return {
    type: 'web_search_20250305',
    name: 'web_search',
    allowed_domains: input.allowed_domains,
    blocked_domains: input.blocked_domains,
    max_uses: 8,
  }
}

这说明 WebSearchTool 不是自己去模拟浏览器,也不是 shell 调第三方搜索接口。
它实际上是把官方的 Web Search server tool 接进了 Claude Code 的工具系统。

换句话说,它做的是:

由 Claude Code 主线程触发,再由底层支持 Web Search 的模型能力去执行搜索

一张图看搜索链路

加载图表中...

它的核心调用方式很值得研究

call() 里这段代码最能说明它的本质:

const queryStream = queryModelWithStreaming({
  messages: [userMessage],
  systemPrompt: asSystemPrompt([
    'You are an assistant for performing a web search tool use',
  ]),
  tools: [],
  options: {
    extraToolSchemas: [toolSchema],
    querySource: 'web_search_tool',
    ...
  },
})

这里有几个关键点:

  1. 它自己重新发起了一次模型调用
  2. 这次调用的目的不是普通回答,而是执行搜索
  3. 搜索工具是通过 extraToolSchemas 注入进去的

这说明 WebSearchTool 其实是一个包装型工具

  • 外层是 Claude Code 的标准工具
  • 内层再发起一次支持 web search 的模型请求

它为什么不是直接把结果返回,而是要先解析内容块

源码里有一段非常关键的解析逻辑:

function makeOutputFromSearchResponse(
  result: BetaContentBlock[],
  query: string,
  durationSeconds: number,
): Output

注释写得很直白:返回内容不是一个单纯数组,而是一串混合块:

  • server_tool_use
  • web_search_tool_result
  • text
  • citation 相关块

所以 WebSearchTool 的一个核心工作就是:

把底层返回的搜索结果块重新整理成 Claude Code 更容易消费的结构

它的输出其实是“搜索结果 + 说明文本”的混合体

输出 schema 里有一段非常重要:

results: z
  .array(z.union([searchResultSchema(), z.string()]))
  .describe('Search results and/or text commentary from the model')

这意味着返回结果不是纯搜索命中列表,而可能同时包含:

  • 一组搜索 hits
  • 一段文字说明

换句话说,WebSearchTool 并不是只把链接丢回来,而是允许模型在搜索结果外再补一些中间解释。

这也是它比普通搜索 API 更像 Agent 工具的地方

普通搜索 API 常常返回:

  • 标题
  • URL
  • 摘要

而 Claude Code 的 WebSearchTool 还会处理:

  • 工具调用块
  • 文本解释块
  • 引用与来源要求

这让它更适合直接接入主循环,而不是只当一个数据源。

prompt 里对“来源”要求非常严格

tools/WebSearchTool/prompt.ts

CRITICAL REQUIREMENT - You MUST follow this:
  - After answering the user's question, you MUST include a "Sources:" section at the end of your response
  - In the Sources section, list all relevant URLs from the search results as markdown hyperlinks

这说明 Anthropic 对这个工具的产品要求很明确:

  • 只要用了联网搜索
  • 最终回答里就应该带来源

这对可信度和可验证性非常重要。

一张图看它和回答生成的关系

加载图表中...

从产品角度看,这也是 Claude Code 比“模型偷偷搜一下然后不告诉你来源”更成熟的地方。

它还会强制提醒“用当前年份搜索”

getWebSearchPrompt() 里还有一条很有意思的约束:

IMPORTANT - Use the correct year in search queries:
  - The current month is ${currentMonthYear}. You MUST use this year when searching for recent information

这说明 Anthropic 已经意识到一个非常真实的问题:

如果模型搜“latest React docs”却没带当前年份,很可能召回到旧内容

所以 prompt 明确要求它在搜“最新信息”时带上当前年份。
这是一个很典型的产品化修补动作。

它并不是在所有环境里都能开

isEnabled() 里可以看到它会检查 provider 和 model:

if (provider === 'firstParty') return true
if (provider === 'vertex') {
  const supportsWebSearch =
    model.includes('claude-opus-4') ||
    model.includes('claude-sonnet-4') ||
    model.includes('claude-haiku-4')
  return supportsWebSearch
}
if (provider === 'foundry') return true
return false

这说明 WebSearchTool 不是“所有 Claude Code 运行环境都能用”的固定能力。
它会受到:

  • API provider
  • 模型能力
  • 平台支持情况

的共同影响。

这也是为什么这类工具要单独做 isEnabled()

它的权限模型和 WebFetch 不一样

WebSearchTool 的权限逻辑更粗一点:

async checkPermissions(_input): Promise<PermissionResult> {
  return {
    behavior: 'passthrough',
    message: 'WebSearchTool requires permission.',
    suggestions: [
      {
        type: 'addRules',
        rules: [{ toolName: WEB_SEARCH_TOOL_NAME }],
        behavior: 'allow',
        destination: 'localSettings',
      },
    ],
  }
}

这说明它更偏“工具级权限”,不像 WebFetchTool 细化到域名级规则。
原因也很好理解:

  • WebSearchTool 处理的是泛化搜索
  • WebFetchTool 处理的是具体 URL 访问

后者在安全边界上天然更细。

它和 WebFetchTool 是最经典的一前一后组合

这两者的配合关系几乎可以画成固定模板:

加载图表中...

这个链路特别适合:

  • 最新文档查询
  • 新闻或最近公告
  • 对比多个资料来源
  • 从搜索命中里继续深挖单个页面

一次典型使用路径

比如用户问:

Claude Code 官方命令页最近怎么写 /usage 的?

主线程常见路径会是:

  1. WebSearchTool 搜官方文档或特定域名
  2. 找到命令文档页
  3. 再交给 WebFetchTool 抓正文
  4. 最后组织回答并附上来源

这就是为什么 WebSearchTool 更像“找入口”,而不是“直接完成研究”。

最容易误解它的地方

误解一:WebSearchTool 就是搜索引擎 API 包装

不完全对。
它还包括:

  • 模型发起的 server tool 调用
  • 内容块解析
  • 来源约束
  • 环境与 provider 判定

误解二:有了 WebSearchTool 就不需要 WebFetchTool

也不对。
搜索结果通常只是入口,真正看正文往往还得 WebFetchTool

误解三:它总能搜到最新内容

它只是提供联网搜索能力,不等于结果一定完美。
所以 Anthropic 还专门在 prompt 里要求:

  • 使用当前年份
  • 回答时列来源

这些都是为了降低“搜到旧资料”或“说不清来源”的风险。

小结

如果用一句话总结:

WebSearchTool 是 Claude Code 的联网检索入口,它通过官方 web search schema 发起搜索,再把底层搜索结果块整理成主循环可消费的结构化结果。

它真正重要的地方,不只是“能搜”,而是:

能把搜索结果、来源约束、模型环境和后续网页深读串成一条完整的联网工作流。