AIGC标识 ChatGPT总结的Claude Code架构-Claude Code 的工具调用系统

一、整体介绍

Claude Code 的工具调用系统,本质上是一个“模型负责思考,程序负责执行”的 Agent 架构。

模型本身不能直接运行 Bash、读取硬盘或修改代码。它只能输出一个结构化请求,例如:

{
  "name": "Bash",
  "input": {
    "command": "npm test",
    "timeout": 120000
  }
}

Claude Code 收到请求后,才负责:

  1. 检查这个工具是否存在;
  2. 运行 PreToolUse Hooks;
  3. 检查权限规则;
  4. 判断是否需要用户批准;
  5. 为 Bash 创建沙箱边界;
  6. 真正执行命令;
  7. 收集退出码、标准输出和错误输出;
  8. 运行 PostToolUse Hook;
  9. 把结果作为新消息交还给模型;
  10. 模型根据结果决定下一步。

所以它不是“模型调用了一个函数就结束”,而是持续运行这个循环:

理解任务 → 选择工具 → 安全检查 → 执行 → 观察结果
          ↑                              ↓
          └──────── 调整策略并继续 ────────┘

官方将其概括为“收集上下文、执行操作、验证结果”三阶段 Agent 循环。Claude Code 工作原理


二、核心理念是什么

1. 推理和执行分离

Claude 模型只负责:

  • 理解用户意图;
  • 判断缺什么信息;
  • 选择工具;
  • 生成工具参数;
  • 根据工具结果调整计划。

Claude Code 客户端负责:

  • 真实文件访问;
  • 命令执行;
  • 权限判断;
  • 沙箱隔离;
  • Hooks;
  • 超时和后台任务;
  • 会话记录;
  • 上下文管理。

这意味着即便模型“想做”某件事,也不代表它真的能做。最终权限掌握在客户端和用户手中。

2. 工具结果是下一轮推理的“观察”

一次工具调用不是任务终点,而是 Agent 获得环境反馈的方式。

例如:

Claude 猜测测试失败是数据库问题
    ↓
调用 Bash:npm test
    ↓
结果显示 Redis 连接失败
    ↓
Claude 修正判断
    ↓
读取 Redis 配置
    ↓
修改配置
    ↓
再次调用 Bash 验证

这是一种典型的:

Reason → Act → Observe → Reason
思考   → 行动 → 观察    → 再思考

3. 默认最小权限,逐步扩大能力

Claude Code 不会因为模型输出了工具请求就直接执行。

默认情况下:

  • 工作区内的 ReadGrepGlob 等只读工具通常可以直接执行;
  • EditWrite 等修改操作需要批准;
  • Bash 默认需要批准,但部分内置只读命令可以免提示;
  • 可以对具体命令配置允许、询问或禁止;
  • 企业管理策略可以设置不可被用户覆盖的限制。

权限判断顺序是:

Deny → Ask → Allow

只要命中 Deny,即使同时命中更具体的 Allow,也会被拒绝。例如:

{
  "permissions": {
    "allow": [
      "Bash(git *)"
    ],
    "deny": [
      "Bash(git push *)"
    ]
  }
}

结果:

git status       → 允许
git log --oneline → 允许
git push origin main → 禁止

权限是由 Claude Code 客户端强制执行的,不是靠提示词提醒模型自律。Claude Code 权限系统

4. 给模型能力,但把风险限制在边界内

Claude Code 没有简单地阉割 Bash,而是采用:

强大的通用 Bash
        +
细粒度权限
        +
OS 级沙箱
        +
可编程 Hooks
        +
用户确认

这样的设计思路是:保留 Agent 的通用性,同时从执行层控制影响范围。


三、整体工具系统架构图

