Files
claude-code/docs/design/tool-search-design-guide.md
claude-code-best 2cf18c4c49 docs: 添加 ToolSearch 设计指南 + 禁用 turn-zero 工具推荐弹窗
- 新增 docs/design/tool-search-design-guide.md,涵盖架构、搜索算法、执行管道、演进历史
- 禁用 getTurnZeroSearchExtraToolsPrefetch,消除用户输入时的频繁弹窗
- inter-turn 发现机制保持不变

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
2026-05-09 16:45:56 +08:00

324 lines
16 KiB
Markdown
Raw Permalink 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.
# ToolSearch 设计指南
> 基于 feature/tool_search 分支的 4 次 commit 迭代,系统性地记录 ToolSearch 的架构、核心机制、演进历史和维护指南。
## 1. 问题背景
Claude Code 内置了 60+ 工具,加上用户连接的 MCP 服务器可能引入数十甚至上百个额外工具。将所有工具的完整 schema 一次性发送给模型,会产生几个严重问题:
1. **Token 爆炸** — 每个工具定义name + description + inputSchema平均消耗数百 token60 个工具就是数万 token 的常量开销。
2. **Prompt Cache 失效** — 工具列表作为 prompt 的一部分参与缓存计算。任何工具的增减(如 MCP 服务器连接/断开)都会导致整段缓存失效。
3. **模型注意力稀释** — 过多的工具定义干扰模型对核心工具的选择准确性。
## 2. 解决方案概览
ToolSearch 采用 **延迟加载Deferred Loading** 模式:
- 将工具分为 **Core Tools**(始终加载)和 **Deferred Tools**(按需发现)
- 模型通过 `SearchExtraTools` 工具搜索并发现 deferred tools
- 通过 `ExecuteExtraTool` 工具代理执行发现的 deferred tools
- **工具数组在会话中保持稳定**,不再动态注入已发现的 deferred toolsv3 修复的关键决策)
## 3. 核心架构
### 3.1 工具分类体系
```
┌─────────────────────────────────────────────────────────────┐
│ All Tools (60+ built-in + MCP) │
├───────────────────────────┬─────────────────────────────────┤
│ Core Tools (29 个) │ Deferred Tools (其余全部) │
│ 始终加载,直接调用 │ 不加载 schema按需发现 │
│ CORE_TOOLS 白名单定义 │ isDeferredTool() 判定 │
└───────────────────────────┴─────────────────────────────────┘
```
**Core Tools**`src/constants/tools.ts` 中的 `CORE_TOOLS` Set
| 类别 | 工具 |
|------|------|
| 文件操作 | Bash/Shell, Read, Edit, Write, Glob, Grep, NotebookEdit |
| Agent 交互 | Agent, AskUserQuestion |
| 任务管理 | TaskOutput, TaskStop, TaskCreate, TaskGet, TaskList, TaskUpdate, TodoWrite |
| 规划 | EnterPlanMode, ExitPlanMode, VerifyPlanExecution |
| Web | WebFetch, WebSearch |
| 代码智能 | LSP |
| 技能 | Skill |
| 调度/监控 | Sleep |
| 工具发现 | SearchExtraTools, ExecuteExtraTool, SyntheticOutput |
**isDeferredTool 判定逻辑**`packages/builtin-tools/src/tools/SearchExtraToolsTool/prompt.ts`
```
isDeferredTool(tool) =
tool.alwaysLoad === true? → false显式跳过延迟
CORE_TOOLS.has(tool.name)? → false核心工具不延迟
otherwise → true其余全部延迟
```
### 3.2 三层组件架构
```
┌──────────────────────────────────────────────────────┐
│ API Layer (src/services/api/claude.ts) │
│ ├─ 判定是否启用 ToolSearch │
│ ├─ 过滤 deferred tools 不进入 API tools 数组 │
│ ├─ 注入 <available-deferred-tools> 或 delta 附件 │
│ └─ 处理 tool_reference/text 格式的消息归一化 │
├──────────────────────────────────────────────────────┤
│ Query Loop (src/query.ts) │
│ ├─ Turn-zero 预取:用户输入时触发 │
│ └─ Inter-turn 预取assistant turn 后异步触发 │
├──────────────────────────────────────────────────────┤
│ Search Engine │
│ ├─ SearchExtraToolsTool — 搜索入口4 种查询模式) │
│ ├─ TF-IDF Index (toolIndex.ts) — 语义搜索 │
│ ├─ Keyword Search — 精确匹配 │
│ └─ ExecuteExtraTool — 代理执行 │
└──────────────────────────────────────────────────────┘
```
### 3.3 搜索引擎设计
SearchExtraToolsTool 支持四种查询模式:
| 模式 | 语法 | 行为 | 返回 |
|------|------|------|------|
| **Select** | `select:CronCreate,Snip` | 按名称直接获取,逗号分隔多选 | 精确匹配列表 |
| **Discover** | `discover:schedule cron job` | 纯发现模式,返回描述+schema | 工具信息文本 |
| **Keyword** | `notebook jupyter` | 关键词搜索 | 按相关性排序 |
| **Required** | `+slack send` | `+` 前缀强制包含 | 包含必选词的结果 |
**混合搜索算法**
```
最终分数 = 关键词分数 × 0.4 + TF-IDF 分数 × 0.6
```
- **Keyword Search**基于工具名解析CamelCase 分词、MCP 前缀拆解、searchHint 匹配、描述文本匹配,加权计分
- **TF-IDF Search**:复用 `skillSearch/localSearch.ts` 的算法,对 name (3.0)、searchHint (2.5)、description (1.0) 三个字段加权计算 TF-IDF 向量
**MCP 工具名解析**
```
mcp__slack__send_message → parts: ["slack", "send", "message"]
CamelCase → parts: ["cron", "create"]
```
### 3.4 执行管道
```
模型调用 ExecuteExtraTool({tool_name: "CronCreate", params: {...}})
ExecuteTool.call() 在全局工具注册表中查找 CronCreate
检查目标工具 isEnabled() — 桥接/条件工具可能不可用
委托目标工具的 checkPermissions() — 权限传递给实际工具
调用目标工具的 call() — 与直接调用完全等价
返回结果(包装为 ExecuteExtraTool 的 output schema
```
关键设计ExecuteExtraTool 的 `checkPermissions()` 返回 `passthrough`,将权限决策完全委托给目标工具。它本身不引入额外的权限层。
### 3.5 Prompt Cache 稳定性策略v3 关键修复)
**问题**:早期版本在发现 deferred tool 后会将其注入 API tools 数组,导致每次发现新工具时 tools JSON 变化prompt cache 全面失效。
**修复**commit `c14b7ead`deferred tools **始终不进入 API tools 数组**。tools 数组在整个会话中只包含 core tools + SearchExtraTools + ExecuteExtraTool保持稳定。
```
API Tools 数组(会话期间不变):
[Core Tools (29)] + [SearchExtraTools, ExecuteExtraTool, SyntheticOutput]
不包含: 任何 deferred tool即使已被发现
执行方式: 通过 ExecuteExtraTool 代理调用
```
## 4. 预取机制Prefetch
### 4.1 两个触发时机
1. **Turn-zero**`getTurnZeroSearchExtraToolsPrefetch`)— 用户输入第一轮时,基于输入文本搜索相关 deferred tools以 attachment 形式注入
2. **Inter-turn**`startSearchExtraToolsPrefetch`)— assistant turn 结束后,基于对话上下文异步搜索
### 4.2 Attachment 管道
```
prefetch → Attachment(type: 'tool_discovery')
→ messages.ts 转换为 system-reminder
→ "The following tools were discovered... Use ExecuteExtraTool to invoke..."
```
### 4.3 会话去重
`discoveredToolsThisSession` Set 跟踪已发现的工具,避免重复推荐。该 Set 独立于 skill prefetch 的去重集合,互不影响。使用 `addBoundedSessionEntry()` 保持上限 500 条,超出时裁剪到 400 条。
## 5. 模式切换系统
通过环境变量 `ENABLE_SEARCH_EXTRA_TOOLS` 控制:
| 环境变量值 | 模式 | 行为 |
|-----------|------|------|
| 未设置 | `tst` | 默认启用,始终延迟非核心工具 |
| `true` | `tst` | 强制启用 |
| `false` | `standard` | 完全禁用,所有工具内联加载 |
| `auto` | `tst-auto` | 仅当 deferred tools 超过上下文窗口 10% 时启用 |
| `auto:N` | `tst-auto` | 自定义阈值百分比N=0 启用N=100 禁用) |
| `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1` | `standard` | 全局 kill switch |
`isSearchExtraToolsEnabledOptimistic()` — 快速判断(不检查阈值),用于工具注册
`isSearchExtraToolsEnabled()` — 完整判断(含阈值检查),用于 API 调用
## 6. Deferred Tools Delta 机制
对于 Anthropic 内部用户(`USER_TYPE=ant`)或启用了 `tengu_glacier_2xr` feature flag 的用户,使用 **delta attachment** 替代 `<available-deferred-tools>` 头部注入:
- 首次:注入完整的 deferred tools 列表
- 后续:只注入增量变化(新增/移除)
- 优势:不会因为工具池变化导致整个头部缓存失效
Delta attachment 扫描历史消息中的 `deferred_tools_delta` 类型 attachment重建已宣告集合然后差分计算当前 deferred pool 的变化。
## 7. 演进历史
### v1: 基础设施层(`7be08f53`
**34 个文件,+4040/-90 行**
- 定义 `CORE_TOOLS` 白名单31 个核心工具)
- 实现 TF-IDF 工具索引模块 `toolIndex.ts`
- 创建 `ExecuteTool` 作为统一执行入口
- 增强 ToolSearchToolTF-IDF 搜索路径、discover 模式、并行搜索合并
- 新增 27 个单元测试
- 实现预取管道和 UI 组件
**关键文件**
- `src/services/toolSearch/toolIndex.ts` → 后续重命名为 `searchExtraTools/toolIndex.ts`
- `packages/builtin-tools/src/tools/ExecuteTool/` — 执行入口
- `src/constants/tools.ts` — CORE_TOOLS 定义
### v2: 统一自建搜索(`8c157f07`
**17 个文件,+274/-395 行**(净减少 121 行)
- **移除 `tool_reference` blocks** — 不再依赖 Anthropic API 的 `tool_reference` 功能
- **移除 `defer_loading` 字段** — 不再发送 API 级别的工具延迟加载标记
- **移除 `modelSupportsToolReference()`** — 不再区分模型是否支持 tool_reference
- **重命名 ExecuteTool → ExecuteExtraTool** — 更清晰地表达其作为代理执行器的角色
- **输出改为纯文本** — 所有 provider 通用,无需特殊 API 功能支持
- **简化 system prompt** — 工具使用指南从 ~120 行压缩到 ~10 行
**设计决策**:这次重构的核心洞察是 — 依赖 Anthropic 私有 API 特性tool_reference、defer_loading、beta header使得系统只能用于 first-party provider。自建 TF-IDF + keyword 搜索完全能满足需求,且对所有 providerOpenAI、Gemini、Grok通用。
### v3: Cache 稳定性修复(`c14b7ead`
**7 个文件,+46/-31 行**
- **移除 "discover then include" 逻辑** — 发现的 deferred tools 不再注入 tools 数组
- **tools 数组保持稳定** — 只有 core tools + SearchExtraTools + ExecuteExtraTool
- **强化优先级引导** — core tools 直接调用ToolSearch 仅作为发现 deferred tools 的手段
- **已加载工具拒绝提示** — 搜索 core tool 时返回明确拒绝
**设计决策**prompt cache 是 Claude Code 性能优化的关键。每次 tools JSON 变化都会导致缓存失效,代价远大于通过 ExecuteExtraTool 代理调用 deferred tools 的额外 token。因此选择牺牲一点直接调用的便利性换取 cache 稳定性。
### v4: Agents/Teams 延迟化(`af0d7dc8`
**7 个文件,+36/-18 行**
-`TeamCreate``TeamDelete``SendMessage` 从 CORE_TOOLS 移除
- 这些工具仅在 swarm 模式下常用,平时占用 context token
- swarm 模式下 SendMessage 保持 always loaded
- TeamCreate/TeamDelete 在 swarm 未启用时返回启用提示
**设计决策**:不是所有用户都需要团队功能。将其延迟化后,大部分用户可以节省约 3 个工具定义的 token 开销。
## 8. 文件索引
### 核心文件
| 文件 | 职责 |
|------|------|
| `src/constants/tools.ts` | CORE_TOOLS 白名单、工具权限集合 |
| `src/utils/searchExtraTools.ts` | 模式判定、阈值计算、delta 差分、discovered tools 提取 |
| `src/services/searchExtraTools/toolIndex.ts` | TF-IDF 索引构建和搜索 |
| `src/services/searchExtraTools/prefetch.ts` | 预取管道turn-zero + inter-turn |
| `packages/builtin-tools/src/tools/SearchExtraToolsTool/` | 搜索工具实现4 种查询模式) |
| `packages/builtin-tools/src/tools/ExecuteTool/` | 代理执行器实现 |
| `src/services/api/claude.ts` | API 层集成(工具过滤、消息归一化) |
| `src/query.ts` | 查询循环集成(预取触发点) |
| `src/utils/messages.ts` | Attachment → system-reminder 转换 |
### 共享基础设施
| 文件 | 被复用的导出 |
|------|-------------|
| `src/services/skillSearch/localSearch.ts` | `tokenizeAndStem`, `computeWeightedTf`, `computeIdf`, `cosineSimilarity` |
| `src/services/skillSearch/prefetch.ts` | `extractQueryFromMessages` |
### 测试文件
| 文件 | 覆盖范围 |
|------|---------|
| `src/services/searchExtraTools/__tests__/toolIndex.test.ts` | 索引构建、TF-IDF 搜索、CJK 处理 |
| `src/services/searchExtraTools/__tests__/prefetch.test.ts` | 预取管道、去重、attachment 生成 |
| `packages/builtin-tools/src/tools/SearchExtraToolsTool/__tests__/` | 搜索工具 4 种模式 |
| `packages/builtin-tools/src/tools/ExecuteTool/__tests__/` | 代理执行 |
## 9. 维护指南
### 9.1 新增工具的延迟化决策
将新工具加入 deferred 状态的标准:
- 工具仅在特定场景使用(如 swarm 模式、特定 MCP 集成)
- 工具的 schema 较大(占用较多 context token
- 工具不是模型默认会尝试的核心操作
将已延迟的工具提升为 core tool
-`src/constants/tools.ts``CORE_TOOLS` Set 中添加工具名常量
- 确保导入对应的 `*_TOOL_NAME` 常量
### 9.2 修改注意事项
1. **修改 `localSearch.ts` 的 TF-IDF 函数**:需同步检查 `toolIndex.test.ts``localSearch.test.ts`
2. **修改 `skillSearch/prefetch.ts` 的 `extractQueryFromMessages`**:需同步检查工具预取行为(`searchExtraTools/prefetch.ts` 调用同一函数)
3. **修改 CORE_TOOLS**:需更新 `src/constants/__tests__/tools.test.ts` 测试
4. **修改 `isDeferredTool`**:需更新 `src/constants/__tests__/tools.test.ts``SearchExtraToolsTool.test.ts`
### 9.3 性能优化配置
```bash
# 环境变量调优
ENABLE_SEARCH_EXTRA_TOOLS=auto:15 # 当 deferred tools 超过上下文 15% 时启用
SEARCH_EXTRA_TOOLS_WEIGHT_KEYWORD=0.5 # 关键词搜索权重
SEARCH_EXTRA_TOOLS_WEIGHT_TFIDF=0.5 # TF-IDF 搜索权重
SEARCH_EXTRA_TOOLS_DISPLAY_MIN_SCORE=0.10 # 最低显示分数阈值
```
### 9.4 搜索质量调优
- `TOOL_FIELD_WEIGHT``toolIndex.ts`):控制 name/searchHint/description 对 TF-IDF 分数的贡献权重
- `KEYWORD_WEIGHT` / `TFIDF_WEIGHT``SearchExtraToolsTool.ts`):控制混合搜索中两种算法的最终权重比例
- `searchHint` 属性:为工具添加精心编写的搜索提示,提高关键词匹配质量
## 10. 与 Skill Search 的关系
ToolSearch 和 SkillSearch 是平行的搜索系统,共享底层算法但服务于不同领域:
| 维度 | ToolSearch | SkillSearch |
|------|-----------|-------------|
| 搜索对象 | Deferred 工具(内置 + MCP | 用户技能skill |
| 执行方式 | `ExecuteExtraTool` 代理调用 | 直接注入 attachment 内容 |
| 字段权重 | name:3.0, searchHint:2.5, desc:1.0 | name:3.0, whenToUse:2.0, desc:1.0 |
| 缓存策略 | 按工具名列表缓存 | 按 cwd 缓存 |
| 去重集合 | `discoveredToolsThisSession` | 独立的 Set |
共享的底层函数:
- `tokenizeAndStem` — 统一的 CJK/ASCII 分词和词干提取
- `computeWeightedTf` — 加权词频计算
- `computeIdf` — 逆文档频率计算
- `cosineSimilarity` — 向量余弦相似度
- `extractQueryFromMessages` — 从对话历史中提取搜索查询文本