ChatGPT总结的Claude Code架构-Claude Code 的工具调用系统
一、整体介绍
Claude Code 的工具调用系统,本质上是一个“模型负责思考,程序负责执行”的 Agent 架构。
模型本身不能直接运行 Bash、读取硬盘或修改代码。它只能输出一个结构化请求,例如:
{
"name": "Bash",
"input": {
"command": "npm test",
"timeout": 120000
}
}
Claude Code 收到请求后,才负责:
- 检查这个工具是否存在;
- 运行
PreToolUseHooks; - 检查权限规则;
- 判断是否需要用户批准;
- 为 Bash 创建沙箱边界;
- 真正执行命令;
- 收集退出码、标准输出和错误输出;
- 运行
PostToolUseHook; - 把结果作为新消息交还给模型;
- 模型根据结果决定下一步。
所以它不是“模型调用了一个函数就结束”,而是持续运行这个循环:
理解任务 → 选择工具 → 安全检查 → 执行 → 观察结果
↑ ↓
└──────── 调整策略并继续 ────────┘
官方将其概括为“收集上下文、执行操作、验证结果”三阶段 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 不会因为模型输出了工具请求就直接执行。
默认情况下:
- 工作区内的
Read、Grep、Glob等只读工具通常可以直接执行; Edit、Write等修改操作需要批准;- 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 等 |
| 文件修改 | Edit、Write |
精确修改或创建文件 |
| 代码搜索 | Glob、Grep、LSP |
查找文件、代码和符号 |
| 系统执行 | Bash、PowerShell |
命令、脚本、Git、构建和测试 |
| 网络访问 | WebSearch、WebFetch |
搜索和读取网页 |
| 工具发现 | ToolSearch |
按需加载大量工具 |
| 工作流 | Skill、Workflow |
执行可复用流程 |
| 多代理 | Agent |
在独立上下文中运行子代理 |
| 任务管理 | TaskCreate、TaskUpdate |
管理复杂任务状态 |
| 用户交互 | 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 不会把所有工具都无脑并行:
Read、Glob、Grep等只读工具可以并行;- 标注为只读的 MCP 工具可以并行;
Edit、Write、Bash等可能修改状态的工具默认串行;- 自定义工具默认串行;
- 自定义工具可以使用
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 完成几乎所有终端操作,但优先提供 Read、Edit、Grep 等专业工具。
这样既不会被固定工具集限制,又能让常见操作:
- 更结构化;
- 更可审计;
- 更容易授权;
- 更节省上下文;
- 更容易验证。
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、工具懒加载、副作用感知并发、后台任务和上下文裁剪,使它既通用,又可控、可扩展、可审计。

浙公网安备 33010602011771号