Anthropic Claude API 默认支持在同一轮次并发执行多个独立工具调用以节省 token 消耗,但当工具间存在依赖时会导致结果错误。disable_parallel_tool_use 参数可强制串行执行,其作用范围取决于所嵌入的 tool_choice 类型。
默认情况下,Claude 可以在一次对话轮次中发出多个 tool_use 块,并期望你在回复前全部执行它们。disable_parallel_tool_use 就是用来关闭这个行为的。它是 tool_choice 上的一个字段,而不是顶级参数,它保证的行为取决于它所在的 tool_choice 类型。
假设有两个工具 get_weather 和 get_time,以及一个需要同时调用两者的请求,Claude 通常会在单次交互中同时调用这两个工具:
{
"role": "assistant",
"content": [
{ "type": "tool_use", "id": "toolu_01A...", "name": "get_weather",
"input": { "city": "Lisbon" } },
{ "type": "tool_use", "id": "toolu_01B...", "name": "get_time",
"input": { "city": "Lisbon" } }
],
"stop_reason": "tool_use"
}
这是大多数情况下你期望的行为。两次独立查询合并为一次发送,节省了一次模型往返,而模型往返是 Agent 循环中开销最大的部分——它会重新发送整个对话并对所有内容支付 prefill 成本。协议中你需要做的,是执行两个工具并在紧随其后的单条 user 消息中返回两个 tool_result 块。
但当调用之间不是独立的时,这就成了问题。如果第二个调用的参数依赖于第一个调用的结果——比如先查询账户、再对它扣款——那么并行执行意味着模型在所需信息还不存在的情况下就猜测了第二组参数。模型在一个轮次内没有办法表达"等待那个结果"。
值得明确的是,模型在此过程中并没有做错什么。它发出两个调用是因为从已有信息来看两者都是可回答的;依赖关系存在于你的系统中,且从未以模型能看到的任何方式呈现过。这个框架指向了解决方案——要么让依赖关系可见,要么让并行成为不可能——而这个 flag 只是后者。
它是一个布尔值,位于 tool_choice 对象内部,默认为 false:
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"tools": [ /* ... */ ],
"tool_choice": { "type": "auto", "disable_parallel_tool_use": true },
"messages": [ { "role": "user", "content": "Weather and time in Lisbon?" } ]
}
开启后,同样的请求每个轮次只产生一个调用:
{
"role": "assistant",
"content": [
{ "type": "tool_use", "id": "toolu_01A...", "name": "get_weather",
"input": { "city": "Lisbon" } }
],
"stop_reason": "tool_use"
}
// 你返回天气结果;下一个轮次才会请求 get_time。
注意这里显式付出的代价:回答这个问题现在需要三次模型调用而不是两次,且每次调用之间对话都会增长。这就是权衡——用往返次数和输入 token 换取顺序的正确性。
tool_choice 有四种类型,flag 与其中三种的组合方式各不相同。这是需要搞清楚的关键区别:
{"type": "auto"} —— 模型决定是否使用工具。开启 disable_parallel_tool_use: true 后,它最多输出一个工具调用。仍有可能为零:模型可能以文本回答,并以 stop_reason: "end_turn" 结束该轮次。
{"type": "any"} —— 模型必须使用其中一个工具,但可以选择哪个。开启 flag 后它恰好输出一个工具调用。
{"type": "tool", "name": "..."} —— 必须使用指定的工具。开启 flag 后它恰好被调用一次,这里值得知道的是:没有 flag 时,被强制调用的工具仍然可以在一个轮次内被调用多次。这是可靠结构化输出背后的组合方式,参见 forcing a specific tool with tool_choice。
{"type": "none"} —— 不允许使用任何工具,所以该 flag 在此无关。
"最多一个"与"恰好一个"是 auto 和 any 之间的全部区别,在 auto 下设置 flag 后假设必然存在一个工具调用的代码,偶尔会收到纯文本回答而非预期结果。根据 stop_reason 分支判断,而不是根据你设置的 flag。
强制工具使用与 extended thinking 之间有一个已文档化的交互:开启 thinking 的路径不支持与默认路径相同方式的强制模式。如果你在同时使用 thinking 和 tool_choice,请查阅 Anthropic 的工具使用文档了解当前约束,而不是假设这个组合是免费的。
依赖步骤。 第二个调用的参数来自第一个调用的结果。并行在这里不会产生慢的答案,而是产生错误的答案。
副作用。 任何写、扣款、发送或删除操作。每轮一个操作意味着一次审查、一次确认、一件事可撤销。
限流或昂贵工具。 序列化是一个粗糙的节流手段,但它是模型无法绕过的节流。
单次结构化输出。 使用 type: "tool" 并设置 flag,你恰好获得一次 schema 形状参数的调用——一次干净提取,无需协调第二个块。
调试 Agent 循环。 每轮一个调用使跟踪可读。诊断期间值得开启,诊断结束后再关闭。
对独立实体的只读查询、对多个来源的扇出检索、以及任何调用之间确实不交互的延迟敏感场景。因为一条路由需要就全局开启是一个常见且代价昂贵的错误——它是一个 per-request 字段,应该按路由设置。
两个操作注意事项。Flag 约束的是 tool_use 块的数量;它不会移除伴随的文本块,单个调用旁仍可能出现文本——参见 text and tool calls in the same turn。另外它是 Anthropic 特有的:OpenAI 形状 API 中的等价物是顶级 parallel_tool_calls: false,位于请求的不同位置且默认行为不同,因此只映射 tool_choice 的转换层会悄悄丢弃这个设置。
Flag 是粗糙的:它作用于整个请求,且无论步骤是否真的依赖,每步都消耗一次往返。有三种替代方案值得了解,因为它们通常比全面关闭并行更合适。
在工具 schema 中编码依赖。 如果 charge_account 需要一个只有 lookup_account 才能产生的 account_id,且工具描述中说明了这一点,模型就没有可能在一个轮次内同时调用两者——它还没有参数。一个必需的不透明标识符比请求 flag 更强的约束,因为它使错误行为不可表示,而不是仅仅被禁止。
每轮变换 tool_choice。 它是一个 per-request 字段,因此循环可以在只读阶段保持 flag 关闭,然后在写入步骤中开启它——或强制特定工具。这让你在可以扇出的地方扇出,在需要严格顺序的地方严格顺序,都在一次对话内完成。
在自己一侧序列化。 没有任何规则要求你并行执行并行调用。你可以按数组顺序执行它们,或者先运行第一个并返回 is_error: true 的 tool_result 解释必须重新请求——模型会用第一个结果已在上下文中的状态重新发出它们。这需要更多代码,但这是唯一支持按调用而不是按请求决策的方案。
无论你选择哪种方式,有一件事不会变:后续 user 消息中 tool_result 块的数量必须与 assistant 轮次中 tool_use 块的数量匹配。决定不运行某个调用与被允许省略其结果是两回事。
Forcing a Specific Tool Call in the Claude API
Why Claude Sometimes Returns Text and a Tool Call in the Same Turn
Claude's tool_use and tool_result Content Blocks, End to End