Claude Code 如何判断工具调用该并发还是串行?

在 Claude Code 里,模型经常会调用工具,比如读文件、搜索代码、执行 Bash 命令、编辑文件等。

一个很自然的问题是:

Claude Code 是怎么知道多个工具应该并发执行,还是一个一个串行执行的?

答案可以概括为一句话:

模型负责决定“这一轮要不要发多个工具调用”,客户端负责判断“这些工具能不能安全并发执行”。

一、整体流程

Claude Code 并不是靠模型返回一个明确的 parallel: true 或 serial: true 字段来判断。

它的逻辑分成两层:

  1. 提示词层:告诉模型什么时候应该一次性发多个工具调用
  2. 执行层:客户端根据工具的安全属性决定是否真的并发执行

流程大概如下:

  flowchart TD
    A[用户提出任务] --> B[系统提示词指导模型]
    B --> C{任务之间是否独立?}

    C -->|独立| D[模型在同一轮返回多个 tool_use]
    C -->|依赖| E[模型先返回一个 tool_use]

    D --> F[客户端收到多个工具调用]
    E --> F

    F --> G{工具是否并发安全?}

    G -->|是| H[并发执行]
    G -->|否| I[串行执行]

    H --> J[返回 tool_result]
    I --> J

    J --> K[模型继续下一轮推理]

二、模型侧:通过提示词引导并发

Claude Code 的系统提示词里有类似这样的规则:

1
2
3
4
5
6
7
8
Should work run in parallel?

Independent operations, such as reading unrelated files
or running unrelated searches, should make all calls
in the same response.

Dependent operations, where Step B needs output from Step A,
should be called sequentially.

翻译一下就是:

  • 互不依赖的任务:尽量放在同一轮里一起调用
  • 有依赖关系的任务:等前一个工具结果回来后,再调用下一个

例如:

可以并发

用户问:

帮我看看 src/query.ts 和 src/Tool.ts 里工具调用相关逻辑。

模型可以在同一轮返回两个读文件工具:

1
2
Read(src/query.ts)
Read(src/Tool.ts)

因为这两个文件的读取互不依赖。

应该串行

用户问:

先运行测试,看看失败原因,然后帮我修复。

这时模型应该先调用:

1
Bash("bun test")

等测试结果回来后,再决定读哪个文件、改哪里。

因为后续操作依赖测试输出。

三、客户端侧:通过 isConcurrencySafe 判断是否安全

即使模型在同一轮返回了多个工具调用,Claude Code 也不会无脑并发执行。

真正执行前,它会检查每个工具是否“并发安全”。

核心字段是:

1
isConcurrencySafe(input): boolean

每个工具都可以声明自己是否适合并发执行。

默认情况下,工具是 不并发安全 的:

1
isConcurrencySafe: () => false

这是一种保守策略:

如果不确定工具是否安全,就按串行处理。

四、哪些工具可以并发?

读文件工具:可以并发

读文件不会修改状态,所以通常是并发安全的。

1
2
3
isConcurrencySafe() {
  return true
}

例如:

1
2
3
Read(src/query.ts)
Read(src/Tool.ts)
Read(src/services/tools/toolOrchestration.ts)

这些可以一起执行。

Bash 工具:看命令是否只读

Bash 比较特殊,因为它既可能是安全的:

1
2
3
ls
grep
git status

也可能是危险的:

1
2
3
4
rm -rf
git commit
npm install
mv fileA fileB

所以 Bash 工具的逻辑大致是:

1
2
3
isConcurrencySafe(input) {
  return this.isReadOnly(input)
}

也就是说:

  • 只读命令:可以并发
  • 会修改系统状态的命令:串行执行

五、工具调用会被分批执行

客户端会把模型返回的一组工具调用拆成多个 batch。

规则是:

  • 连续的并发安全工具会合并成一个并发 batch
  • 非并发安全工具单独成为一个 batch
  • batch 之间串行执行

例如模型返回了下面这些工具:

1
2
3
4
5
6
Read(A)
Read(B)
Edit(C)
Read(D)
Bash("git status")
Bash("npm install")

可能会被拆成:

  flowchart TD
    A[模型返回多个 tool_use] --> B[Batch 1: Read A + Read B]
    B --> C[并发执行]

    C --> D[Batch 2: Edit C]
    D --> E[串行执行]

    E --> F[Batch 3: Read D + Bash git status]
    F --> G[并发执行]

    G --> H[Batch 4: Bash npm install]
    H --> I[串行执行]

原因是:

  • Read(A) 和 Read(B) 都安全,可以并发
  • Edit(C) 会修改文件,必须单独执行
  • Read(D) 和 git status 都是只读,可以并发
  • npm install 会修改依赖和文件系统,必须串行

六、最大并发数限制

即使有很多工具都可以并发,Claude Code 也有并发上限。

默认最大并发数是:

1
10

也可以通过环境变量调整:

1
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY=20

这样可以避免一次性启动太多任务,导致系统资源压力过大。

七、Streaming 模式下也一样

Claude Code 还有一种 Streaming Tool Execution 模式。

也就是说,模型还在流式输出时,只要某个 tool_use 已经出现,客户端就可以提前开始执行工具,而不用等整条 assistant 消息结束。

但它仍然遵守同样的并发安全规则:

1
2
3
只有当前正在执行的工具都并发安全,
并且新工具也并发安全,
才允许并发执行。

可以理解为:

  flowchart TD
    A[流式收到一个 tool_use] --> B{当前有工具在执行吗?}

    B -->|没有| C[立即执行]
    B -->|有| D{当前工具和新工具都并发安全吗?}

    D -->|是| E[一起并发执行]
    D -->|否| F[进入队列等待]

    C --> G[返回结果]
    E --> G
    F --> G

八、总结

Claude Code 的工具并发机制设计得比较稳健:

层级 作用
模型提示词 引导模型把独立任务放在同一轮调用
tool_use 数量 同一轮多个 tool_use 表示可以尝试批量执行
isConcurrencySafe 工具自己声明是否并发安全
执行器 按安全性分批,并发或串行执行
并发上限 防止资源被打爆

最终逻辑可以总结为:

模型决定“哪些事情可以一起请求”,客户端决定“哪些工具真的可以一起跑”。

这也是一个很值得借鉴的设计:

  • 模型负责语义判断
  • 程序负责安全约束
  • 默认保守
  • 显式声明并发安全
  • 对读操作开放并发,对写操作保持串行

这种设计既能提高性能,又能避免并发修改带来的风险。