stop_reasonの扱い方

Claude API エンジニアリング 7分で読めます

Messages APIのレスポンスには必ずstop_reasonが含まれ、Claudeがなぜ生成を止めたのかを示します。これはエラーではなく成功レスポンスの一部で、返ってきた内容をそのまま使ってよいのか、続きを取りに行くのか、再試行するのかを判断するための情報です。

stop_reasonとは

stop_reasonとエラーは別のものです。エラーはHTTPの4xxや5xxで返りますが、stop_reasonはHTTP 200の成功レスポンスの中に入っています。つまり「リクエストは成功したが、生成がどこで終わったか」を伝えるフィールドです。

公式ドキュメントが挙げるベストプラクティスの筆頭は、「レスポンス処理のロジックで常にstop_reasonを確認すること」です。確認せずにcontent[0].textを読むだけの実装は、途中で切れたレスポンスを完成したものとして扱ってしまいます。

7つの終了理由

終了理由は7種類あります。

このうち、そのまま使ってよいのはend_turnだけです。残りの6つはいずれも、何らかの追加処理が必要な状態を示しています。

理由別の対処

end_turn — レスポンスをそのまま使います。

if response.stop_reason == "end_turn":
    print(response.content[0].text)

max_tokens — 途中で切れています。max_tokensを上げるか、続きを別のリクエストで取りに行きます。とくに注意すべきなのは、最後のコンテンツブロックが不完全なtool_useだった場合です。この場合はより大きなmax_tokensで再試行して、完全なツール呼び出しを取得する必要があります。

stop_sequence — どのシーケンスで止まったかはstop_sequenceフィールドで確認できます。

if response.stop_reason == "stop_sequence":
    print(f"Stopped at sequence: {response.stop_sequence}")

tool_use — ツールを実行し、結果を返します。contentの中のtool_useブロックを取り出して実行し、次のユーザーメッセージでtool_resultブロックとして返す流れです。多くの場合はツールランナーを使って自動化するほうが確実です。

pause_turn — サーバツールを使っていて反復上限に達した状態です。レスポンスをそのままメッセージ列に戻して継続します。

if response.stop_reason == "pause_turn":
    messages = [
        {"role": "user", "content": user_query},
        {"role": "assistant", "content": response.content}
    ]
    continuation = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=4096,
        messages=messages,
        tools=tools
    )

refusal — Claudeが応答を拒否した状態です。stop_detailsでポリシー上の分類を確認できます。表現を変える、リクエストを修正する、フォールバックのモデルで再試行する、といった対応が考えられます。

model_context_window_exceededmax_tokensに達する前にモデルのコンテキストウィンドウ上限に達した状態です。レスポンスは途中で切れているものとして扱います。

ストリーミングでの扱い

ストリーミングを使う場合、stop_reasonが入るイベントは決まっています。

したがってストリーミングでの終了理由の判定は、message_deltaを見るという一点に集約されます。ストリームの途中で判定しようとしても値は入っていません。

実装上の注意点

公式ドキュメントが挙げるベストプラクティスを整理すると、次のようになります。

  1. レスポンス処理のロジックで常にstop_reasonを確認する
  2. tool_resultブロックの後にテキストを追加しない。これをするとClaudeが「ツール使用のあとには必ずユーザー入力が来る」と学習してしまい、空の応答を返すようになります
  3. サーバツールを使うエージェントループではpause_turnを処理する。レスポンスをメッセージ列に戻して再試行します
  4. stop_reasonとエラーを区別する。前者はHTTP 200の成功レスポンスの一部、後者はHTTP 4xx/5xx
  5. 途中で切れたレスポンスは、その旨をユーザーに伝えるか、新しいリクエストで生成を続ける

2つ目は見落とされやすく、しかも影響が分かりにくい形で出ます。ツール結果を返すメッセージにはtool_resultブロックだけを入れ、「この結果を踏まえて答えてください」といった補足テキストを後ろに足さないでください。

この記事は公式ドキュメントに基づく非公式の日本語解説です。仕様は更新されることがあるため、実装前に公式ドキュメントで最新の内容をご確認ください。