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— Claudeが自然に生成を終えた。最も一般的max_tokens— 指定したmax_tokensの上限に達したstop_sequence— 独自に指定した停止シーケンスが出力されたtool_use— Claudeがツールを呼び出しているpause_turn— サーバツールのループが反復回数の上限に達したrefusal— Claudeが応答を拒否したmodel_context_window_exceeded— モデルのコンテキストウィンドウを埋め切った
このうち、そのまま使ってよいのは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_exceeded — max_tokensに達する前にモデルのコンテキストウィンドウ上限に達した状態です。レスポンスは途中で切れているものとして扱います。
ストリーミングでの扱い
ストリーミングを使う場合、stop_reasonが入るイベントは決まっています。
message_startイベントではnullmessage_deltaイベントで実際の値が渡される- それ以外のイベントには含まれない
したがってストリーミングでの終了理由の判定は、message_deltaを見るという一点に集約されます。ストリームの途中で判定しようとしても値は入っていません。
実装上の注意点
公式ドキュメントが挙げるベストプラクティスを整理すると、次のようになります。
- レスポンス処理のロジックで常に
stop_reasonを確認する tool_resultブロックの後にテキストを追加しない。これをするとClaudeが「ツール使用のあとには必ずユーザー入力が来る」と学習してしまい、空の応答を返すようになります- サーバツールを使うエージェントループでは
pause_turnを処理する。レスポンスをメッセージ列に戻して再試行します stop_reasonとエラーを区別する。前者はHTTP 200の成功レスポンスの一部、後者はHTTP 4xx/5xx- 途中で切れたレスポンスは、その旨をユーザーに伝えるか、新しいリクエストで生成を続ける
2つ目は見落とされやすく、しかも影響が分かりにくい形で出ます。ツール結果を返すメッセージにはtool_resultブロックだけを入れ、「この結果を踏まえて答えてください」といった補足テキストを後ろに足さないでください。
この記事は公式ドキュメントに基づく非公式の日本語解説です。仕様は更新されることがあるため、実装前に公式ドキュメントで最新の内容をご確認ください。