Claude Code 工具延迟加载系统:完整设计与源码分析
基于 Claude Code npm 包 source map 泄露快照 (2026-03-31) 的源码分析。
适用于团队内部分享,理解 AI Agent 工具管理的工程范式。
一、设计哲学
源码注释(src/Tool.ts:438-442)定义了工具延迟加载的核心概念:
When true, this tool is deferred (sent with defer_loading: true) and requires ToolSearch to be used before it can be called.
核心原则:不要把工具注册表当静态配置——把它变成一个可搜索的服务,让模型成为自己的工具发现者。「不发送」比「发送后忽略」更高效:defer_loading 让工具 schema 完全不进入 prompt,而不是靠模型自觉不用。
二、问题:工具膨胀的乘法代价
Claude Code 是一个 agent 系统。每次调用 Claude API,必须把所有可用工具的完整 JSON Schema 发给模型。
Claude Code 面临的现实数据:
| 指标 | 数量 |
|---|---|
| 内置工具 | 44+ |
| MCP 工具(用户可扩展) | 不限,实际可达 100+ |
| 每个工具 schema 大小 | 数百到数千 token |
| 每次对话轮数 | 10-50+ |
代价是乘法效应:44 个工具的 schema 约占上下文窗口 5%-8%。用户安装 MCP 服务器后,工具数可轻松翻 3-5 倍,schema 占比膨胀到 20-30%,挤压真正的对话和代码内容。
更隐蔽的问题是 prompt cache 稳定性:Claude API 对 prompt 的前缀做 cache。工具列表的任何变动(MCP 服务器连接/断开、工具 schema 更新)都会让整个 cache 失效,导致下一轮调用重新计算全部 token——在长对话中这是巨大的成本。
三、解法架构总览
Claude Code 没有选择简单的「减少工具」或「手动分组」,而是设计了一套三层架构,核心思想是:让模型成为自己的工具发现者。
四、Layer 1: 分类决策 —— 哪些工具该延迟
4.1 核心判定函数
文件:src/tools/ToolSearchTool/prompt.ts
export function isDeferredTool(tool: Tool): boolean {
// ① 显式 opt-out:alwaysLoad = true 永远不延迟
// MCP 工具可通过 _meta['anthropic/alwaysLoad'] 设置
if (tool.alwaysLoad === true) return false
// ② MCP 工具一律延迟(用户/工作流特定,无法全局缓存)
if (tool.isMcp === true) return true
// ③ ToolSearch 自身不能延迟——鸡蛋问题
if (tool.name === TOOL_SEARCH_TOOL_NAME) return false
// ④ Agent 工具在 fork 模式下不延迟(第一轮就需要生成子 agent)
if (feature('FORK_SUBAGENT') && tool.name === AGENT_TOOL_NAME) {
if (m.isForkSubagentEnabled()) return false
}
// ⑤ Brief 是主要通信通道(KAIROS 模式),不能有额外延迟
if (BRIEF_TOOL_NAME && tool.name === BRIEF_TOOL_NAME) return false
// ⑥ SendUserFile 是文件传输通道,同理
if (SEND_USER_FILE_TOOL_NAME && tool.name === SEND_USER_FILE_TOOL_NAME
&& isReplBridgeActive()) return false
// ⑦ 最终:看工具自身的 shouldDefer 声明
return tool.shouldDefer === true
}
设计原则:
- alwaysLoad 优先级最高 —— 任何工具(包括 MCP 工具)都可以通过此标记强制始终加载
- MCP 工具默认延迟 —— MCP 工具是用户安装的外部扩展,Claude Code 控制不了数量
- 关键通道工具不延迟 —— ToolSearch、Agent、Brief 等在第一轮就可能被需要
- 内置工具按频率分类 —— 高频工具不延迟,低频工具设
shouldDefer: true
4.2 内置工具的分类
通过搜索源码中所有 shouldDefer: true 的声明:
始终加载(高频核心工具):
| 工具 | 职责 |
|---|---|
BashTool | Shell 命令执行 |
FileReadTool | 文件读取 |
FileEditTool | 文件编辑 |
FileWriteTool | 文件创建/覆盖 |
GlobTool | 文件模式匹配 |
GrepTool | 内容搜索 |
AgentTool | 子 agent 生成 |
SkillTool | 技能执行 |
ToolSearchTool | 工具发现(自身) |
延迟加载(shouldDefer: true):
| 工具 | searchHint |
|---|---|
WebFetchTool | (无,但 description 可搜) |
WebSearchTool | search the web for current information |
NotebookEditTool | edit Jupyter notebook cells (.ipynb) |
LSPTool | (无) |
AskUserQuestionTool | prompt the user with a multiple-choice question |
TaskCreateTool | (无) |
TaskGetTool | (无) |
TaskUpdateTool | (无) |
TaskListTool | (无) |
TaskStopTool | (无) |
TaskOutputTool | read output/logs from a background task |
SendMessageTool | (无) |
TeamCreateTool | create a multi-agent swarm team |
TeamDeleteTool | disband a swarm team and clean up |
EnterPlanModeTool | (无) |
ExitPlanModeV2Tool | (无) |
EnterWorktreeTool | (无) |
ExitWorktreeTool | (无) |
TodoWriteTool | (无) |
ConfigTool | (无) |
ListMcpResourcesTool | list resources from connected MCP servers |
ReadMcpResourceTool | read a specific MCP resource by URI |
CronCreateTool | schedule a recurring or one-shot prompt |
CronDeleteTool | cancel a scheduled cron job |
CronListTool | list active cron jobs |
RemoteTriggerTool | manage scheduled remote agent triggers |
关键数据:大多数对话只需要始终加载的 7-9 个核心工具。延迟加载的 25+ 个内置工具加上所有 MCP 工具,只在需要时按需加载。
五、Layer 2: API 协议 —— defer_loading 和 tool_reference
5.1 工具 schema 构建
文件:src/utils/api.ts
每个工具的 schema 通过 toolToAPISchema() 构建。该函数有一个会话级缓存,工具名+schema 作为 key,避免重复序列化:
// 扩展的 BetaTool 类型
type BetaToolWithExtras = BetaTool & {
strict?: boolean
defer_loading?: boolean // ← 延迟加载标记
cache_control?: { type: 'ephemeral'; scope?: 'global' | 'org' }
eager_input_streaming?: boolean
}
export async function toolToAPISchema(tool, options): Promise<BetaToolUnion> {
// ① 会话级 cache:name + description + input_schema + strict + eager_input_streaming
const cache = getToolSchemaCache()
let base = cache.get(cacheKey)
if (!base) {
base = {
name: tool.name,
description: await tool.prompt({ ... }),
input_schema: zodToJsonSchema(tool.inputSchema),
}
cache.set(cacheKey, base)
}
// ② 每次请求的 overlay(不修改缓存的 base)
const schema: BetaToolWithExtras = {
name: base.name,
description: base.description,
input_schema: base.input_schema,
...(base.strict && { strict: true }),
}
// ③ 按需添加 defer_loading
if (options.deferLoading) {
schema.defer_loading = true
}
return schema
}
为什么 defer_loading 不放在 cache 里:同一个工具在不同轮次可能需要/不需要延迟(比如被发现后就不再延迟)。将 defer_loading 作为 per-request overlay,避免 cache 被 defer 状态污染。
5.2 API 端行为
当 API 收到一个带 defer_loading: true 的工具 schema:
- 从实际 prompt 中剥离 —— 模型看不到完整 schema,不消耗 context token
- 工具名仍然可见 —— 模型知道这个工具存在(通过
<system-reminder>或<available-deferred-tools>消息) - 无法直接调用 —— 模型必须先通过 ToolSearch 获取完整 schema
5.3 tool_reference 内容块
当 ToolSearchTool 找到匹配工具时,返回特殊的 tool_reference 块:
// ToolSearchTool.mapToolResultToToolResultBlockParam()
if (content.matches.length === 0) {
return {
type: 'tool_result',
tool_use_id: toolUseID,
content: 'No matching deferred tools found',
}
}
return {
type: 'tool_result',
tool_use_id: toolUseID,
content: content.matches.map(name => ({
type: 'tool_reference', // ← 特殊内容块
tool_name: name,
})),
}
API 端行为:收到 tool_reference 后,API 会在当前轮次的模型可见上下文中动态注入对应工具的完整 schema。此后模型就可以正常使用 tool_use 调用该工具了。
5.4 Beta Header
使用 defer_loading 和 tool_reference 需要在 API 请求中附带 beta header:
// claude.ts:1174-1182
const toolSearchHeader = useToolSearch ? getToolSearchBetaHeader() : null
if (toolSearchHeader && getAPIProvider() !== 'bedrock') {
if (!betas.includes(toolSearchHeader)) {
betas.push(toolSearchHeader)
}
}
// 不同 provider 使用不同 header:
// 1P/Foundry: 'advanced-tool-use'
// Vertex/Bedrock: 'tool-search-tool'
六、Layer 3: 运行时发现 —— ToolSearchTool
6.1 工具列表通知
模型需要知道有哪些延迟工具可用。有两种机制(正在从 A 迁移到 B):
机制 A:<available-deferred-tools> 消息注入
// claude.ts:1330-1345
if (useToolSearch && !isDeferredToolsDeltaEnabled()) {
const deferredToolList = tools
.filter(t => deferredToolNames.has(t.name))
.map(formatDeferredToolLine) // 只返回工具名
.sort()
.join('\n')
if (deferredToolList) {
messagesForAPI = [
createUserMessage({
content: `<available-deferred-tools>\n${deferredToolList}\n</available-deferred-tools>`,
isMeta: true,
}),
...messagesForAPI,
]
}
}
这种方式在每次 API 调用前注入一条包含所有延迟工具名的合成消息。缺点是每次都发送完整列表,且变化时会破坏 cache。
机制 B:deferred_tools_delta 增量通知(正在推广)
// attachments.ts:836-841
maybe('deferred_tools_delta', () =>
Promise.resolve(
getDeferredToolsDeltaAttachment(
toolUseContext.options.tools,
toolUseContext.options.mainLoopModel,
messages,
scanContext,
),
),
),
增量机制只通知变化(新增/移除的工具),作为持久化的 attachment 消息存在于对话历史中。渲染为 <system-reminder>:
// messages.ts:4178-4182
case 'deferred_tools_delta': {
const parts: string[] = []
if (attachment.addedLines.length > 0) {
parts.push(
`The following deferred tools are now available via ToolSearch:\n${attachment.addedLines.join('\n')}`,
)
}
// ...
}
6.2 ToolSearchTool 的搜索实现
文件:src/tools/ToolSearchTool/ToolSearchTool.ts
两种查询模式:
模式一:精确选择(select: 前缀)
const selectMatch = query.match(/^select:(.+)$/i)
if (selectMatch) {
const requested = selectMatch[1]!
.split(',') // 支持逗号分隔多选
.map(s => s.trim())
.filter(Boolean)
const found: string[] = []
for (const toolName of requested) {
// 先在延迟工具中找,再在全量工具中找
const tool =
findToolByName(deferredTools, toolName) ??
findToolByName(tools, toolName) // 已加载工具也能命中(幂等)
if (tool && !found.includes(tool.name)) found.push(tool.name)
}
return buildSearchResult(found, query, deferredTools.length)
}
使用场景:模型已经知道工具名(从 <system-reminder> 中看到),直接选择。
模式二:关键词搜索
async function searchToolsWithKeywords(query, deferredTools, tools, maxResults) {
const queryLower = query.toLowerCase().trim()
// 快速路径:精确名称匹配
const exactMatch =
deferredTools.find(t => t.name.toLowerCase() === queryLower) ??
tools.find(t => t.name.toLowerCase() === queryLower)
if (exactMatch) return [exactMatch.name]
// MCP 前缀匹配:mcp__slack → 所有 mcp__slack__* 工具
if (queryLower.startsWith('mcp__') && queryLower.length > 5) {
const prefixMatches = deferredTools
.filter(t => t.name.toLowerCase().startsWith(queryLower))
.slice(0, maxResults)
.map(t => t.name)
if (prefixMatches.length > 0) return prefixMatches
}
// 解析查询词(支持 + 前缀表示必选项)
const queryTerms = queryLower.split(/\s+/).filter(term => term.length > 0)
const requiredTerms: string[] = [] // +前缀的词
const optionalTerms: string[] = [] // 普通词
// 评分搜索
const scored = await Promise.all(
candidateTools.map(async tool => {
const parsed = parseToolName(tool.name) // 拆解 CamelCase 和 mcp__x__y
const description = await getToolDescriptionMemoized(tool.name, tools)
let score = 0
for (const term of allScoringTerms) {
// 工具名精确段匹配 — MCP +12 / 普通 +10
if (parsed.parts.includes(term))
score += parsed.isMcp ? 12 : 10
// 工具名部分匹配 — MCP +6 / 普通 +5
else if (parsed.parts.some(part => part.includes(term)))
score += parsed.isMcp ? 6 : 5
// searchHint 匹配 — +4(人工策划的能力短语,信号强于 description)
if (hintNormalized && pattern.test(hintNormalized))
score += 4
// description 匹配 — +2(使用词边界 regex 避免误报)
if (pattern.test(descNormalized))
score += 2
}
return { name: tool.name, score }
}),
)
return scored
.filter(item => item.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, maxResults)
.map(item => item.name)
}
评分体系设计思路:
| 匹配类型 | 分值 | 举例 |
|---|---|---|
| 工具名精确段匹配(MCP) | 12 | 查 slack → mcp__slack__send_message |
| 工具名精确段匹配(普通) | 10 | 查 notebook → NotebookEditTool |
| 工具名部分段匹配(MCP) | 6 | 查 slac → mcp__slack__send_message |
| 工具名部分段匹配(普通) | 5 | 查 note → NotebookEditTool |
| searchHint 词边界匹配 | 4 | 查 jupyter → searchHint 含 Jupyter |
| description 词边界匹配 | 2 | 查 cron → description 含 cron |
为什么 MCP 工具分值更高:MCP 工具命名为 mcp__server__action,其 server 段是最关键的区分信息。用户查 slack 时,期望优先命中 Slack 相关的 MCP 工具,而不是 description 中碰巧提到 Slack 的内置工具。
6.3 工具名解析
function parseToolName(name: string) {
// MCP 工具:mcp__slack__send_message → ["slack", "send", "message"]
if (name.startsWith('mcp__')) {
const parts = name.replace(/^mcp__/, '').split('__').flatMap(p => p.split('_'))
return { parts, full: '...', isMcp: true }
}
// 普通工具:NotebookEditTool → ["notebook", "edit", "tool"]
const parts = name
.replace(/([a-z])([A-Z])/g, '$1 $2') // CamelCase 拆分
.replace(/_/g, ' ')
.toLowerCase()
.split(/\s+/)
return { parts, full: '...', isMcp: false }
}
6.4 性能优化
- description 记忆化:
getToolDescriptionMemoized按工具名缓存,避免每次搜索重复调用tool.prompt() - cache 失效检测:
maybeInvalidateCache检查延迟工具集合是否变化(MCP 服务器连接/断开) - 预编译正则:
compileTermPatterns对所有搜索词一次性编译词边界 regex,避免 tools×terms 次重复编译 - 并发评分:
Promise.all并行计算所有候选工具的分数
七、完整调用链 —— 从用户输入到 defer_loading
7.1 时序图
7.2 模型视角的交互流程
7.3 query.ts 中 attachment 注入时机
deferred_tools_delta 作为 attachment 在查询循环中注入:
// query.ts:1580-1590
// 在每轮工具执行结束后、准备下一轮 API 调用前
for await (const attachment of getAttachmentMessages(
null,
updatedToolUseContext,
null,
queuedCommandsSnapshot,
[...messagesForQuery, ...assistantMessages, ...toolResults],
querySource,
)) {
yield attachment
toolResults.push(attachment) // 注入到消息流中
}
getAttachmentMessages 内部会调用 getDeferredToolsDeltaAttachment,计算与上一次通知的 diff。
八、与上下文压缩的协同 —— 防止工具「失忆」
8.1 问题
Claude Code 的长对话使用多种压缩策略(auto-compact、reactive-compact、snip)来管理上下文。压缩会删除/合并历史消息。但 tool_reference 块就藏在那些消息里——如果被删了,之前发现的工具在后续轮次就不会被包含在 filteredTools 中。
8.2 解法:compact boundary 快照
// toolSearch.ts:545-592
export function extractDiscoveredToolNames(messages: Message[]): Set<string> {
const discoveredTools = new Set<string>()
for (const msg of messages) {
// ① compact 边界消息携带压缩前的发现集合
if (msg.type === 'system' && msg.subtype === 'compact_boundary') {
const carried = msg.compactMetadata?.preCompactDiscoveredTools
if (carried) {
for (const name of carried) discoveredTools.add(name)
}
continue
}
// ② 扫描用户消息中的 tool_result → tool_reference
if (msg.type !== 'user') continue
const content = msg.message?.content
if (!Array.isArray(content)) continue
for (const block of content) {
if (isToolResultBlockWithContent(block)) {
for (const item of block.content) {
if (isToolReferenceWithName(item)) {
discoveredTools.add(item.tool_name)
}
}
}
}
}
return discoveredTools
}
流程:
- 压缩发生前,当前已发现的工具集合被快照到
compactMetadata.preCompactDiscoveredTools - 压缩后,旧的 tool_reference 消息被删除,但 compact boundary 消息保留了快照
extractDiscoveredToolNames同时从 boundary 快照和残存的 tool_reference 中重建发现集合
8.3 cache 稳定性保护
// claude.ts:1461-1467
// defer_loading 工具不参与 prompt cache 检测的 hash 计算
const toolsForCacheDetection = allTools.filter(
t => !('defer_loading' in t && t.defer_loading),
)
原理:defer_loading: true 的工具 schema 被 API 从实际 prompt 中剥离,不影响 cache key。如果把它们算进 hash,每次 MCP 服务器连接/断开都会误触发 cache 失效检测。
同样,assembleToolPool() 中的工具排序也考虑了 cache 稳定性:
// tools.ts: assembleToolPool()
// 内置工具排序后作为连续前缀
// MCP 工具排序后追加
// uniqBy 保证内置工具在名称冲突时优先
const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name)
return uniqBy(
[...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
'name',
)
为什么不 flat sort:API 的 cache breakpoint 设在最后一个内置工具之后。如果 flat sort 让 MCP 工具插入到内置工具之间,内置工具的 cache 前缀就被破坏了。
九、模式选择与降级
9.1 三种运行模式
文件:src/utils/toolSearch.ts
ENABLE_TOOL_SEARCH 值 | 模式 | 行为 |
|---|---|---|
| 未设置(默认) | tst | 始终延迟 MCP + shouldDefer 工具 |
true / auto:0 | tst | 同上 |
auto / auto:N(N=1-99) | tst-auto | 延迟工具 token 超过上下文窗口 N% 才启用 |
false / auto:100 | standard | 不延迟,全量发送 |
9.2 自动模式的阈值检查
async function checkAutoThreshold(tools, getToolPermissionContext, agents, model) {
// ① 优先用精确 token 计数(通过 API 计算,按工具集合缓存)
const deferredToolTokens = await getDeferredToolTokenCount(
tools, getToolPermissionContext, agents, model
)
if (deferredToolTokens !== null) {
const threshold = contextWindow * (percentage / 100) // 默认 10%
return { enabled: deferredToolTokens >= threshold }
}
// ② 降级:字符数估算(1 token ≈ 2.5 字符)
const chars = await calculateDeferredToolDescriptionChars(tools, ...)
const charThreshold = threshold * 2.5
return { enabled: chars >= charThreshold }
}
9.3 多级安全降级链
9.4 isToolSearchEnabled 的完整决策链
export async function isToolSearchEnabled(model, tools, getToolPermissionContext, agents) {
// 模型不支持 → false
if (!modelSupportsToolReference(model)) return false
// ToolSearch 被禁用 → false
if (!isToolSearchToolAvailable(tools)) return false
const mode = getToolSearchMode()
switch (mode) {
case 'tst': return true
case 'standard': return false
case 'tst-auto':
const { enabled } = await checkAutoThreshold(tools, ...)
return enabled
}
}
关键点:这个函数每次 API 调用都重新执行(在 queryModel() 内部),而不是一次性决策。这意味着:
- subagent 切换到 Haiku 时自动关闭
- MCP 服务器中途连接后,
tst-auto模式可能从关 → 开 - deny rule 动态变化也即时生效
十、deferred_tools_delta 增量通知
10.1 动机
MCP 服务器可能在对话过程中动态连接/断开。<available-deferred-tools> 每次发完整列表,变化时破坏 cache。增量通知只传 diff。
10.2 实现
文件:src/utils/toolSearch.ts
export function getDeferredToolsDelta(tools, messages, scanContext) {
// ① 从历史 attachment 消息重建已通知集合
const announced = new Set<string>()
for (const msg of messages) {
if (msg.type !== 'attachment') continue
if (msg.attachment.type !== 'deferred_tools_delta') continue
for (const n of msg.attachment.addedNames) announced.add(n)
for (const n of msg.attachment.removedNames) announced.delete(n)
}
// ② 计算当前延迟工具集合
const deferred = tools.filter(isDeferredTool)
const deferredNames = new Set(deferred.map(t => t.name))
const poolNames = new Set(tools.map(t => t.name))
// ③ 计算 diff
const added = deferred.filter(t => !announced.has(t.name))
const removed: string[] = []
for (const n of announced) {
if (deferredNames.has(n)) continue
// 如果工具仍在 pool 中但不再 deferred → 变成了直接加载 → 不报 removed
if (!poolNames.has(n)) removed.push(n)
}
if (added.length === 0 && removed.length === 0) return null
return { addedNames, addedLines, removedNames }
}
边界情况:如果一个工具从 deferred 变成了 always-load(比如 feature flag 改变),它仍然可用,只是加载方式变了。此时不应该通知模型「工具被移除了」,所以只有「从 pool 中完全消失」的工具才报 removed。
10.3 消息渲染
// messages.ts
case 'deferred_tools_delta': {
const parts: string[] = []
if (attachment.addedLines.length > 0) {
parts.push(
`The following deferred tools are now available via ToolSearch:\n${attachment.addedLines.join('\n')}`,
)
}
if (attachment.removedNames.length > 0) {
parts.push(
`The following deferred tools are no longer available:\n${attachment.removedNames.join('\n')}`,
)
}
// 渲染为 <system-reminder> 消息
}
十一、完整数据流图
十二、关键源码文件清单
src/tools/ToolSearchTool/
├── ToolSearchTool.ts # 搜索实现(评分算法、精确选择、关键词搜索)
├── prompt.ts # isDeferredTool() 分类逻辑、工具列表格式化
└── constants.ts # TOOL_SEARCH_TOOL_NAME 常量
src/utils/
├── toolSearch.ts # 模式选择(tst/tst-auto/standard)、阈值检查、
│ # extractDiscoveredToolNames()、getDeferredToolsDelta()
├── api.ts # toolToAPISchema()(schema 构建 + defer_loading overlay)
├── attachments.ts # getDeferredToolsDeltaAttachment()(增量通知)
└── messages.ts # deferred_tools_delta attachment 渲染
src/services/api/
└── claude.ts # queryModel()(useToolSearch 决策 + 工具过滤 + schema 构建)
src/
├── Tool.ts # Tool 接口(shouldDefer、alwaysLoad、searchHint 定义)
├── tools.ts # getAllBaseTools()、assembleToolPool()(工具注册 + 排序)
├── query.ts # 主循环中 attachment 注入时机
└── query/deps.ts # 依赖注入(callModel 绑定到 queryModelWithStreaming)
十三、可借鉴的设计模式
| 设计模式 | 实现方式 | 解决的问题 | 适用场景 |
|---|---|---|---|
| 工具二分法 | shouldDefer + isDeferredTool() | 高频工具零延迟,低频工具不浪费 token | 任何工具数 > 15 的 Agent |
| API 级声明 | defer_loading: true | 工具存在但不占用 context | 需要 API 支持 |
| 模型自主发现 | ToolSearchTool + tool_reference | 零人工干预的按需加载 | 插件/MCP 生态 |
| 评分排序搜索 | name/hint/desc 多层权重 | 模糊查询也能找到正确工具 | 大量外部工具 |
| 增量通知 | deferred_tools_delta | 动态工具池变化不破坏 cache | MCP 服务器动态连接 |
| compact 快照 | preCompactDiscoveredTools | 压缩不丢失已发现状态 | 长对话 Agent |
| 排序保证 cache | 内置前缀 + MCP 后缀排序 | 工具变化不破坏内置工具 cache | 混合工具源 |
| 多级降级 | 模型/代理/kill switch/deny rule | 生产环境兼容性 | 用户面向的 Agent |
| per-request overlay | defer_loading 不入 schema cache | 同一工具不同轮次可切换状态 | 动态工具状态 |
| 每轮重新决策 | isToolSearchEnabled 在 queryModel 内调用 | 适应运行时变化(模型切换、MCP 连接) | 多模型/多 agent |
十四、核心洞察
不要把工具注册表当静态配置 —— 把它变成可搜索的服务,让模型成为自己的工具发现者
「不发送」比「发送后忽略」更高效 ——
defer_loading让工具 schema 完全不进入 prompt,而不是靠模型自觉不用cache 稳定性是隐藏的性能瓶颈 —— 工具列表变化导致的 cache 失效,在长对话中的 token 浪费远超工具 schema 本身的开销
增量优于全量 —— 从
<available-deferred-tools>(全量列表)到deferred_tools_delta(增量通知)的演进,体现了对 cache 稳定性的持续优化安全降级是生产必需品 —— 五层降级确保不管什么环境(Haiku 模型、第三方代理、用户禁用)都能正常工作,只是失去优化
文件来源:Claude Code npm 包 source map 泄露快照 (2026-03-31)
分析日期:2026-04-07