Skip to content

A05: 工具调用内部机制 ​

相关章节: 第 1 章 安装与首次会话、第 5 章 文件与 Shell 操作

每当 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
  • 必需参数:哪些输入是必须的
json
{
  "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"]
  }
}

这些定义有两个用途:

  1. 告诉 Claude 存在哪些工具以及何时使用
  2. 告诉 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 正确使用:

json
// 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 s

Edit 工具结果确认变更:

File edited successfully.

Claude 如何解析结果 ​

Claude 处理工具结果的方式与处理任何其他上下文相同:通过注意力机制。它没有特殊的"结果解析"逻辑。这意味着:

  1. 格式良好的结果更容易被 Claude 使用。 Read 输出中的行号帮助 Claude 引用具体行。结构化的测试输出帮助 Claude 识别哪些测试失败。

  2. 冗长的结果浪费上下文。 500 行测试输出中只有 3 行显示失败,约 2,000 Token 花在了不相关的信息上。这就是为什么控制输出详细程度很重要(参见 A02 上下文工程)。

  3. 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 的权限规则通常最细粒度:

json
{
  "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 工具定义示例:

json
{
  "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"]
  }
}

自定义工具的关键设计原则:

  1. 描述性名称:database_query 比 db_q 好。Claude 使用名称作为信号。
  2. 详细描述:解释何时使用工具,而不仅仅是它做什么。描述是 Claude 选择工具的主要指南。
  3. 约束性 Schema:使用 minimum、maximum、enum 和 pattern 防止误用。只接受 SELECT 查询的工具比接受任何 SQL 的工具更安全。
  4. 有用的参数描述:每个参数描述应解释预期格式和任何约束。

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 Claude

Hooks 配置 ​

Hooks 在 settings.json 中配置:

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 类型匹配器命令
编辑后自动格式化postToolCallEditnpx prettier --write $TOOL_INPUT_FILE_PATH
记录所有命令postToolCallBashecho "$(date): $TOOL_INPUT_COMMAND" >> .claude/command-log.txt
保护受限文件preToolCallEditnode .claude/hooks/protect-files.js
提交前检查 lintpreToolCallBashnode .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)

核心要点 ​

  1. 工具调用通过下一 Token 预测生成,而非单独的路由系统。Claude "决定"使用工具的方式与它决定写任何文本的方式相同 -- 通过预测最有用的下一个 Token。
  2. 工具描述是工具选择的主要因素。 写详细、具体的描述,解释何时使用工具,而不仅仅是它做什么。
  3. JSON Schema 约束工具使用。 定义良好的带有类型、范围和描述的 Schema 减少参数错误。
  4. 权限系统是决策和执行之间的安全层。 配置它以自动允许安全操作、控制危险操作。
  5. 错误处理是模型的职责,但你可以通过鼓励"重新读取后再重试"模式和在反复失败后介入来帮助。
  6. 自定义工具(MCP)是一等公民。 Claude 以与内置工具完全相同的方式对待它们。在描述和 Schema 上投入精力。
  7. Hooks 在不修改 Claude Code 的情况下扩展管道。 用于格式化、日志记录和规范执行。

另见:A04 Agent 架构模式 了解工具调用如何融入更大的 Agent 循环,以及 A06 MCP 协议详解 了解完整的协议规范。

基于 MIT 许可发布