```mermaid
flowchart TB
    U["用户任务"] --> C["上下文组装器"]

    subgraph Context["上下文层"]
        C1["系统提示词"]
        C2["CLAUDE.md 与 Auto Memory"]
        C3["会话历史"]
        C4["当前文件与工具结果"]
        C5["工具名称与参数 Schema"]
    end

    C1 --> C
    C2 --> C
    C3 --> C
    C4 --> C
    C5 --> C

    C --> M["Claude 模型:理解、规划、选择工具"]

    M --> D{"是否需要调用工具?"}
    D -- "否" --> F["生成最终回答"]
    D -- "是" --> TC["结构化 Tool Use 请求"]

    TC --> R["工具注册与路由层"]

    subgraph Registry["工具能力层"]
        R1["内置工具:Read、Edit、Bash、Grep 等"]
        R2["MCP 外部工具"]
        R3["Skill 工作流"]
        R4["Agent 子代理"]
        R5["ToolSearch 延迟发现"]
    end

    R1 --> R
    R2 --> R
    R3 --> R
    R4 --> R
    R5 --> R

    R --> H1["PreToolUse Hooks"]

    H1 --> HD{"Hook 是否阻止、修改或延迟?"}
    HD -- "拒绝" --> REJ["生成拒绝结果"]
    HD -- "修改" --> IN["使用 updatedInput"]
    HD -- "继续" --> P["权限引擎"]
    IN --> P

    P --> PD{"Deny / Ask / Allow"}
    PD -- "Deny" --> REJ
    PD -- "Ask" --> USER["请求用户批准"]
    USER -- "拒绝" --> REJ
    USER -- "批准" --> S
    PD -- "Allow" --> S["执行环境"]

    subgraph Runtime["执行层"]
        S1["文件工具执行器"]
        S2["Bash / PowerShell 进程"]
        S3["OS 级沙箱"]
        S4["MCP Server"]
        S5["子代理独立上下文"]
    end

    S --> S1
    S --> S2
    S2 --> S3
    S --> S4
    S --> S5

    S1 --> O["标准化 Tool Result"]
    S3 --> O
    S4 --> O
    S5 --> O
    REJ --> O

    O --> H2["PostToolUse 或 Failure Hooks"]
    H2 --> CM["结果裁剪、落盘和上下文管理"]
    CM --> C4
    C4 --> M

    M --> V{"任务是否完成并验证?"}
    V -- "否" --> D
    V -- "是" --> F

    F --> LOG["JSONL 会话记录与最终结果"]
```

这个架构中有一个非常重要的闭环:

模型输出 Tool Use
        ↓
客户端输出 Tool Result
        ↓
Tool Result 重新进入模型上下文
        ↓
模型继续选择下一个工具

Agent 会一直循环,直到模型返回一个不包含工具调用的文本响应。Agent Loop 生命周期


四、工具调用系统的详细组成

1. 工具描述和 Schema

每个工具会向模型提供:

  • 工具名称;
  • 工具作用;
  • 输入字段;
  • 哪些字段必填;
  • 字段的数据类型;
  • 使用限制。

简化后的 Bash Schema 可以理解为:

{
  "name": "Bash",
  "description": "在用户环境中执行 Shell 命令",
  "input_schema": {
    "type": "object",
    "properties": {
      "command": {
        "type": "string"
      },
      "timeout": {
        "type": "number"
      },
      "run_in_background": {
        "type": "boolean"
      }
    },
    "required": ["command"]
  }
}

模型并不是随意输出文本:

帮我运行一下 npm test

而是输出机器可以识别的结构化 Tool Use Block。

简化表示:

{
  "type": "tool_use",
  "id": "toolu_01ABC",
  "name": "Bash",
  "input": {
    "command": "npm test",
    "timeout": 120000
  }
}

工具执行完成后,Claude Code 使用相同 ID 进行关联:

{
  "type": "tool_result",
  "tool_use_id": "toolu_01ABC",
  "content": "3 tests failed...",
  "is_error": false
}

这样可以准确知道某个结果属于哪次调用。


2. 内置工具分类

Claude Code 不只提供 Bash,而是按语义提供专业工具。

