A05: 工具调用内部机制
每当 Claude Code 读取文件、编辑代码、运行 Shell 命令或搜索代码库时,它都在进行一次工具调用(Tool Call)。工具调用是将 Claude 从文本生成器转变为强大的软件工程 Agent 的机制。本附录深入介绍内部机制:Claude 如何选择工具、参数如何结构化、结果如何返回、权限如何控制操作,以及如何通过 MCP 使用自定义工具扩展系统。
工具调用的工作原理:协议
对话结构
Claude Code 使用结构化消息格式与 Claude API 通信。对话中的每条消息有一个角色和内容:
Conversation messages (simplified):
{ role: "system", content: "You are Claude Code..." }
{ role: "user", content: "Fix the auth bug" }
{ role: "assistant", content: "I'll investigate the auth module.",
tool_use: { name: "Read", input: { file_path: "src/auth/middleware.ts" } } }
{ role: "tool", content: "[file contents...]",
tool_use_id: "toolu_01abc..." }
{ role: "assistant", content: "I found the issue on line 24.",
tool_use: { name: "Edit", input: { ... } } }
{ role: "tool", content: "File edited successfully.",
tool_use_id: "toolu_01def..." }
{ role: "assistant", content: "The bug was a unit mismatch..." }关键洞察:工具调用是 Claude 回复的一部分,而非与之分离。当 Claude 生成回复时,它可以同时包含文本和一个或多个工具调用。Harness 执行工具调用并将结果作为 tool 角色消息反馈。然后 Claude 继续生成。
工具定义:可用操作菜单
在对话开始前,Harness 向 Claude 提供一个工具定义列表。每个定义包括:
- 名称:工具标识符(如
Read、Edit、Bash) - 描述:工具的功能(自然语言)
- 参数:定义输入的 JSON Schema
- 必需参数:哪些输入是必须的
{
"name": "Read",
"description": "Reads a file from the local filesystem. You can access any file directly by using this tool.",
"parameters": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "The absolute path to the file to read"
},
"offset": {
"type": "integer",
"description": "The line number to start reading from",
"minimum": 0
},
"limit": {
"type": "integer",
"description": "The number of lines to read",
"exclusiveMinimum": 0
}
},
"required": ["file_path"]
}
}这些定义有两个用途:
- 告诉 Claude 存在哪些工具以及何时使用
- 告诉 Harness 如何验证 Claude 的工具调用参数后再执行
Claude 如何选择工具
决策过程
当 Claude 收到提示时,它必须决定:是用文本回复,还是调用工具?如果是工具,选哪个?
这个决策不是由单独的"工具路由"模块做出的。它来自与生成所有 Claude 输出相同的下一 Token 预测。工具定义是上下文的一部分,Claude 的训练教会了它在适当时候生成工具调用 Token。
过程大致如下:
Claude's internal decision (conceptual):
Context includes:
- System prompt saying "you have these tools available"
- Tool definitions with descriptions and schemas
- User says "Read the auth middleware"
Claude's attention mechanism weighs:
- "Read" in the user prompt matches the "Read" tool name
- "auth middleware" suggests a file path is needed
- The Read tool's description says "reads a file"
- Previous turns show examples of Read tool usage
Claude generates:
→ tool_use token (instead of regular text)
→ name: "Read"
→ input: { "file_path": "src/auth/middleware.ts" }影响工具选择的因素
多个因素影响 Claude 选择哪个工具:
1. 工具描述质量
描述是最重要的因素。Claude 读取描述来理解何时使用某个工具。对比:
# Weak description — Claude may not know when to use this
"Runs a command"
# Strong description — Claude understands exactly when to use this
"Executes a given bash command and returns its output. Use for
running tests, installing packages, checking git status, or any
shell operation."2. 参数 Schema 的具体性
定义良好的参数 Schema 引导 Claude 正确使用:
// Vague schema — Claude may pass wrong types
{
"path": { "type": "string" }
}
// Specific schema — Claude knows exactly what to provide
{
"file_path": {
"type": "string",
"description": "The absolute path to the file to read (must be absolute, not relative)"
}
}3. 对话上下文
Claude 从对话中之前的工具调用中学习。如果它用 Read 查看了一个文件现在需要修改它,它已经知道文件的路径和格式,使后续的 Edit 工具调用更准确。
4. 系统提示指令
系统提示包含工具偏好的指导。例如,Claude Code 的系统提示指示 Claude 优先使用 Edit 而非 Write 来修改现有文件,以及优先使用 Read 而非 Bash("cat ...") 来读取文件。这些指令影响工具选择。
工具选择决策树
以下是 Claude 对常见 Claude Code 任务遵循的概念性决策树:
Need information about a file?
├── Know the exact path?
│ └── Read (with file_path)
├── Know the filename but not the path?
│ └── Bash("find . -name 'filename'") → then Read
├── Know a pattern in the file content?
│ └── Bash("grep -r 'pattern' src/") → then Read
└── Need to understand directory structure?
└── Bash("ls -la src/") or Bash("find . -type f -name '*.ts'")
Need to modify a file?
├── File exists?
│ ├── Small, targeted change?
│ │ └── Edit (with old_string/new_string)
│ └── Complete rewrite?
│ └── Write (after Read)
└── New file?
└── Write
Need to execute something?
├── Shell command?
│ └── Bash
├── Need real-time output?
│ └── Bash (with timeout)
└── Long-running process?
└── Bash (with run_in_background)工具结果格式
结果如何进入上下文
当工具执行时,结果被格式化并作为 tool 角色消息添加到对话中。不同工具的格式不同:
Read 工具结果包含行号:
1 import { Router } from 'express';
2 import { validateToken } from './auth';
3
4 const router = Router();
5
6 router.get('/api/users', validateToken, (req, res) => {
7 // ...
8 });Bash 工具结果包含 stdout 和 stderr:
$ npm test
PASS src/auth/__tests__/middleware.test.ts
● Console
console.log
Token validated successfully
Tests: 3 passed, 3 total
Time: 1.234 sEdit 工具结果确认变更:
File edited successfully.Claude 如何解析结果
Claude 处理工具结果的方式与处理任何其他上下文相同:通过注意力机制。它没有特殊的"结果解析"逻辑。这意味着:
格式良好的结果更容易被 Claude 使用。 Read 输出中的行号帮助 Claude 引用具体行。结构化的测试输出帮助 Claude 识别哪些测试失败。
冗长的结果浪费上下文。 500 行测试输出中只有 3 行显示失败,约 2,000 Token 花在了不相关的信息上。这就是为什么控制输出详细程度很重要(参见 A02 上下文工程)。
Claude 可能误解模糊的结果。 如果命令产生了可能表示成功或失败的输出,Claude 可能误读。清晰的成功/失败信号(退出码、显式的 PASS/FAIL 消息)有所帮助。
工具调用中的错误处理
工具失败时发生什么
工具调用可能以几种方式失败,每种的处理方式不同:
1. 验证错误(执行前)
Harness 在执行工具前根据 JSON Schema 验证 Claude 的参数。如果验证失败,立即返回错误:
Claude: Edit({ file_path: "src/auth.ts", old_string: "", new_string: "..." })
Harness: Error — old_string cannot be empty
Claude: "I need to provide the text to replace. Let me read the file first
to find the exact text."2. 执行错误(执行中)
工具运行但失败了:
Claude: Bash("npm test")
Result: Error: ENOENT - package.json not found
Claude: "We're not in the right directory. Let me check the
project structure first."
Claude: Bash("ls")3. 超时错误
工具运行时间过长:
Claude: Bash("npm install", timeout: 30000)
Result: Error: Command timed out after 30 seconds
Claude: "The install is taking too long. Let me try with a longer
timeout or check if there's a network issue."4. 权限拒绝
权限系统阻止了工具调用:
Claude: Bash("rm -rf /important/data")
Harness: [Permission denied — user must approve]
User: [Denies]
Result: Error: Permission denied by user
Claude: "I understand. That was too broad. Let me use a more
targeted approach."错误恢复模式
Claude 的错误恢复遵循可预测的模式:
| 错误类型 | Claude 的典型恢复 |
|---|---|
| 文件未找到 | 搜索正确的文件路径 |
| 编辑目标未找到 | 重新读取文件获取当前内容 |
| 命令失败 | 读取错误消息,调整命令 |
| 权限拒绝 | 询问用户或尝试权限更低的方法 |
| 超时 | 使用更长超时重试或将任务拆分为更小的部分 |
| 输出语法错误 | 使用不同的格式标志重新运行 |
良好的错误处理是模型大小最重要的领域之一。更大的模型(Opus)更擅长从上下文线索诊断错误并尝试实质不同的方法,而不是重试同样失败的命令。
权限系统
权限如何工作
Claude Code 的权限系统是 Claude 工具调用决策和实际执行之间的安全层。每个工具调用在执行前都通过权限系统:
Permission evaluation flow:
Claude generates tool call
│
▼
┌──────────────────┐
│ Is this tool in │──── Yes ──── Execute immediately
│ the allowlist? │
└───────┬──────────┘
│ No
▼
┌──────────────────┐
│ Is this tool in │──── Yes ──── Block and return error
│ the denylist? │
└───────┬──────────┘
│ No
▼
┌──────────────────┐
│ Prompt user for │──── Allow ──── Execute
│ permission │──── Deny ──── Return error
└──────────────────┘权限配置层级
权限在多个层级配置,更具体的层级覆盖更宽泛的:
Permission resolution order:
1. CLI flags (--allowedTools, --disallowedTools)
Most specific — overrides everything
2. Project settings (.claude/settings.json)
Project-specific permissions
3. User settings (~/.claude/settings.json)
Global personal permissions
4. Default permissions
Built-in defaults for each tool工具风险分类
工具按风险级别隐式分类:
只读工具(低风险):
Read-- 读取文件内容Glob-- 按模式列出文件Grep-- 搜索文件内容
这些通常自动允许,因为它们不能修改你的系统。
写入工具(中风险):
Edit-- 修改现有文件Write-- 创建或覆盖文件
这些默认需要权限,因为它们会更改你的代码。
执行工具(高风险):
Bash-- 运行任意 Shell 命令
Bash 是风险最高的工具,因为它可以做任何事:删除文件、安装包、发起网络请求、修改系统配置。Bash 的权限规则通常最细粒度:
{
"permissions": {
"allow": [
"Bash(npm test)",
"Bash(npm run lint)",
"Bash(git status)",
"Bash(git diff*)"
]
}
}这允许特定的安全命令,同时仍需审批其他任何命令。
工具调用批量和并行
顺序 vs 并行工具调用
Claude 可以在单个回复中生成多个工具调用。当工具调用之间相互独立时,Harness 可以并行执行它们:
Sequential execution (dependent calls):
Claude: Read("src/auth/middleware.ts")
Result: [file contents]
Claude: Edit("src/auth/middleware.ts", { old_string: "...", new_string: "..." })
Result: File edited
The Edit depends on the Read result, so they must be sequential.
Parallel execution (independent calls):
Claude: Read("src/auth/middleware.ts")
Read("src/auth/types.ts")
Read("src/auth/utils.ts")
Results: [all three files read simultaneously]
The Reads are independent, so they execute in parallel.Claude 何时批量调用工具
Claude 倾向于在以下情况批量工具调用:
- 读取多个已知路径的文件
- 运行独立的命令(如同时检查 git 状态和运行 linter)
- 在做决定前从多个来源收集信息
Claude 倾向于在以下情况顺序执行:
- 每个工具调用依赖于前一个的结果
- 它不确定正确路径需要探索
- 它处于试错循环中(编辑、测试、修复)
优化并行性
你可以通过提示来鼓励并行工具调用:
# Encourages parallel reads
You: "Read these three files and compare their approaches to error
handling: src/auth/errors.ts, src/api/errors.ts, src/db/errors.ts"
# Encourages sequential (Claude must read before analyzing)
You: "Find all error handling files, then analyze their patterns"第一个提示给 Claude 了三个完整路径,使并行读取成为可能。第二个需要先搜索步骤,迫使顺序执行。
通过 MCP 扩展自定义工具
MCP 工具如何被发现
模型上下文协议(Model Context Protocol, MCP)允许你用自定义工具扩展 Claude Code。当 Claude Code 启动时,它连接到配置的 MCP 服务器并发现可用工具:
MCP tool discovery:
1. Claude Code reads MCP server configuration from settings
2. Connects to each configured MCP server
3. Calls the server's `tools/list` endpoint
4. Receives tool definitions (name, description, schema)
5. Adds these definitions to the tool list alongside built-in tools
6. Claude now "sees" custom tools in its context and can use them自定义工具定义
设计良好的 MCP 工具定义示例:
{
"name": "database_query",
"description": "Execute a read-only SQL query against the development database. Returns results as a JSON array of objects. Use this when you need to inspect data, verify migrations, or understand the current database state. Only SELECT queries are allowed.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The SQL SELECT query to execute. Must start with SELECT."
},
"limit": {
"type": "integer",
"description": "Maximum number of rows to return (default: 100, max: 1000)",
"default": 100,
"maximum": 1000
}
},
"required": ["query"]
}
}自定义工具的关键设计原则:
- 描述性名称:
database_query比db_q好。Claude 使用名称作为信号。 - 详细描述:解释何时使用工具,而不仅仅是它做什么。描述是 Claude 选择工具的主要指南。
- 约束性 Schema:使用
minimum、maximum、enum和pattern防止误用。只接受 SELECT 查询的工具比接受任何 SQL 的工具更安全。 - 有用的参数描述:每个参数描述应解释预期格式和任何约束。
Claude 如何对待自定义工具
Claude 以与内置工具完全相同的方式对待 MCP 工具。它在同一工具列表中看到它们,读取描述,并使用相同机制生成工具调用。没有特殊的"自定义工具模式"。
这意味着:
- 好的描述使自定义工具可被发现:如果你的工具描述清楚解释了何时使用它,Claude 会适当选择它。
- 差的描述导致混淆:如果你的自定义工具描述与内置工具重叠,Claude 可能选错。
- Schema 质量影响参数准确性:宽松的 Schema 导致更多参数错误。
Hooks:扩展工具调用管道
什么是 Hooks
Hooks 允许你在工具调用管道的特定点注入自定义行为。它们作为由工具调用事件触发的 Shell 命令运行:
Hook integration points:
User prompt arrives
│
▼
Claude generates tool call
│
▼
┌──────────────────┐
│ PreToolCall hook │ ← Runs BEFORE the tool executes
└───────┬──────────┘ Can modify parameters or block the call
│
▼
Tool executes
│
▼
┌──────────────────┐
│ PostToolCall hook │ ← Runs AFTER the tool executes
└───────┬──────────┘ Can modify results or trigger side effects
│
▼
Result returned to ClaudeHooks 配置
Hooks 在 settings.json 中配置:
{
"hooks": {
"preToolCall": [
{
"matcher": "Edit",
"command": "node .claude/hooks/lint-before-edit.js"
}
],
"postToolCall": [
{
"matcher": "Bash",
"command": "node .claude/hooks/log-commands.js"
}
]
}
}实用 Hooks 用例
| 用例 | Hook 类型 | 匹配器 | 命令 |
|---|---|---|---|
| 编辑后自动格式化 | postToolCall | Edit | npx prettier --write $TOOL_INPUT_FILE_PATH |
| 记录所有命令 | postToolCall | Bash | echo "$(date): $TOOL_INPUT_COMMAND" >> .claude/command-log.txt |
| 保护受限文件 | preToolCall | Edit | node .claude/hooks/protect-files.js |
| 提交前检查 lint | preToolCall | Bash | node .claude/hooks/check-lint.js |
Hooks 在不修改 Claude Code 本身的情况下扩展工具调用管道。它们对团队工作流特别有用,可以强制执行规范(格式化、lint)或跟踪 Agent 行为(命令日志、变更审计)。
综合理解:工具调用的完整生命周期
以下是单个工具调用的完整生命周期,从 Claude 的决策到结果进入上下文:
1. Context assembly
System prompt + CLAUDE.md + history + user prompt
→ sent to Claude API
2. Token generation
Claude generates tokens, including a tool_use block
→ { name: "Bash", input: { command: "npm test" } }
3. Schema validation
Harness validates input against Bash tool's JSON Schema
→ Is "command" a string? Yes → valid
4. Permission check
Harness checks permission rules
→ Is "Bash(npm test)" in the allowlist? Yes → proceed
5. Pre-hook execution (if configured)
Hook runs before the tool executes
→ Pre-hook logs the command, returns "proceed"
6. Tool execution
Harness runs: npm test
→ stdout: "Tests: 5 passed", exit code: 0
7. Post-hook execution (if configured)
Hook runs after execution
→ Post-hook logs success
8. Result formatting
Harness formats the output for the conversation
→ { role: "tool", content: "Tests: 5 passed\n...", tool_use_id: "toolu_..." }
9. Context update
Result is appended to conversation history
→ Context grows by ~200 tokens
10. Next turn
Updated context is sent back to Claude API
Claude generates next response (text or more tool calls)核心要点
- 工具调用通过下一 Token 预测生成,而非单独的路由系统。Claude "决定"使用工具的方式与它决定写任何文本的方式相同 -- 通过预测最有用的下一个 Token。
- 工具描述是工具选择的主要因素。 写详细、具体的描述,解释何时使用工具,而不仅仅是它做什么。
- JSON Schema 约束工具使用。 定义良好的带有类型、范围和描述的 Schema 减少参数错误。
- 权限系统是决策和执行之间的安全层。 配置它以自动允许安全操作、控制危险操作。
- 错误处理是模型的职责,但你可以通过鼓励"重新读取后再重试"模式和在反复失败后介入来帮助。
- 自定义工具(MCP)是一等公民。 Claude 以与内置工具完全相同的方式对待它们。在描述和 Schema 上投入精力。
- Hooks 在不修改 Claude Code 的情况下扩展管道。 用于格式化、日志记录和规范执行。
另见:A04 Agent 架构模式 了解工具调用如何融入更大的 Agent 循环,以及 A06 MCP 协议详解 了解完整的协议规范。