AIエージェント実装の現場で「ツール呼び出しを最適化したい」という声を頻繁に聞く。そのカギとなるのが Parallel Tool Use(並列ツール呼び出し) と tool_choice(ツール選択制御) だ。
Parallel Tool Use を適切に設計すれば、独立した複数の情報取得を直列でなく並列に実行でき、エージェントの応答時間を大幅に短縮できる。一方 tool_choice を使えば「必ずツールを使わせる」「逆に完全禁止にする」といった制御が可能になる。本記事では Claude API・OpenAI API 両対応のコードを交えながら、実務に即した設計パターンを解説する。
既存の関連記事として、ツール出力のスキーマ設計(ID 2529)とInstructor による構造化出力(ID 2573)もあわせて参照してほしい。本記事はそれらの「スキーマ定義・バリデーション」視点とは異なり、「ツールをいつ・どのように呼び出すかの制御」に特化した内容だ。
1. tool_choice の4モードと選択フロー
Claude API の tool_choice パラメータには4つのモードがある(2026年6月時点の公式仕様)。
| モード | JSON指定 | 動作 | 典型ユースケース |
|---|---|---|---|
auto |
{"type": "auto"} |
Claude が自律判断。tools が渡されている場合のデフォルト | 汎用チャットボット、状況に応じて使い分けるエージェント |
any |
{"type": "any"} |
提供されているツールのうち必ず1つ以上を使用 | データ取得フェーズ、ルーティングエージェント |
tool |
{"type": "tool", "name": "ツール名"} |
指定した特定のツールを強制使用 | フォーム入力の構造化、関数の強制呼び出し |
none |
{"type": "none"} |
ツール使用を完全禁止。tools 未指定時のデフォルト | 最終要約フェーズ、純粋テキスト応答が必要な場面 |
制限事項(2026年6月時点)
- Extended Thinking と組み合わせた場合:
anyとtoolは 400 エラーになる。Extended Thinking 使用時はautoまたはnoneのみ対応 - Claude Mythos Preview(招待制):
anyとtoolは 400 エラー。auto/noneのみ対応 any/tool指定時は API がアシスタントメッセージをプリフィルするため、自然言語の前置き説明はモデルから出力されない
ツール選択の決定フロー
シナリオ判断
│
├─ ツールを使うかどうかモデルに任せたい
│ └─ tool_choice: auto(デフォルト)
│
├─ 必ずツールを使わせたいが、どれかは問わない
│ └─ tool_choice: any
│ └─ ただし Extended Thinking 併用時は不可
│
├─ 特定のツールを強制したい(フォーム埋め、スキーマ取得 等)
│ └─ tool_choice: { type: "tool", name: "ツール名" }
│ └─ ただし Extended Thinking 併用時・Mythos Preview では不可
│
└─ テキスト応答だけ欲しい(要約・最終回答フェーズ)
└─ tool_choice: none
2. Parallel Tool Use の仕組みと実装コード
Claude は同一ターンで複数の tool_use ブロックを返すことができる。これが Parallel Tool Use だ。依存関係のない複数ツールを同時に呼び出し、asyncio.gather や Promise.all で並列実行することでレイテンシを削減できる。
レスポンスの content 配列に複数の tool_use ブロックが入る形式になっている。
// Claude APIのレスポンス例(並列ツール呼び出し)
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "東京と大阪の天気を確認します。"
},
{
"type": "tool_use",
"id": "toolu_01AbCd",
"name": "get_weather",
"input": { "city": "東京" }
},
{
"type": "tool_use",
"id": "toolu_02EfGh",
"name": "get_weather",
"input": { "city": "大阪" }
}
]
}
重要: tool_result は必ず単一の user メッセージにまとめること
並列呼び出しの結果を返す際は、全ての tool_result を1つの user メッセージにまとめなければならない。複数の user メッセージに分割すると、モデルが以後の並列呼び出しを避けるようになるという公式ドキュメントの注意がある。
import anthropic
import asyncio
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "指定都市の天気を取得する",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名"}
},
"required": ["city"]
}
},
{
"name": "get_population",
"description": "指定都市の人口を取得する",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名"}
},
"required": ["city"]
}
}
]
async def execute_tool(tool_use_block):
"""ツールを非同期実行(実際の実装では外部APIを呼び出す)"""
tool_name = tool_use_block.name
tool_input = tool_use_block.input
# 実際の処理(ここではモック)
await asyncio.sleep(0.1) # API呼び出しをシミュレート
if tool_name == "get_weather":
result = f"{tool_input['city']}の天気: 晴れ, 気温25℃"
elif tool_name == "get_population":
result = f"{tool_input['city']}の人口: 約1,396万人"
else:
result = "Unknown tool"
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": result
}
async def run_agent_with_parallel_tools(user_message: str):
# 初回リクエスト
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": user_message}]
)
# 並列ツール呼び出しが含まれているか確認
tool_use_blocks = [
block for block in response.content
if block.type == "tool_use"
]
if not tool_use_blocks:
return response.content[0].text
# 全ツールを並列実行
tool_results = await asyncio.gather(
*[execute_tool(block) for block in tool_use_blocks]
)
# tool_result を全て単一の user メッセージにまとめる(必須)
messages = [
{"role": "user", "content": user_message},
{"role": "assistant", "content": response.content},
{"role": "user", "content": list(tool_results)} # 全結果を1メッセージに
]
# 最終回答を取得
final_response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
return final_response.content[0].text
# 実行例
result = asyncio.run(run_agent_with_parallel_tools(
"東京と大阪の天気と人口を教えてください"
))
print(result)
3. OpenAI API の parallel_tool_calls と tool_choice
OpenAI API でも同様の機能が提供されている。Claude API との主な違いを比較してみよう。
| 機能 | Claude API | OpenAI API |
|---|---|---|
| 並列ツール呼び出し制御 | tool_choice の disable_parallel_tool_use フィールド |
parallel_tool_calls パラメータ(bool) |
| デフォルト挙動 | 並列呼び出しが有効 | parallel_tool_calls: true(有効) |
| 特定ツール強制 | {"type": "tool", "name": "ツール名"} |
{"type": "function", "function": {"name": "ツール名"}} |
| ツール禁止 | {"type": "none"} |
{"type": "none"}(同様) |
| 必ずツール使用 | {"type": "any"} |
{"type": "required"} |
以下は OpenAI API での並列ツール呼び出しと、その無効化の実装例だ。
from openai import OpenAI
client = OpenAI()
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定都市の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "株価を取得する",
"parameters": {
"type": "object",
"properties": {
"ticker": {"type": "string"}
},
"required": ["ticker"]
}
}
}
]
# 並列ツール呼び出し(デフォルト: true)
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "東京の天気とトヨタの株価を教えてください"}],
tools=tools,
parallel_tool_calls=True # デフォルトで true
)
# 並列ツール呼び出しを無効化(直列実行したい場合)
response_serial = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "東京の天気とトヨタの株価を教えてください"}],
tools=tools,
parallel_tool_calls=False # 1回につき1ツールのみ
)
# 特定ツールの強制呼び出し
response_forced = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "天気に関係なく株価だけ教えてください"}],
tools=tools,
tool_choice={"type": "function", "function": {"name": "get_stock_price"}}
)
4. disable_parallel_tool_use による直列化(Claude API)
Claude API では tool_choice の中に disable_parallel_tool_use フィールドを追加することで並列呼び出しを無効化できる。
import anthropic
client = anthropic.Anthropic()
# 並列ツール呼び出しを無効化(直列実行)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={
"type": "auto",
"disable_parallel_tool_use": True # 1ターンにつき最大1ツールのみ
},
messages=[{"role": "user", "content": "東京と大阪の天気を教えてください"}]
)
# any + disable_parallel_tool_use: 厳密に1ツールのみ(0は不可)
response_any_single = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={
"type": "any",
"disable_parallel_tool_use": True # 必ず1つ、かつ1つのみ
},
messages=[{"role": "user", "content": "何か情報を取得してください"}]
)
disable_parallel_tool_use の動作まとめ:
auto+disable_parallel_tool_use: true→ 最大1ツールを使用(0も可)any+disable_parallel_tool_use: true→ 厳密に1ツールのみ(必ず使用・複数禁止)tool+disable_parallel_tool_use: true→ 指定ツールを1回だけ呼び出す
5. 実務ユースケース別の設計パターン
パターン1: 情報収集フェーズ(並列化で高速化)
互いに依存関係のないデータ取得は並列化の恩恵が最も大きい。たとえばユーザーのダッシュボードに表示するために「売上データ・在庫数・アラート数」を3つのAPIから取得するケースでは、直列実行より並列実行の方が全体レイテンシを大幅に削減できる。
import anthropic
import asyncio
client = anthropic.Anthropic()
# 情報収集専用のツールセット
data_tools = [
{
"name": "get_sales",
"description": "売上データを取得",
"input_schema": {"type": "object", "properties": {"period": {"type": "string"}}, "required": ["period"]}
},
{
"name": "get_inventory",
"description": "在庫数を取得",
"input_schema": {"type": "object", "properties": {"category": {"type": "string"}}, "required": ["category"]}
},
{
"name": "get_alerts",
"description": "アラート一覧を取得",
"input_schema": {"type": "object", "properties": {}, "required": []}
}
]
async def fetch_dashboard_data(period: str, category: str):
"""tool_choice: any で必ずツール使用を強制、並列収集"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
tools=data_tools,
tool_choice={"type": "any"}, # 必ずツールを使わせる(情報収集フェーズ)
messages=[{
"role": "user",
"content": f"{period}の売上と{category}カテゴリの在庫とアラートをすべて取得してください"
}]
)
tool_uses = [b for b in response.content if b.type == "tool_use"]
# 並列実行
async def mock_tool_call(block):
await asyncio.sleep(0.2) # 外部API呼び出しのシミュレーション
return {"type": "tool_result", "tool_use_id": block.id, "content": f"{block.name}の結果"}
results = await asyncio.gather(*[mock_tool_call(b) for b in tool_uses])
return results
# 最終要約フェーズは tool_choice: none でテキスト応答のみ
def summarize_results(data_results, messages):
"""tool_choice: none で純粋テキスト要約"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=data_tools,
tool_choice={"type": "none"}, # ツール禁止。テキスト要約のみ
messages=messages + [{"role": "user", "content": "以上のデータを200字で要約してください"}]
)
return response.content[0].text
パターン2: 冪等性と部分失敗のハンドリング
並列ツール呼び出しで重要なのが冪等性の確保だ。複数ツールを同時に実行する場合、一部が失敗しても残りの結果は返す必要がある。is_error: true を使えば Claude が次ターンで正しく再発行する。
冪等性・リトライ・タイムアウトの設計詳細(ID 2605)もあわせて参照してほしい。
async def execute_tool_safe(tool_use_block):
"""部分失敗時に is_error を返す安全なツール実行"""
try:
# 外部APIコール(冪等なエンドポイントを使用)
result = await call_external_api(tool_use_block.name, tool_use_block.input)
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": result
}
except Exception as e:
# 失敗してもエラーを返す(他のツール結果は活かす)
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": f"エラー: {str(e)}",
"is_error": True # Claudeが次ターンで再試行を判断
}
6. よくある落とし穴
落とし穴1: tool_result を複数の user メッセージに分割する
並列で呼び出した複数ツールの結果を、それぞれ別の user メッセージに入れてしまうパターン。これをするとモデルが以後の並列呼び出しを避けるようになる。必ず全ての tool_result を1つの user メッセージの content 配列にまとめること。
落とし穴2: Extended Thinking 利用時に any / tool を使う
拡張思考モードと any / tool を組み合わせると 400 エラーになる。拡張思考が有効なリクエストでは auto または none のみ使用可能。
落とし穴3: 依存関係のあるツールを並列に投げる
ツールAの結果を使ってツールBを呼び出す必要がある場合、同一ターンで両方を指定するのは誤りだ。依存関係がある場合は必ず直列(複数ターン)に分けること。disable_parallel_tool_use: true で直列化するか、プロンプトで「まずAを実行してから、その結果でBを実行してください」と指示する。
落とし穴4: OpenAI の gpt-4.1-nano で並列ツールを使う
OpenAI の gpt-4.1-nano-2025-04-14 は parallel_tool_calls 有効時に同一ツールを複数回呼ぶ場合があるため、このモデルを使う場合は parallel_tool_calls: false を推奨。
7. コスト最適化との連携
Parallel Tool Use と tool_choice の適切な設計はレイテンシだけでなく、コスト削減にも効く。AIエージェントのコスト最適化7原則(ID 2467)でも触れているが、不要なターン数を減らすことがコスト削減の基本だ。
- 直列で3ターンかかっていたものを並列で1ターンにまとめる → 入力トークンの重複が減る
- 情報収集フェーズは
anyで強制・並列実行 → ツール選択の無駄な思考フェーズを削減 - 最終要約フェーズは
noneでツールを渡さない → コンテキストにツール定義を含めない
まとめ
Parallel Tool Use と tool_choice の4モードを組み合わせることで、AIエージェントのツール実行を精密にコントロールできる。主なポイントをまとめると:
- auto: 汎用チャット・状況依存の判断が必要な場面。Extended Thinking との組み合わせも安全
- any: データ収集フェーズで「必ずツールを使わせたい」場合。Extended Thinking 非対応
- tool: 特定の関数を強制実行させたい場合。フォーム入力の構造化に有効
- none: 最終要約・純粋テキスト応答が必要なフェーズ
- 並列実行:
tool_resultを必ず1つの user メッセージにまとめること - 冪等性: 部分失敗には
is_error: trueで Claude に次ターンで再判断させる
この記事を読んでAIエージェント設計の解像度が上がってきた方へ
UravationではAIエージェント導入の研修・コンサルを行っています。設計パターンの選択から実装・運用フェーズまで、チームに合わせたハンズオン支援が可能です。
