Claude APIのツール呼び出しの流れ:tool_useから結果の返却までのループ設計
AIを使いこなすClaudeで執筆し、GPTで自動校閲・修正しています。

結論から言うと、Claude APIのツール呼び出しは「モデルが tool_use で依頼する → 自分のコードが実行する → tool_result で返す」を、stop_reason が tool_use である間くり返す往復です。モデル自身は何も実行しません。ループを止める条件は公式に示されている stop_reason の分岐で決まりますが、回数の上限は自分で設けるものです。この記事では、往復の手順図と、回数上限つきループの擬似コードを示します。 対象は、自前で関数(クライアントツール)を定義して呼ばせる場合です。確認した資料は、Claude Platform Docsの Tool use with Claude、How tool use works、Handle tool calls、Stop reasonsです(2026-10-27に閲覧。ページの更新日は確認できていません)。仕様は変わり得るので、実装前に最新の記述を見てください。
往復の手順図
次の図は、公式の説明を私が整理した説明用のものです。
[あなたのアプリ] [Claude API]
1. messages + tools を送信 ───────────▶
◀─────────── 2. stop_reason: "tool_use"
content に tool_use ブロック
(id / name / input)
3. name で関数を選び、input で実行
4. 直前の assistant 応答をそのまま履歴に追加
5. user メッセージに tool_result を入れて送信
(tool_use_id = 手順2の id) ───────────▶
◀─────────── 6. stop_reason を確認
├ "tool_use" → 手順3へ戻る
└ それ以外 → ループ終了ポイントは3つあります。
- tool_use ブロックには id、name、input が入っています。返す tool_result の tool_use_id には、この id を使います。
- 履歴には、assistantの応答(content 全体)を残したうえで、次のuserメッセージとして結果を送ります。APIには tool や function といった専用のroleはなく、user と assistant のメッセージの中にブロックとして入ります。
- 並列呼び出しで tool_use が複数あった場合は、すべてに対する tool_result を同じuserメッセージにまとめます。
結果を返すときの守るべき形式
公式が明記している制約は次のとおりです。
- tool_result は、対応する tool_use を含むassistantメッセージの直後のuserメッセージに入れます。間に別のメッセージを挟めません。
- そのuserメッセージでは、tool_result ブロックを先頭に置き、テキストを足すなら後ろです。テキストが前にあると400エラーになると説明されています。
- 実行に失敗したときは、エラー内容を content に入れ、is_error: true を付けて返します。「failed」だけでなく、何が起きたか・次に何を試すべきかまで書くよう推奨されています。
- 引数不足などで呼び出しが不正だった場合も、is_error つきの tool_result で返せます。公式には、Claudeが2〜3回修正して再試行してから謝罪する、とあります。
ループを止める条件を `stop_reason` で決める
公式の基本形は「stop_reason が tool_use の間だけ続ける」です。それ以外の end_turn、max_tokens、stop_sequence、refusal では抜けます。実装では、次のように振り分けると迷いません。
- tool_use、意味:ツール実行の依頼、ループでの扱い:実行して tool_result を返し、続行
- end_turn、意味:通常の完了、ループでの扱い:最終回答として返して終了
- max_tokens、意味:出力上限に到達、ループでの扱い:max_tokens を上げるか、応答を継続する。未完成の tool_use がある場合は、そのブロックを実行せず、履歴を変えずに再試行する
- pause_turn、意味:サーバーツールのループが反復上限に達した、ループでの扱い:assistantの応答を履歴に入れて再送し、続きを実行
- refusal、意味:応答の拒否、ループでの扱い:終了し、stop_details を見て対処
- model_context_window_exceeded、意味:コンテキストの上限に到達、ループでの扱い:切れたものとして扱う
pause_turn は、Anthropic側で動くサーバーツール(web_searchなど)を使うときに出ます。自前の関数だけなら通常は気にしなくて構いませんが、混在させるなら分岐に入れておきます。また、サーバーツールとクライアントツールが同じ並列呼び出しに含まれると、tool_use のまま返ることがあります。その場合は、自分が実行したクライアントツールの tool_result だけを、テキストを添えずに送り、tools 配列も変えません。
回数上限つきループの擬似コード
公式の基本形には、回数上限は含まれていません。上限は、モデルが同じ呼び出しをくり返したり、結果が毎回「見つかりません」だったりしたときに、費用が膨らむのを防ぐための編集上の提案です。以下は説明用の擬似コードで、実行も検証もしていません。call_claude、TOOLS は自分の実装に置き換えてください。
MAX_TURNS = 8 # 往復の上限(用途に合わせて決める)
MAX_RESULT_CHARS = 4000
def run_agent(user_text):
messages = [{"role": "user", "content": user_text}]
max_tokens, retried = 1024, False
seen = set() # 同一呼び出しの検出用
for turn in range(MAX_TURNS):
resp = call_claude(messages, max_tokens) # tools付きで送る
if resp.stop_reason == "max_tokens":
# 切れた応答は履歴に追加せず、必要なら上限を上げて再試行
if not retried:
max_tokens, retried = max_tokens * 4, True
continue # 履歴は変えず再試行
return stop("truncated", resp)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
return final_text(resp)
if resp.stop_reason == "pause_turn":
# サーバーツールの続き。クライアントのtool_useが未処理でないこと
continue # assistant応答を履歴に含めて再送
if resp.stop_reason != "tool_use":
return stop(resp.stop_reason, resp) # refusal など
results = []
for b in resp.content:
if b.type != "tool_use":
continue
key = (b.name, json_dumps(b.input, sort_keys=True))
if b.name not in TOOLS:
out, err = "Unknown tool: " + b.name, True
elif key in seen:
out, err = "Same call already made. Use another approach.", True
else:
seen.add(key)
try:
out, err = str(TOOLS[b.name](**b.input)), False
except Exception as e:
out, err = f"{type(e).__name__}: {e}", True
results.append({"type": "tool_result", "tool_use_id": b.id,
"content": out[:MAX_RESULT_CHARS], "is_error": err})
messages.append({"role": "user", "content": results}) # resultのみ
return stop("max_turns_reached", messages) # 上限到達: 人かアプリ側に返す設計上の要点は次のとおりです。
- tool_use ブロックごとに必ず tool_result を作ります。 未知のツール名や実行拒否でも、is_error: true で返します。公式にも、実行しない呼び出しにも is_error つきの結果を返すとあります。
- max_tokens の再試行は履歴を変えません。 切れた tool_use は不完全なので、assistantとして追加する前に分岐しています。
- tool_result の本文は長さを切ります。 tool_tokens は入力トークンとして課金に含まれます。長いログをそのまま返さない方が安全です。
- 上限到達時は、未回答の tool_use が履歴の最後に残ります。 そのまま会話を再開するには、対応する tool_result が必要です。再開しない前提なら、失敗として記録して終えるのが単純です。
外部由来の内容は信頼しない
Webページ、メール、アップロード、外部APIなどから取った内容を返すと、そこに紛れた指示でモデルが誘導される(間接プロンプトインジェクション)可能性があると、公式は警告しています。信頼できない内容は、system や通常のuserテキストではなく、tool_result の中に留めるよう案内されています。加えて、メール送信やファイル削除のような副作用のあるツールは、ループの外で人の承認を挟む設計にしておくと、回数上限だけでは防げない事故を抑えられます。これは編集上の提案です。
実装前の確認リスト
- 履歴に、assistantの content 全体を欠落なく入れているか。
- 並列の tool_use すべてに、同じ tool_use_id の tool_result を返しているか。
- tool_result がuserメッセージの先頭にあり、テキストが後ろか。
- end_turn 以外の stop_reason(特に max_tokens、refusal)に行き先があるか。
- 往復の上限と、上限到達時の挙動を決めたか。
SDKの「tool runner」を使えば、このループ・結果の整形・再試行をSDKが自動で処理すると公式にあります。細かい制御(上限、承認、ログ)が不要なら、自前実装より先に検討してください。