类型 工具示例 作用
文件读取 Read 读取文件、图片、PDF 等
文件修改 EditWrite 精确修改或创建文件
代码搜索 GlobGrepLSP 查找文件、代码和符号
系统执行 BashPowerShell 命令、脚本、Git、构建和测试
网络访问 WebSearchWebFetch 搜索和读取网页
工具发现 ToolSearch 按需加载大量工具
工作流 SkillWorkflow 执行可复用流程
多代理 Agent 在独立上下文中运行子代理
任务管理 TaskCreateTaskUpdate 管理复杂任务状态
用户交互 AskUserQuestion 请求用户提供选择
外部系统 MCP 工具 连接数据库、浏览器和第三方 API

完整列表见:Claude Code Tools Reference

为什么有 Bash 还需要 Read、Edit 和 Grep

因为专业工具比通用 Bash 更容易治理。

例如读取文件可以用:

cat .env

也可以用:

Read(".env")

但两者的安全含义不同:

  • Read 能直接匹配文件路径权限;
  • Read 的结果格式更稳定;
  • Claude Code 可以追踪读过哪些文件;
  • 可以实施 read-before-edit;
  • 更容易做上下文裁剪;
  • 不需要启动 Shell 子进程。

Bash("cat .env") 是一个通用进程。单独禁止 Read(.env) 并不能阻止 Bash 读取 .env;必须用 OS 级沙箱或凭据路径限制阻止底层进程访问。

这正是 Claude Code 为什么同时需要“工具权限”和“Bash 沙箱”。


3. 工具选择

一般不是通过传统程序写死:

if user_says_test:
    run_bash("npm test")

而是把可用工具及其描述交给模型,由模型结合当前上下文判断:

  • 是否需要调用工具;
  • 调用哪个工具;
  • 参数是什么;
  • 能不能并行;
  • 结果是否足够;
  • 下一步要不要换工具。

例如用户说:

找到处理支付回调的代码,并告诉我有没有签名校验。

Claude 可能选择:

Glob → Grep → Read → 最终回答

如果用户说:

修复支付回调签名校验并运行测试。

Claude 可能选择:

Glob → Grep → Read → Edit → Bash → Read → Edit → Bash

工具组合不是固定工作流,而是模型根据每一步结果动态生成。


4. ToolSearch:工具按需加载

如果连接很多 MCP Server,工具可能有几百甚至几千个。把每个工具的完整 Schema 都塞进上下文会浪费大量 Token,也会降低模型选择准确率。

Claude Code 使用 ToolSearch 做延迟发现:

会话启动
  ↓
只提供精简的工具名称或搜索入口
  ↓
Claude 判断需要 GitHub 工具
  ↓
ToolSearch 搜索 GitHub PR 相关工具
  ↓
加载具体工具 Schema
  ↓
调用工具

核心思想是:

工具描述懒加载,而不是全量预加载

这既降低上下文成本,也避免模型面对过多相似工具时产生选择混乱。


5. 并行调用策略

Claude 可以在一轮响应中请求多个工具。

但 Claude Code 不会把所有工具都无脑并行:

  • ReadGlobGrep 等只读工具可以并行;
  • 标注为只读的 MCP 工具可以并行;
  • EditWriteBash 等可能修改状态的工具默认串行;
  • 自定义工具默认串行;
  • 自定义工具可以使用 readOnlyHint 声明只读,从而允许并发。

例如:

并行读取:

Read(package.json) ─┐
Read(tsconfig.json) ├─→ 一起返回
Read(vite.config.ts)┘

但是:

Edit(config.ts)
    ↓
Bash(npm test)

需要串行,因为测试必须看到修改后的代码。

这是一个基于“副作用”的并发模型,而不是只根据工具名称硬编码。并行工具执行


五、权限、Hooks 和沙箱如何配合

1. 权限层:决定允不允许

权限规则可以控制:

{
  "permissions": {
    "allow": [
      "Read(/src/**)",
      "Bash(npm test *)",
      "Bash(npm run lint *)"
    ],
    "ask": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Read(//Users/me/.ssh/**)",
      "Bash(git push *)",
      "Bash(rm -rf *)"
    ]
  }
}

