載入中
載入中

平行工具呼叫:縮短延遲

摘要逐字稿

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 將失敗的結果送回,這樣一個失敗的工具呼叫就不會讓整個迴圈中止。