加载中
加载中

并行工具调用:降低延迟

摘要文字记录

Claude可以在一次响应中请求多个工具,您的代码可以一起运行它们。该视频检查了Meridian(一家虚构的工程咨询公司)的工作现场。逐个运行工具调用需要19.9秒,而并行工具调用只需10.8秒。

本视频中的演示录制于2026年9月。它运行在Claude Sonnet 5(claude-sonnet-5)上。在较新的模型上,结果可能会有所不同。

逐个运行工具调用会让您的用户依次等待每一个调用。

当Claude想要的工具彼此不依赖时,它可以在一次响应中全部请求。Claude默认会这样做,因此您的循环必须处理响应中的每一个工具调用,并为每一个都发回一个结果。

一次响应中的三个工具调用

并行工具使用意味着一个响应中包含多个tool_use块。

Meridian有一个Python脚本,用于检查工作现场是否已准备好进行现场作业。现场检查需要三项互不依赖的查询:项目状态、现场天气,以及许可证状态。

以下是Claude对Meridian现场检查的响应,其中为每个想要使用的工具都有一个tool_use块。

json
{
  "role": "assistant",
  "stop_reason": "tool_use",
  "content": [
    {"type": "tool_use", "id": "toolu_01", "name": "get_project_status",
     "input": {"job_number": "MEG-2026-0142"}},
    {"type": "tool_use", "id": "toolu_02", "name": "get_site_weather",
     "input": {"site": "Riverside"}},
    {"type": "tool_use", "id": "toolu_03", "name": "get_permit_state",
     "input": {"permit_id": "P-88213"}}
  ]
}

该响应的stop_reason是tool_use,每个工具调用都有各自的id。

运行每一个工具调用

为单个工具调用编写的循环只会读取第一个tool_use块。它只会发回一个结果,另外两个工具调用永远得不到各自的结果,API会拒绝该请求。因此要收集响应中的每一个tool_use块。

python
calls = [b for b in resp.content if b.type == "tool_use"]

接下来,同时运行这些工具调用。每个查询大部分时间都花在等待另一个系统上。给每个查询分配各自的线程,这样三个等待过程就会重叠,而不是累加。

python
with ThreadPoolExecutor() as pool:
    results = list(pool.map(run_tool, calls))

run_tool运行一个工具调用并返回其结果,接下来的两节将构建这个函数。

当工具调用只读取数据、彼此不依赖时(就像Meridian的查询那样),同时运行它们通常是安全的。当工具会更改数据,或必须按特定顺序执行时,最好逐个依次运行它们。

在一条消息中发回所有结果

一旦三个工具调用运行完毕,Claude就在等待它们的结果。每个结果都回传到一个tool_result块中,其tool_use_id持有它所响应的工具调用的id。

python
result = {"type": "tool_result", "tool_use_id": call.id}

每个tool_use块都会得到一个tool_result,它们全部放在下一条用户消息中。

python
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": results})
顶部的横条显示 Claude 的一条响应,其中包含三个工具调用:toolu_01 get_project_status、toolu_02 get_site_weather 和 toolu_03 get_permit_state。下方左侧面板带有对勾,显示一条用户消息包含全部三个工具结果。右侧面板带有叉号,显示三条用户消息,每条包含一个结果。

将全部三个结果放在一条用户消息中,可以让Claude继续并行调用工具。

如果您的循环改为将每个结果放在各自的消息中发送,那么对话看起来就像Claude一次只请求了一个工具一样。这是Claude减少并行工具调用的一个常见原因。

为错误的工具调用返回一个错误结果

同时运行三个工具调用意味着其中一个可能失败,而另外两个成功。如果您的代码抛出异常且没有任何地方捕获它,您的循环就会停止,那两个正确的结果也会随之丢失。

因此要把失败也作为结果发回去。 这就是一个普通的tool_result,其content为错误信息,并附带is_error: true。该消息应说明出了什么问题,以及Claude接下来应该尝试什么。

python
try:
    result["content"] = HANDLERS[call.name](**call.input)
except Exception as exc:
    result["content"] = f"{call.name} failed: {exc}"
    result["is_error"] = True

HANDLERS是一个字典,将工具名称映射到执行每个工具具体工作的Python函数。每个函数都返回一个字符串,因为tool_result的content既可以是字符串,也可以是内容块列表。

把循环组装起来

上面的代码片段组合成了下面Meridian的循环。该脚本已经设置好了client、tools、prompt和HANDLERS。

加载中

关键要点

  • 运行响应中的每一个工具调用,当它们彼此不依赖时,同时运行它们。
  • 为每个工具调用发回一个tool_result,并将它们全部放在一条用户消息中。
  • 不要把每个结果放在各自的消息中发送,因为那样会减少并行工具使用。
  • 将失败作为结果发回,并附带is_error: true,这样一个坏的工具调用就不会使循环停止。