权限层负责回答:

这个工具请求在策略上是否被允许?

2. Hook 层:运行时检查、修改和审计

PreToolUse 在工具执行和权限提示之前运行。

它能:

  • 阻止调用;
  • 强制弹出询问;
  • 修改工具输入;
  • 添加额外上下文;
  • 写审计日志;
  • 根据生产/测试环境动态判断;
  • 调用外部审批服务。

Hook 接收到的输入类似:

{
  "session_id": "abc123",
  "cwd": "/project",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

Hook 可以返回:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "测试命令符合项目策略",
    "updatedInput": {
      "command": "npm test -- --runInBand",
      "timeout": 180000
    },
    "additionalContext": "当前环境为本地开发环境"
  }
}

多个 Hook 同时运行时,采用更严格的结果:

deny > defer > ask > allow

需要注意:Hook 返回 allow 不能覆盖显式 Deny 或 Ask 权限规则;Hook 是附加控制层,不是绕过组织策略的后门。Hooks 官方说明

3. 沙箱层:即使执行了,也限制它能碰什么

权限控制的是:

这条命令能不能开始运行?

沙箱控制的是:

命令运行以后,操作系统允许它访问什么?

默认沙箱一般允许:

  • 读取工作目录;
  • 写入工作目录及子目录;
  • 写入会话临时目录。

一般阻止:

  • 修改项目外文件;
  • 修改系统目录;
  • 访问未允许的网络域名;
  • 读取显式禁止的凭据;
  • 子进程绕过边界。

macOS 使用 Seatbelt,Linux/WSL2 主要使用 bubblewrap;限制作用于 Bash 启动的所有子进程,而不仅仅是最外层 Shell。Bash 沙箱官方文档

因此,即使有人通过提示注入让 Claude 生成:

cat ~/.ssh/id_rsa

只要 OS 级沙箱明确禁止 ~/.ssh,底层进程仍然无法读取。


六、Claude Code 工具系统的独特之处

1. Bash 是“通用逃生舱”,专业工具是“可治理主路径”

Claude Code 可以通过 Bash 完成几乎所有终端操作,但优先提供 ReadEditGrep 等专业工具。

这样既不会被固定工具集限制,又能让常见操作:

  • 更结构化;
  • 更可审计;
  • 更容易授权;
  • 更节省上下文;
  • 更容易验证。

2. 安全决策不依赖模型自觉

很多 Agent 系统只在提示词里写:

不要执行危险命令。

Claude Code 还会在模型外部实施:

权限规则
+ PreToolUse Hooks
+ 用户批准
+ OS 沙箱
+ 网络代理
+ 凭据屏蔽

模型可以犯错,但执行层仍能阻止越权。

3. Hook 可以改变行为,不只是监听日志

Hooks 不只是“执行后通知”。

PreToolUse 可以:

  • 拒绝;
  • 修改参数;
  • 请求审批;
  • 延迟执行;
  • 向 Claude 注入额外上下文。

所以工具系统可以被企业扩展成动态策略引擎。

4. 工具生态可以无限扩展,但上下文不会同步爆炸

通过 MCP 接入外部系统,通过 ToolSearch 延迟加载工具 Schema,解决了:

工具越多 → Schema 越多 → Token 越多 → 工具选择越差

的问题。

5. 根据副作用决定并发方式

只读工具并行、写操作串行,是一个简单但实用的事务思路:

无副作用 → 提高并发
有副作用 → 保证顺序

6. Bash 大输出不会无限塞进上下文

Bash 默认输出超过约 30,000 字符时:

  • 完整输出保存到会话目录文件;
  • 只给 Claude 返回开头预览和文件路径;
  • Claude 有需要时再搜索或分段读取完整输出。

这避免一次构建日志填满整个上下文。输出上限可以配置,但存在硬上限。Bash 输出机制

7. 长命令自动后台化

