Files
claude-code/docs/features/web-search-tool.md
2026-04-03 10:56:52 +08:00

187 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WEB_SEARCH_TOOL — 网页搜索工具
> 实现状态适配器架构完成Bing 适配器为当前默认后端
> 引用数:核心工具,无 feature flag 门控(始终启用)
## 一、功能概述
WebSearchTool 让模型可以搜索互联网获取最新信息。原始实现仅支持 Anthropic API 服务端搜索(`web_search_20250305` server tool在第三方代理端点下不可用。现已重构为适配器架构新增 Bing 搜索页面解析作为 fallback确保任何 API 端点都能使用搜索功能。
## 二、实现架构
### 2.1 适配器模式
```
WebSearchTool.call()
createAdapter() ← 适配器工厂
├── ApiSearchAdapter — Anthropic 官方 API 服务端搜索
│ └── 使用 web_search_20250305 server tool
│ 通过 queryModelWithStreaming 二次调用 API
└── BingSearchAdapter — Bing HTML 抓取 + 正则提取(当前默认)
└── 直接抓取 Bing 搜索页 HTML
正则提取 b_algo 块中的标题/URL/摘要
```
### 2.2 模块结构
| 模块 | 文件 | 说明 |
|------|------|------|
| 工具入口 | `src/tools/WebSearchTool/WebSearchTool.ts` | `buildTool()` 定义schema、权限、执行、输出格式化 |
| 工具 prompt | `src/tools/WebSearchTool/prompt.ts` | 搜索工具的系统提示词 |
| UI 渲染 | `src/tools/WebSearchTool/UI.tsx` | 搜索结果的终端渲染组件 |
| 适配器接口 | `src/tools/WebSearchTool/adapters/types.ts` | `WebSearchAdapter` 接口、`SearchResult`/`SearchOptions`/`SearchProgress` 类型 |
| 适配器工厂 | `src/tools/WebSearchTool/adapters/index.ts` | `createAdapter()` 工厂函数,选择后端 |
| API 适配器 | `src/tools/WebSearchTool/adapters/apiAdapter.ts` | 封装原有 `queryModelWithStreaming` 逻辑,使用 server tool |
| Bing 适配器 | `src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 抓取 + 正则解析 |
| 单元测试 | `src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 32 个测试用例 |
| 集成测试 | `src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 真实网络请求验证 |
### 2.3 数据流
```
模型调用 WebSearchTool(query, allowed_domains, blocked_domains)
validateInput() — 校验 query 非空、allowed/block 不共存
createAdapter() → BingSearchAdapter当前硬编码
adapter.search(query, { allowedDomains, blockedDomains, signal, onProgress })
├── onProgress({ type: 'query_update', query })
├── axios.get(bing.com/search?q=...&setmkt=en-US)
│ └── 13 个 Edge 浏览器请求头
├── extractBingResults(html) — 正则提取 <li class="b_algo"> 块
│ ├── resolveBingUrl() — 解码 base64 重定向 URL
│ ├── extractSnippet() — 三级降级摘要提取
│ └── decodeHtmlEntities() — he.decode
├── 客户端域名过滤 (allowedDomains / blockedDomains)
├── onProgress({ type: 'search_results_received', resultCount })
格式化为 markdown 链接列表返回给模型
```
## 三、Bing 适配器技术细节
### 3.1 反爬绕过
使用 13 个 Edge 浏览器请求头(含 `Sec-Ch-Ua``Sec-Fetch-*` 等),避免 Bing 返回 JS 渲染的空页面:
```typescript
const BROWSER_HEADERS = {
'User-Agent': '...Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0',
'Sec-Ch-Ua': '"Microsoft Edge";v="131", "Chromium";v="131", ...',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
// ... 共 13 个标头
}
```
`setmkt=en-US` 参数强制美式英语市场,避免 IP 地理定位导致区域化结果。
### 3.2 URL 解码(`resolveBingUrl()`
Bing 返回的重定向 URL 格式:`bing.com/ck/a?...&u=a1aHR0cHM6Ly9...`
- `u` 参数前 2 字符为协议前缀:`a1` = https`a0` = http
- 剩余部分为 base64url 编码的真实 URL
- Bing 内部链接和相对路径被过滤返回 `undefined`
### 3.3 摘要提取(`extractSnippet()`
三级降级策略:
1. `<p class="b_lineclamp...">` — Bing 的搜索摘要段落
2. `<div class="b_caption">` 内的 `<p>` — 备选摘要位置
3. `<div class="b_caption">` 直接文本 — 最终 fallback
### 3.4 域名过滤
客户端侧实现,支持子域名匹配:
- `allowedDomains`:白名单,结果域名必须匹配列表中的某项(含子域名)
- `blockedDomains`:黑名单,匹配的结果被过滤
- 两者不可同时使用(`validateInput` 校验)
## 四、适配器选择逻辑
当前 `createAdapter()` 硬编码返回 `BingSearchAdapter`,原逻辑已注释保留:
```typescript
export function createAdapter(): WebSearchAdapter {
return new BingSearchAdapter()
// 注释保留的选择逻辑:
// 1. WEB_SEARCH_ADAPTER 环境变量强制指定 api|bing
// 2. isFirstPartyAnthropicBaseUrl() → API 适配器
// 3. 第三方端点 → Bing 适配器
}
```
恢复自动选择:取消 `index.ts` 中的注释即可。
## 五、接口定义
### WebSearchAdapter
```typescript
interface WebSearchAdapter {
search(query: string, options: SearchOptions): Promise<SearchResult[]>
}
interface SearchResult {
title: string
url: string
snippet?: string
}
interface SearchOptions {
allowedDomains?: string[]
blockedDomains?: string[]
signal?: AbortSignal
onProgress?: (progress: SearchProgress) => void
}
interface SearchProgress {
type: 'query_update' | 'search_results_received'
query?: string
resultCount?: number
}
```
### 工具 Input Schema
```typescript
{
query: string // 搜索关键词,最少 2 字符
allowed_domains?: string[] // 域名白名单
blocked_domains?: string[] // 域名黑名单
}
```
## 六、文件索引
| 文件 | 职责 |
|------|------|
| `src/tools/WebSearchTool/WebSearchTool.ts` | 工具定义入口 |
| `src/tools/WebSearchTool/prompt.ts` | 搜索工具 prompt |
| `src/tools/WebSearchTool/UI.tsx` | 终端 UI 渲染 |
| `src/tools/WebSearchTool/adapters/types.ts` | 适配器接口 |
| `src/tools/WebSearchTool/adapters/index.ts` | 适配器工厂 |
| `src/tools/WebSearchTool/adapters/apiAdapter.ts` | API 服务端搜索适配器 |
| `src/tools/WebSearchTool/adapters/bingAdapter.ts` | Bing HTML 解析适配器 |
| `src/tools/WebSearchTool/__tests__/bingAdapter.test.ts` | 单元测试 (32 cases) |
| `src/tools/WebSearchTool/__tests__/bingAdapter.integration.ts` | 集成测试 |
| `src/tools.ts` | 工具注册 |