はじめに / 対象と前提
Claude API の tool use で「ツールを定義したのに呼んでくれない」「必ずこのツールを通して JSON で返してほしい」という場面で使うのが tool_choice だ。パラメータ自体は 1 行だが、強制した途端に別の場所が壊れるので、自分がハマった順にまとめる。
- 想定読者:Claude API の tool use を一度は動かしたことがある人
- 前提知識:
toolsの定義とtool_use/tool_resultの往復 - 環境:Python 3.13 /
anthropicSDK 0.6x 系 / モデルはclaude-sonnet-5-5(手元のモデル ID に読み替えてほしい)
TL;DR
-
tool_choiceはauto(既定)/any(どれか必ず呼ぶ)/tool(指定の 1 本を必ず呼ぶ)/none(呼ばせない)の 4 種類 - 強制(
any/tool)は 最初の 1 ターンだけ にする。ループ全体に付けると永遠に終わらない - 強制は拡張思考と併用できず 400 になる。また強制時は
tool_useの前の説明テキストが出なくなる
手順 / 動かし方
1. 4 種類の指定方法
tool_choice={"type": "auto"} # 呼ぶかどうかは Claude が決める(既定)
tool_choice={"type": "any"} # tools のどれかを必ず呼ぶ
tool_choice={"type": "tool", "name": "classify"} # classify を必ず呼ぶ
tool_choice={"type": "none"} # ツールを呼ばせない
2. 特定ツールを強制して構造化された結果を受け取る
問い合わせ文を分類する例。tool で 1 本に固定すると、返ってくる content は必ず tool_use ブロックになる。
import anthropic
client = anthropic.Anthropic() # ANTHROPIC_API_KEY を環境変数から読む
tools = [{
"name": "classify",
"description": "問い合わせ文をカテゴリと緊急度に分類して記録する",
"input_schema": {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["billing", "bug", "question"]},
"urgency": {"type": "integer", "description": "1(低)〜3(高)"},
},
"required": ["category", "urgency"],
},
}]
res = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "classify"},
messages=[{"role": "user", "content": "昨日から決済画面が 500 で落ちます。至急お願いします"}],
)
print(res.stop_reason)
block = next(b for b in res.content if b.type == "tool_use")
print(block.input)
実行結果:
tool_use
{'category': 'bug', 'urgency': 3}
tool_choice を外して同じ入力を投げると、モデルの判断次第で「決済画面のエラーですね。分類します」のようなテキストだけ返して stop_reason が end_turn になる回が混ざる。分類・抽出のように ツールを通らない応答が不要な処理 では、最初から強制しておくほうが後段のコードが単純になる。
3. 強制は 1 ターン目だけにして auto に戻す
エージェントループに組み込む場合の核心部分はこうなる。
def run(messages, first_choice):
choice = first_choice
while True:
res = client.messages.create(
model="claude-sonnet-5-5", max_tokens=2048,
tools=tools, tool_choice=choice, messages=messages,
)
if res.stop_reason != "tool_use":
return res
messages.append({"role": "assistant", "content": res.content})
messages.append({"role": "user", "content": execute_tools(res.content)})
choice = {"type": "auto"} # 2 ターン目以降は必ず auto に戻す
最後の 1 行が次のハマりどころ 1 の回避策そのものだ。
ハマりどころ
1. 強制したままループを回すと永遠に終わらない
tool_choice={"type": "any"} を付けっぱなしでループを書いたところ、stop_reason が毎回 tool_use で返り続け、同じツールが同じ引数で何十回も呼ばれた。
-
原因:
any/toolは「このターンで必ずツールを呼べ」という指定で、ツール結果を受け取った後のターンにも等しく効く。Claude はテキストで締めくくる選択肢を持てないので、end_turnに到達できない -
回避策:強制は初回だけにして以降は
autoに戻す(上のコード)。加えてループには必ず上限回数を入れる。強制の有無に関係なく保険になる
なお tool_choice を途中で変えると、プロンプトキャッシュのうち messages 部分が無効になる(tools と system のキャッシュは残る)。長い会話でキャッシュを使っている場合、切り替えたターンだけ cache_read_input_tokens が減るのは仕様どおりの挙動だ。
2. 拡張思考と併用すると 400
thinking を有効にしたリクエストに any か tool を付けると、リクエスト自体が弾かれる。
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error':
{'type': 'invalid_request_error',
'message': 'Thinking may not be enabled when tool_choice forces tool use.'}}
-
原因:拡張思考と両立する
tool_choiceはautoとnoneだけ。強制系は思考ブロックを挟む余地がないため非対応になっている -
回避策:どちらかを諦める。自分は「思考が要る難しい判断」は
autoのまま system プロンプトで「回答前に必ず classify を呼ぶこと」と明示し、「機械的な抽出」は thinking を切って強制、と用途で分けた。auto+ 強い指示でも呼ばれない回はゼロにならないので、stop_reasonがtool_useでなければ 1 回だけ再要求する分岐を入れておく
3. 強制すると前置きテキストが出ない/並列呼び出しの個数が変わる
auto のときは「まず天気を調べます」のようなテキストブロックが tool_use の前に付くことがある。これを進捗表示に使っていた UI が、any に変えた途端に無言になった。
-
原因:強制時は API 側がアシスタントの応答をツール呼び出しから始めさせるので、明示的に頼んでも
tool_useの前に自然文は出ない -
回避策:説明文が欲しいなら
autoのまま、user メッセージ側で「○○ツールを使って答えて」と指示する。進捗表示はtool_useブロックのnameから自前で組み立てるほうが安定する
もう 1 つ、any / tool でも 1 ターンに複数の tool_use が返ることがある。「必ず 1 個」を前提に res.content[0] を決め打ちするとここで落ちる。1 個に固定したいときは次のように書く。
tool_choice={"type": "tool", "name": "classify", "disable_parallel_tool_use": True}
disable_parallel_tool_use は auto と組み合わせると「最大 1 個」、any / tool と組み合わせると「ちょうど 1 個」になる。
背景・補足:どれを選ぶか
| やりたいこと | 指定 |
|---|---|
| 普通のエージェント |
auto(既定) |
| 分類・抽出など、必ず決まった形で受け取る |
tool + disable_parallel_tool_use
|
| 複数ツールのどれかには必ず振り分ける(ルーター) | any |
| ツール定義は残したまま、このターンは文章だけ欲しい | none |
none は地味に便利で、ループの上限に達したときに「ここまでの結果をまとめて」と最終ターンを打つ用途に使える。tools を外すと過去の tool_use ブロックとの整合性でエラーになることがあるので、定義は残して none にするのが安全だった。
また、強制しても input がスキーマに厳密に従う保証は別の話だ。型まで保証したい場合はツール定義に strict: true を付ける構造化出力側の機能と組み合わせる。
まとめ
-
tool_choiceはauto/any/tool/none。強制は「そのターンで必ず呼ぶ」という意味 - 強制は初回ターン限定にして
autoに戻す。付けっぱなしはループが終わらない - 拡張思考と強制は併用不可(400)。思考が要るなら
auto+ 明示的な指示で代替する - 強制時は前置きテキストが出ない。1 個に絞るなら
disable_parallel_tool_useを付ける - 最終ターンのまとめは
toolsを消さずにnoneを使う