Bash 默认超时约 2 分钟,单次可请求最高约 10 分钟。

对于开发服务器、watcher 或长测试:

{
  "command": "npm run dev",
  "run_in_background": true
}

命令会成为后台任务,Claude 可以继续读取代码或处理其他问题。

如果普通命令超时,Claude Code也可能把它移到后台,而不是简单杀死进程。

8. 工具拒绝本身也是一种观察结果

如果权限拒绝某次调用,模型通常不会直接崩溃,而是收到类似:

Bash command denied by permission rule:
Bash(git push *)

模型随后可以:

  • 换一种不需要权限的方式;
  • 请求用户操作;
  • 只生成建议;
  • 报告无法继续。

因此权限失败也被纳入 Agent 循环。


七、用 Bash 走一个完整例子

假设用户说:

运行项目测试,找到失败原因,修复后重新验证。

第一步:模型判断需要执行测试

Claude 已经知道项目使用 npm,于是生成简化后的工具请求:

{
  "type": "tool_use",
  "id": "toolu_test_001",
  "name": "Bash",
  "input": {
    "command": "npm test",
    "timeout": 120000
  }
}

此时模型并没有运行命令,只是提出请求。

第二步:运行 PreToolUse Hooks

Claude Code 向 Hook 发送:

{
  "session_id": "session_123",
  "cwd": "/Users/alice/payment-service",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "timeout": 120000
  }
}

假设项目有两个 Hook:

Hook A:记录全部 Bash 命令
Hook B:阻止 rm -rf、git push 和生产部署

对于 npm test

Hook A → 写入审计日志
Hook B → 不阻止

注意,即使后面的 Hook 阻止调用,前面的审计 Hook 也可能已经执行;匹配的 Hooks 会先各自完成,再汇总决定。

第三步:权限判断

权限配置:

{
  "permissions": {
    "allow": [
      "Bash(npm test *)"
    ],
    "ask": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Bash(rm -rf *)"
    ]
  }
}

npm test 命中:

Bash(npm test *)

因此无需用户确认。

如果没有 Allow 规则,则根据当前权限模式:

  • 弹出确认;
  • 或在沙箱自动允许模式下尝试沙箱执行;
  • 或直接拒绝。

第四步:建立 Bash 执行环境

Claude Code 确定:

Shell:zsh/bash
CWD:/Users/alice/payment-service
超时:120 秒
后台:否
沙箱:开启
可写目录:项目目录和会话临时目录
网络:按 allowedDomains 限制

每次 Bash 命令实际运行在独立进程中。

需要注意:

  • 主会话中合法的 cd 可以影响后续 Bash 调用;
  • export TOKEN=... 不会自动在下一次 Bash 调用中保留;
  • Shell 启动文件中的别名和函数会被捕获并应用;
  • 子代理的 Bash 工作目录变化不会向父会话传播。

第五步:执行命令

系统实际运行:

npm test

得到:

Exit code: 1

FAIL src/auth/token.test.ts
Expected: true
Received: false

TypeError: Cannot read properties of undefined
  at validateToken (src/auth/token.ts:42)

第六步:结果标准化

Claude Code 将结果与原来的 Tool Use ID 对应:

{
  "type": "tool_result",
  "tool_use_id": "toolu_test_001",
  "content": {
    "exit_code": 1,
    "stdout": "FAIL src/auth/token.test.ts ...",
    "stderr": ""
  }
}

如果输出特别长,则会变成:

{
  "exit_code": 1,
  "preview": "FAIL src/auth/token.test.ts ...",
  "full_output_file": "/session/tool-results/bash-001.txt",
  "truncated": true
}

第七步:运行 PostToolUse Hook

PostToolUse Hook 可以:

  • 记录命令耗时;
  • 收集退出码;
  • 上报审计平台;
  • 把额外信息放入 Claude 上下文。

如果是工具层面的异常,会触发相应 Failure Hook。工具已经执行后,Post Hook 可以报告问题,但不能让已经发生的操作倒流。

