並列ツール呼び出し:レイテンシを削減する
Claudeは1回の応答で複数のツールを要求でき、コード側はそれらをまとめて実行できます。この動画では、架空のエンジニアリングコンサルティング会社Meridianの現場を対象に、サイトの状態を確認します。ツール呼び出しを1つずつ実行すると19.9秒かかりますが、並列ツール呼び出しでは10.8秒で済みます。
この動画のデモは2026年9月に収録されました。使用モデルはClaude Sonnet 5(claude-sonnet-5)です。新しいモデルでは結果が異なる場合があります。
ツール呼び出しを1つずつ実行すると、ユーザーは1つ処理されるたびに待たされることになります。
Claudeが要求するツールが互いに依存し合わない場合、1回の応答でまとめて要求することができます。Claudeはデフォルトでこれを行うため、あなたのループはすべてのツール呼び出しを処理し、それぞれについて結果を送り返す必要があります。
1回の応答に含まれる3つのツール呼び出し
並列ツール使用とは、1つの応答に複数の tool_use ブロックが含まれることを意味します。
Meridianには、現場が作業可能な状態かどうかを確認するPythonスクリプトがあります。現場確認には互いに依存しない3つの照会が必要です。プロジェクトの状況、現場の天候、そして許可証の状態です。
以下は、Meridianの現場確認に対するClaudeの応答で、要求する各ツールに対して1つずつ tool_use ブロックが含まれています。
{
"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 があります。
すべてのツール呼び出しを実行する
1回のツール呼び出しを前提に書かれたループは、最初の tool_use ブロックしか読み取りません。結果を1つしか送り返さないため、残りの2つのツール呼び出しは結果を受け取れず、APIはリクエストを拒否します。したがって、応答に含まれるすべての tool_use ブロックを収集してください。
calls = [b for b in resp.content if b.type == "tool_use"]次に、それらのツール呼び出しを同時に実行します。それぞれの照会は、処理時間のほとんどを他システムからの応答待ちに費やします。それぞれに独自のスレッドを割り当てれば、3つの待ち時間は合算されるのではなく重なり合います。
with ThreadPoolExecutor() as pool:
results = list(pool.map(run_tool, calls))run_tool は1つのツール呼び出しを実行してその結果を返すもので、この後の2つのセクションでこれを組み立てていきます。
ツール呼び出しを同時に実行することは、Meridianの照会のようにデータを読み取るだけで互いに依存しない場合は、通常は安全です。ツールがデータを変更する場合や、決まった順序で実行する必要がある場合は、1つずつ順番に実行する方がよいこともあります。
すべての結果を1つのメッセージにまとめて送り返す
3つのツール呼び出しが実行された後、Claudeはそれらの結果を待っています。各結果は tool_result ブロックに入れて送り返され、その tool_use_id には、対応するツール呼び出しの id が入ります。
result = {"type": "tool_result", "tool_use_id": call.id}すべての tool_use ブロックに対して1つの tool_result が対応し、それらはすべて次のユーザーメッセージにまとめて入ります。
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": results})
3つの結果すべてを1つのユーザーメッセージにまとめることで、Claudeは並列でツールを呼び出し続けます。
もしあなたのループが各結果を別々のメッセージで送り返すようにしていると、会話はClaudeが一度に1つずつツールを要求したかのように見えてしまいます。これは、Claudeによる並列ツール呼び出しが減ってしまうよくある原因です。
失敗したツール呼び出しにはエラー結果を返す
3つのツール呼び出しを同時に実行するということは、1つが失敗し、残りの2つが成功することもあり得るということです。あなたのコードが例外を発生させ、それを誰も捕捉しない場合、ループは停止し、成功していた2つの結果も一緒に失われてしまいます。
そのため、失敗も結果として送り返してください。 これは通常の tool_result であり、content にエラーメッセージを入れ、さらに is_error: true を付加したものです。このメッセージには、何が問題だったのか、そしてClaudeが次に何を試すべきかを記載する必要があります。
try:
result["content"] = HANDLERS[call.name](**call.input)
except Exception as exc:
result["content"] = f"{call.name} failed: {exc}"
result["is_error"] = TrueHANDLERS は、ツール名からそのツールの処理を行うPython関数への辞書(dict)です。各関数は文字列を返します。tool_result の content は文字列、またはコンテンツブロックのリストだからです。
ループを組み立てる
ここまでのコード断片は、以下のMeridianのループに組み込まれます。このスクリプトでは、すでに client、tools、prompt、HANDLERS が設定されています。
キーポイント
- 応答に含まれるすべてのツール呼び出しを実行し、互いに依存しないものは同時に実行しましょう。
- 各ツール呼び出しに対して1つの
tool_resultを送り返し、それらをすべて1つのユーザーメッセージにまとめましょう。 - 各結果を別々のメッセージで送らないでください。並列ツール使用が減ってしまいます。
- 失敗は
is_error: trueを付けて結果として送り返すことで、1つのツール呼び出しの失敗でループ全体が止まらないようにしましょう。