目录
- 1. 概述
- 2. 工具定义体系
- 3. 工具集合的组装
- 4. 工具 Schema 转换与 API 请求
- 5. 模型如何使用工具的引导机制
- 6. 工具调用的执行流程
- 7. 工具调用失败的兜底逻辑
- 8. 完整链路图示
1. 概述
Claude Code 的工具调用系统是一个完整链路:定义工具 → 注册工具 → Schema 序列化 → 发给模型 → 模型决定调用 → 解析执行 → 结果返回。
整个过程的核心思路是:
- 所有工具(内置 + MCP)在每次 API 请求时通过
tools参数发给模型 - System Prompt 中包含工具使用的指引(何时用哪个、怎么用)
tool_choice: undefined让模型自主决定是否调用工具、调哪个工具- 模型输出
tool_useblock 后,客户端执行并返回tool_result - 没有自动重试——失败信息返回给模型,由模型自行决定下一步
2. 工具定义体系
2.1 Tool 接口定义
文件:src/Tool.ts:366-695
每个工具实现 Tool 接口,核心字段和方法:
type Tool<Input, Output, P> = {
name: string // 工具名称,唯一标识
aliases?: string[] // 向后兼容的别名
searchHint?: string // 工具搜索关键字提示
// 核心方法
call(args, context, canUseTool, ...): Promise<ToolResult<Output>>
prompt(options): Promise<string> // 返回工具的完整使用说明(作为 API description)
description(input, options): Promise<string> // 一句话描述
// Schema
readonly inputSchema: Input // Zod schema,定义参数结构
readonly inputJSONSchema?: ToolInputJSONSchema // MCP 工具直接提供 JSON Schema
// 执行控制
isConcurrencySafe(input): boolean // 是否可并行执行
isReadOnly(input): boolean // 是否是只读操作
isEnabled(): boolean // 是否启用
// 安全与校验
validateInput?(input, context): Promise<ValidationResult>
checkPermissions(input, context): Promise<PermissionResult>
// 结果处理
maxResultSizeChars: number // 结果字符数上限
mapToolResultToToolResultBlockParam(content, toolUseID): ToolResultBlockParam
renderToolResultMessage?(...): React.ReactNode
renderToolUseMessage?(...): React.ReactNode
// 延迟加载
readonly shouldDefer?: boolean // 是否可延迟(ToolSearch 相关)
readonly alwaysLoad?: boolean // 是否始终加载
readonly strict?: boolean // 严格模式
}
2.2 buildTool 工厂函数
文件:src/Tool.ts:757-792
使用 buildTool() 创建工具实例,它会填充默认值:
const TOOL_DEFAULTS = {
isEnabled: () => true,
isConcurrencySafe: () => false, // 默认认为不安全(串行执行)
isReadOnly: () => false,
isDestructive: () => false,
checkPermissions: () => ({ behavior: 'allow', updatedInput: input }),
toAutoClassifierInput: () => '',
userFacingName: () => name,
}
2.3 工具的 prompt() 方法——使用说明
每个工具的 prompt() 方法返回详细的使用说明,这些说明会被放入 API 的 description 字段。模型通过阅读这些说明来了解工具的用法。
示例:BashTool 的 prompt(src/tools/BashTool/prompt.ts),包含:
- 工具的基本能力描述
- 何时应避免使用 Bash(如果有专用工具)
- 命令编写规范、超时限制
- Git 操作的安全协议
- Sandbox 配置和覆盖规则
让模型知道:"Bash 是通用执行工具,但优先用 Read/Edit/Write 等专用工具"。
BashTool 是cc中最常见的一个工具调用,可以通过 BashTool 源码分析 了解cc是怎么管理自己的工具的。
3. 工具集合的组装
3.1 所有内置工具的注册
文件:src/tools.ts:193-251
export function getAllBaseTools(): Tools {
return [
AgentTool,
TaskOutputTool,
BashTool,
...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
ExitPlanModeV2Tool,
FileReadTool,
FileEditTool,
FileWriteTool,
NotebookEditTool,
WebFetchTool,
TodoWriteTool,
WebSearchTool,
TaskStopTool,
AskUserQuestionTool,
SkillTool,
EnterPlanModeTool,
BriefTool,
ListMcpResourcesTool,
ReadMcpResourceTool,
// ... 条件性添加的工具(通过 feature flag / process.env 控制)
]
}
条件性添加的工具示例:
ConfigTool— 仅USER_TYPE === 'ant'EnterWorktreeTool / ExitWorktreeTool— worktree 模式启用时TaskCreateTool / TaskGetTool / TaskUpdateTool / TaskListTool— todo v2 启用时ToolSearchTool— ToolSearch 功能启用时
3.2 工具过滤与最终集合
文件:src/tools.ts:271-327 和 src/tools.ts:345-367
getTools(permissionContext)
→ 根据 mode(simple/normal)筛选
→ 根据 deny rules 过滤
→ 根据 isEnabled() 过滤
→ 返回最终的内置工具列表
assembleToolPool(permissionContext, mcpTools)
→ getTools() + MCP 工具
→ uniqBy name(内置优先)
→ 排序(按名字保证 prompt cache 稳定)
→ 返回完整的工具池
3.3 工具的禁用列表
文件:src/constants/tools.ts
不同类型的 agent 可用的工具集不同:
ALL_AGENT_DISALLOWED_TOOLS // 普通 sub-agent 不能用的工具
ASYNC_AGENT_ALLOWED_TOOLS // 异步 agent 能用的工具
COORDINATOR_MODE_ALLOWED_TOOLS // 协调者模式能用的工具
4. 工具 Schema 转换与 API 请求
4.1 toolToAPISchema——转为 API 格式
文件:src/utils/api.ts:119-266
async function toolToAPISchema(tool, options): Promise<BetaToolUnion> {
// 1. 获取 JSON Schema(Zod → JSON Schema 或 直接使用 inputJSONSchema)
let input_schema = tool.inputJSONSchema || zodToJsonSchema(tool.inputSchema)
// 2. 过滤掉 swarm 相关字段(非 EAP 用户不可见)
if (!isAgentSwarmsEnabled()) {
input_schema = filterSwarmFieldsFromSchema(tool.name, input_schema)
}
// 3. 构建基础 schema
const schema = {
name: tool.name,
description: await tool.prompt({...}), // ← 工具使用说明就在这里
input_schema,
strict: tool.strict ? true : undefined,
eager_input_streaming: true, // 细粒度 tool streaming
}
// 4. 可选:defer_loading(ToolSearch 功能)
if (options.deferLoading) {
schema.defer_loading = true // 模型调用前需要先发现此工具
}
// 5. 可选:cache_control(prompt caching)
if (options.cacheControl) {
schema.cache_control = options.cacheControl
}
return schema
}
关键的 Schema 转换路径:
Zod Schema(z.object({...}))
→ zodToJsonSchema()
→ JSON Schema({ type: "object", properties: {...} })
→ Anthropic API 的 BetaToolUnion
4.2 API 请求的构建
文件:src/services/api/claude.ts:1100-1396
工具过滤阶段(ToolSearch):
// 判断是否启用 ToolSearch
let useToolSearch = await isToolSearchEnabled(...)
// 若启用,只发送非延迟工具 + 已发现的延迟工具
if (useToolSearch) {
const discoveredToolNames = extractDiscoveredToolNames(messages)
filteredTools = tools.filter(tool => {
if (!deferredToolNames.has(tool.name)) return true
if (toolMatchesName(tool, TOOL_SEARCH_TOOL_NAME)) return true
return discoveredToolNames.has(tool.name)
})
} else {
// 不启用 ToolSearch,直接过滤掉 ToolSearchTool
filteredTools = tools.filter(t => !toolMatchesName(t, TOOL_SEARCH_TOOL_NAME))
}
构建所有工具 Schema:
const toolSchemas = await Promise.all(
filteredTools.map(tool =>
toolToAPISchema(tool, {
getToolPermissionContext: ...,
tools,
agents: ...,
model: options.model,
deferLoading: willDefer(tool), // 是否延迟加载
}),
),
)
const allTools = [...toolSchemas, ...extraToolSchemas]
最终 API 请求参数(claude.ts:1699-1728):
return {
model: normalizeModelStringForAPI(options.model),
messages: addCacheBreakpoints(messagesForAPI, ...),
system, // System Prompt
tools: allTools, // ← 所有工具的定义数组
tool_choice: options.toolChoice, // ← undefined → auto(模型自主决定)
betas: betasParams,
max_tokens: maxOutputTokens,
thinking,
temperature,
}
4.3 传给模型的参数说明
模型看到的工具信息由三部分组成:
| 参数 | 来源 | 作用 |
|---|---|---|
tools[].name | tool.name | 工具的唯一名称,模型通过此名称调用 |
tools[].description | tool.prompt() 的输出 | 工具的使用说明,包含详细的用法、注意事项、示例 |
tools[].input_schema | tool.inputSchema 转换的 JSON Schema | 定义参数结构、类型、必填项 |
tools[].defer_loading | ToolSearch 功能 | 标记为 true 时,模型先要通过 ToolSearch 发现才能调用 |
tool_choice | undefined | 让模型自主决定是否调用工具(auto 模式) |
| system prompt 中"Using your tools"部分 | prompts.ts | 引导模型何时使用什么工具 |
5. 模型如何使用工具的引导机制
5.1 System Prompt 中的工具使用指引
文件:src/constants/prompts.ts:269-314
getUsingYourToolsSection() 生成的内容告诉模型:
# Using your tools
- 当有专用工具时不要用 Bash
- 读文件用 FileReadTool 而不是 cat/head/tail
- 编辑文件用 FileEditTool 而不是 sed/awk
- 创建文件用 FileWriteTool
- 搜索文件用 GlobTool 而不是 find/ls
- 内容搜索用 GrepTool 而不是 grep/rg
- 可以用 TaskCreate 管理任务
- 可以并行调用无依赖的工具
5.2 工具被拒绝后的行为指引
- 如果用户拒绝工具调用,不要重试完全相同的调用
- 思考为什么被拒绝并调整方法
- 可用 AskUserQuestionTool 询问用户原因
5.3 每个工具的独立指引
每个工具的 prompt() 方法提供针对性的使用说明。例如 BashTool 的说明中包含:
- 执行的命令优先使用专用工具
- Git 操作的安全规范
- 如何管理超时
- Sandbox 的限制和绕过方式
模型综合阅读 system prompt 中的通用指引 + 每个工具的私有关键规则字段,来判断什么场景用什么工具。
5.4 ToolSearch:延迟加载机制
文件:src/services/api/claude.ts:1120-1172 和 src/tools/ToolSearchTool/prompt.ts
当工具数量很多时,部分工具设定 shouldDefer: true,其 Schema 不会随每次请求发送。模型需要通过 ToolSearch 工具来发现这些延迟工具:
- 初始请求中只包含非延迟工具 + ToolSearchTool
- 模型调用
ToolSearch(query: "select:toolName")发现某个延迟工具 - 接下来的请求会包含该工具的完整 Schema
- 模型此时才能调用该工具
6. 工具调用的执行流程
6.1 主循环
文件:src/query.ts
用户消息 → 构建 API 请求(含 tools 参数)
→ 流式接收响应
→ 收集 tool_use blocks(query.ts:829-835)
→ 触发工具执行(query.ts:1382)
→ 工具结果作为 tool_result 返回
→ 继续下一轮循环(或结束)
关键代码(query.ts:829-835):
const msgToolUseBlocks = message.message.content.filter(
content => content.type === 'tool_use',
) as ToolUseBlock[]
if (msgToolUseBlocks.length > 0) {
toolUseBlocks.push(...msgToolUseBlocks)
needsFollowUp = true
}
6.2 工具编排
文件:src/services/tools/toolOrchestration.ts:19-82
async function* runTools(toolUseMessages, assistantMessages, canUseTool, toolUseContext) {
// 1. 按并发安全性分区
for (const { isConcurrencySafe, blocks } of partitionToolCalls(toolUseMessages)) {
if (isConcurrencySafe) {
// 读操作 → 并行执行(最多 MAX_TOOL_USE_CONCURRENCY=10 个)
yield* runToolsConcurrently(blocks, ...)
} else {
// 写操作 → 串行执行
yield* runToolsSerially(blocks, ...)
}
}
}
6.3 单个工具执行
文件:src/services/tools/toolExecution.ts:337-490
runToolUse(toolUse, assistantMessage, canUseTool, toolUseContext)
│
├─ 1. findToolByName() 查找工具实例
│ └─ 未找到 → 返回 "No such tool available" 错误
│
├─ 2. 检查是否已取消(abortController)
│ └─ 已取消 → 返回取消消息
│
└─ 3. checkPermissionsAndCallTool()
├─ a. parsedInput = tool.inputSchema.safeParse(input)
│ └─ 校验失败 → 返回 InputValidationError
│
├─ b. tool.validateInput(input, context)
│ └─ 校验失败 → 返回校验错误
│
├─ c. runPreToolUseHooks() 运行预执行钩子
│
├─ d. checkPermissions() → 权限检查
│ └─ 被拒绝 → 返回权限错误
│
├─ e. tool.call(input, context, ...) 真正执行工具
│ └─ 成功 → mapToolResultToToolResultBlockParam()
│
├─ f. runPostToolUseHooks() 运行后置钩子
│
└─ g. 返回 tool_result + 可能的额外消息
6.4 调用时机
模型调用的"时机"由以下三要素决定:
- API 参数
tool_choice: undefined:让模型自主决定何时调用工具 - 模型的训练和推理:模型根据用户请求和对话上下文,判断是否需要调用工具
- System Prompt 中的指引:告诉模型在特定场景下优先使用特定工具
从架构层面看,调用时机分为两个层面:
- 业务层面:模型决定调用工具(在 API 响应中输出
tool_useblock) - 执行层面:客户端收到
tool_useblock 后立即执行(query.ts 流式处理)
7. 工具调用失败的兜底逻辑
7.1 核心哲学:不自动重试
Claude Code 对工具调用失败 不做客户端自动重试。失败信息通过 is_error: true 的 tool_result 返回给模型,由模型自行决定如何处理。
7.2 各场景的失败处理
场景 1:模型调用了不存在的工具
文件:src/services/tools/toolExecution.ts:368-410
if (!tool) {
yield {
message: createUserMessage({
content: [{
type: 'tool_result',
content: `<tool_use_error>Error: No such tool available: ${toolName}</tool_use_error>`,
is_error: true,
tool_use_id: toolUse.id,
}],
}),
}
return // 返回给模型,模型决定下一步
}
场景 2:参数校验失败(Zod)
文件:src/services/tools/toolExecution.ts:615-679
const parsedInput = tool.inputSchema.safeParse(input)
if (!parsedInput.success) {
let errorContent = formatZodValidationError(tool.name, parsedInput.error)
// 如果是延迟工具且不在已发现集合中,附加提示让模型先用 ToolSearch 加载
const schemaHint = buildSchemaNotSentHint(tool, messages, tools)
if (schemaHint) errorContent += schemaHint
// 返回 InputValidationError
}
场景 3:自定义校验失败
文件:src/services/tools/toolExecution.ts:682-732
const isValidCall = await tool.validateInput?.(parsedInput.data, toolUseContext)
if (isValidCall?.result === false) {
// 返回工具自定义的校验错误信息
}
场景 4:执行时抛异常
文件:src/services/tools/toolExecution.ts:469-489
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
yield {
message: createUserMessage({
content: [{
type: 'tool_result',
content: `<tool_use_error>Error calling tool (${tool.name}): ${errorMessage}</tool_use_error>`,
is_error: true,
tool_use_id: toolUse.id,
}],
}),
}
}
场景 5:权限被拒绝
文件:src/services/tools/toolExecution.ts:995-1103
当 permissionDecision.behavior !== 'allow' 时,返回权限拒绝错误。特定情况(auto mode 下的 classifier 拒绝)会运行 PermissionDenied hooks,如果 hook 返回 retry: true,会在结果中附带提示让模型重试。
7.3 system prompt 中对工具失败的指引
文件:src/constants/prompts.ts
- If the user denies a tool call, do not re-attempt the exact same tool call.
Instead, think about why the user has denied the tool call and adjust your approach.
- If you do not understand why the user has denied a tool call, use AskUserQuestionTool to ask them.
7.4 关于重试的总结
| 失败类型 | 客户端自动重试 | 模型收到后行为 |
|---|---|---|
| 工具不存在 | ❌ | 模型自行决定(换工具或报告错误) |
| 参数校验失败 | ❌ | 模型自行决定(修正参数重试) |
| 执行异常 | ❌ | 模型自行决定(换方法或报告错误) |
| 权限被拒绝 | ❌ | 模型不应重试相同调用,需调整方法 |
| API 网络错误 | ✅(withRetry) | 在 claude.ts 中有 API 级别的重试机制,与工具调用无关 |
| 流错误(413等) | ✅(reactive compact) | 进行上下文压缩后重试,也是 API 级别而非工具级别 |
8. 完整链路图示
┌─────────────────────────────────────────────────────────────────┐
│ 1. 工具定义(Tool.ts) │
│ 每个工具实现 Tool 接口:name, prompt(), inputSchema, call() │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. 工具注册(tools.ts) │
│ getAllBaseTools() + MCP 工具 → assembleToolPool() │
│ 按 deny rules / isEnabled() / mode 过滤 │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. Schema 转换(api.ts) │
│ Zod → JSON Schema │
│ tool.prompt() → description 字段 │
│ 添加 defer_loading(可选) │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. API 请求(claude.ts:1699) │
│ POST /v1/messages │
│ { tools: allTools, tool_choice: undefined, ... } │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 5. 模型处理 │
│ 读取 tools[].description → 理解工具用途 │
│ 读取 tools[].input_schema → 理解参数结构 │
│ 读取 system prompt 中的使用指引 │
│ 自主决定是否调用、调用哪个工具 │
│ 输出 tool_use block │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 6. 流式接收(query.ts) │
│ 收集 tool_use blocks │
│ stream → assistantMessages │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 7. 工具编排(toolOrchestration.ts) │
│ partitionToolCalls() → 读并行 / 写串行 │
│ runToolsConcurrently / runToolsSerially │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 8. 工具执行(toolExecution.ts) │
│ findToolByName → 校验输入 → 权限检查 → 执行 → hooks │
│ 失败时返回 is_error: true 的 tool_result │
└──────────────────────────┬──────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 9. 结果返回 │
│ tool_result → 作为 user message 加入消息历史 │
│ API 再次请求(含新的消息)→ 模型处理结果 → 继续或结束 │
└─────────────────────────────────────────────────────────────────┘
关键文件索引
| 文件 | 功能 |
|---|---|
src/Tool.ts | Tool 接口定义、buildTool 工厂函数、findToolByName |
src/tools.ts | 工具组装、过滤、getTools / assembleToolPool |
src/constants/tools.ts | 各类 agent 的工具黑/白名单 |
src/constants/prompts.ts | System Prompt 中工具使用指引的生成 |
src/utils/api.ts | toolToAPISchema、Schema 转换 |
src/services/api/claude.ts | API 请求构建、工具过滤(ToolSearch) |
src/services/tools/toolOrchestration.ts | 工具编排(并行/串行执行) |
src/services/tools/toolExecution.ts | 工具执行核心逻辑、错误处理 |
src/query.ts | 主查询循环、流式响应处理 |
src/tools/BashTool/prompt.ts | BashTool 使用说明(prompt 示例) |