第八步:结果回到模型

Claude 看到:

测试失败
位置:src/auth/token.ts:42
原因:访问 undefined 属性

于是发出并行只读调用:

Read(src/auth/token.ts)
Read(src/auth/token.test.ts)
Grep("validateToken", src/)

这些工具没有副作用,可以并行执行。

第九步:模型修改代码

Claude 找到错误:

return token.user.active;

token.user 不存在时会报错。

它调用 Edit:

{
  "name": "Edit",
  "input": {
    "file_path": "src/auth/token.ts",
    "old_string": "return token.user.active;",
    "new_string": "return token.user?.active === true;"
  }
}

Edit 经过自己的 Hook、权限和文件一致性检查后执行。

第十步:重新调用 Bash 验证

Claude 再次请求:

{
  "name": "Bash",
  "input": {
    "command": "npm test -- --runInBand",
    "timeout": 180000
  }
}

结果:

Exit code: 0

PASS src/auth/token.test.ts
Tests: 24 passed, 24 total

Claude 不只是看到命令成功,还验证目标确实完成。

第十一步:模型停止调用工具

当 Claude 判断:

  • 错误已经修复;
  • 测试已经通过;
  • 没有其他必要操作;

它返回不包含 Tool Use 的最终消息:

已修复 validateToken 在 token.user 缺失时抛出异常的问题。
修改为可选链判断,24 个测试全部通过。

Agent 循环结束。


八、这个 Bash 例子的完整时序图

```mermaid
sequenceDiagram
    participant U as 用户
    participant M as Claude 模型
    participant H as Claude Code Harness
    participant PH as PreToolUse Hooks
    participant P as 权限引擎
    participant S as Bash 沙箱
    participant OS as Shell 子进程
    participant PO as PostToolUse Hooks
    participant C as 会话上下文

    U->>M: 运行测试、修复失败并重新验证
    M->>H: Tool Use:Bash("npm test")

    H->>PH: 发送 tool_name、tool_input、cwd、session_id
    PH-->>H: 记录审计;无阻止决定

    H->>P: 检查 Bash(npm test)
    P-->>H: Allow

    H->>S: 创建文件系统和网络边界
    S->>OS: 在独立进程执行 npm test
    OS-->>S: exit 1 + 测试失败日志
    S-->>H: 标准化执行结果

    H->>PO: 触发 PostToolUse
    PO-->>H: 审计信息或 additionalContext

    H->>C: 写入 Tool Result
    C->>M: 返回失败位置和错误内容

    M->>H: 并行 Read、Read、Grep
    H-->>M: 返回源代码和引用位置

    M->>H: Tool Use:Edit(token.ts)
    H->>PH: PreToolUse(Edit)
    PH-->>H: 继续
    H->>P: 检查编辑权限
    P-->>H: Allow 或用户批准
    H-->>M: Edit 成功

    M->>H: Tool Use:Bash("npm test -- --runInBand")
    H->>PH: PreToolUse(Bash)
    PH-->>H: 继续
    H->>P: 检查 Bash 权限
    P-->>H: Allow
    H->>S: 在沙箱执行
    S->>OS: npm test -- --runInBand
    OS-->>S: exit 0 + 24 tests passed
    S-->>H: Tool Result
    H->>C: 写入验证结果
    C->>M: 测试全部通过

    M->>U: 返回最终修复说明
```

九、一句话总结

Claude Code 工具系统的核心不是“给大模型一个 Bash”,而是建立了一个完整的代理执行平台:

模型通过结构化工具请求表达行动意图,Claude Code 使用权限、Hooks、用户批准和 OS 沙箱控制真实执行,再把环境结果反馈给模型形成闭环;同时通过专业工具、MCP、工具懒加载、副作用感知并发、后台任务和上下文裁剪,使它既通用,又可控、可扩展、可审计。

posted @ 2026-07-21 02:38  炸马铃薯条  阅读(2)  评论(0)    收藏  举报