停止理由とフォールバック
各 stop_reason の値が何を意味するのか、そしてアプリケーションで切り捨て、ツール使用、一時停止されたターン、拒否をどのように処理するかを学びます。
すべての Messages API レスポンスには、Claude が生成を停止した理由を示す stop_reason フィールドが含まれています。このフィールドを確認して、レスポンスをそのまま使用するか、会話を続けるか、リトライするか、別のモデルにフォールバックするかを判断してください。
完全なレスポンススキーマについては、Messages API リファレンスを参照してください。
クイックリファレンス
| 値 | 発生するタイミング | 対処方法 |
|---|---|---|
end_turn | Claude が自然にレスポンスを終了しました。 | レスポンスを使用します。 |
max_tokens | レスポンスが max_tokens の上限に達しました。 | max_tokens を引き上げるか、レスポンスを継続します。 |
stop_sequence | Claude が stop_sequences のいずれかを出力しました。 | stop_sequence を読み取り、どれが発火したかを確認します。 |
tool_use | Claude がツールを呼び出しています。 | ツールを実行して結果を返します。結果ブロックがまだないサーバーツール呼び出しは、後続のレスポンスで完了します。 |
pause_turn | サーバーツールのループが反復回数の上限に達しました。 | アシスタントのコンテンツを送り返して継続します。 |
refusal | Claude が応答を拒否しました。 | stop_details を読み取り、フォールバックモデルでリトライします。 |
model_context_window_exceeded | レスポンスがモデルのコンテキストウィンドウを使い切りました。 | レスポンスを切り捨てられたものとして扱います。 |
stop_reason フィールド
stop_reason フィールドは、成功したすべての Messages API レスポンスに含まれます。リクエストの処理における失敗を示すエラーとは異なり、stop_reason は Claude がレスポンスの生成を完了した理由を示します。
{
"id": "msg_01234",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Here's the answer to your question..."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"stop_details": null,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}停止理由の値
end_turn
最も一般的な停止理由です。Claude が自然にレスポンスを終了したことを示します。
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# 完全なレスポンスを処理する
for block in response.content:
if block.type == "text":
print(block.text)Claude が stop_reason: "end_turn" とともに空のレスポンス(コンテンツのない、ちょうど 2〜3 トークン)を返すことがあります。これは通常、特にツール結果の後に、Claude がアシスタントのターンが完了したと解釈した場合に発生します。
一般的な原因:
- ツール結果の直後にテキストブロックを追加している(Claude はユーザーがツール結果の後に常にテキストを挿入することを期待するようになり、そのパターンに従うためにターンを終了します)
- Claude の完了したレスポンスを何も追加せずに送り返している(Claude はすでに完了したと判断しているため、完了したままになります)
空のレスポンスを防ぐ方法:
# 誤り: tool_resultの直後にテキストを追加している
messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678},
}
],
},
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"},
{
"type": "text",
"text": "Here's the result", # Don't add text after tool_result
},
],
},
]
# 正しい: 追加のテキストなしでツール結果を直接送信する
messages = [
{"role": "user", "content": "Calculate the sum of 1234 and 5678"},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "calculator",
"input": {"operation": "add", "a": 1234, "b": 5678},
}
],
},
{
"role": "user",
"content": [
{"type": "tool_result", "tool_use_id": "toolu_123", "content": "6912"}
],
}, # Just the tool_result, no additional text
]メッセージ構造を修正しても空のレスポンスが返される場合は、空のレスポンスでリトライするのではなく、新しいユーザーメッセージに継続プロンプトを追加してください:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-5", max_tokens=1024, messages=messages
)
# レスポンスが空かどうかを確認
if response.stop_reason == "end_turn" and not response.content:
# 誤り: 空のレスポンスのまま単純にリトライしない
# Claudeはすでに完了と判断しているため、これは機能しません
# 正しい: 新しいユーザーメッセージに継続プロンプトを追加する
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-opus-5", max_tokens=1024, messages=messages
)
return responseベストプラクティス:
- ツール結果の直後にテキストブロックを追加しない: これにより、Claude はすべてのツール使用の後にユーザー入力を期待するようになります。
- 空のレスポンスを変更せずにリトライしない: 空のレスポンスを送り返しても効果はありません。
- 継続プロンプトは最後の手段として使用する: これらの修正で問題が解決しない場合にのみ使用してください。
max_tokens
リクエストで指定した max_tokens の上限に達したため、Claude が停止しました。
client = anthropic.Anthropic()
# トークン数を制限したリクエスト
response = client.messages.create(
model="claude-opus-5",
max_tokens=10,
messages=[{"role": "user", "content": "Explain quantum physics"}],
)
if response.stop_reason == "max_tokens":
# レスポンスが切り捨てられました
print("Response was cut off at token limit")
# 続行するには別のリクエストを送信することを検討してくださいClaude のレスポンスが max_tokens の上限に達したために途中で切れ、切り捨てられたレスポンスに不完全なツール使用ブロックが含まれている場合は、完全なツール使用を取得するために、より大きな max_tokens の値でリクエストをリトライする必要があります。
# ツール使用中にレスポンスが切り捨てられたかどうかを確認
if response.stop_reason == "max_tokens":
# 最後のコンテンツブロックが不完全なtool_useかどうかを確認
last_block = response.content[-1]
if last_block.type == "tool_use":
# より大きなmax_tokensでリクエストを送信
response = client.messages.create(
model="claude-opus-5",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)stop_sequence
Claude がカスタム停止シーケンスのいずれかに遭遇しました。
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
stop_sequences=["END", "STOP"],
messages=[{"role": "user", "content": "Generate text until you say END"}],
)
if response.stop_reason == "stop_sequence":
print(f"Stopped at sequence: {response.stop_sequence}")tool_use
Claude がツールを呼び出しており、あなたがそれを実行することを期待しています。
client = anthropic.Anthropic()
weather_tool = {
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and state"},
},
"required": ["location"],
},
}
def execute_tool(name, tool_input):
"""Execute a tool and return the result."""
return f"Weather in {tool_input.get('location', 'unknown')}: 72°F"
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What is the weather in San Francisco?"}],
)
if response.stop_reason == "tool_use":
# ツールを抽出して実行
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
# 最終応答のために結果をClaudeに返すtool_use レスポンスには、対応する結果ブロックのない id を持つ server_tool_use ブロックが含まれることもあります。そのサーバーツール呼び出しは完了しておらず、このレスポンスにはその結果が含まれていません。一般的なケースでは、Claude が同じ並列ツール呼び出しのグループ内でサーバーツールとクライアントツールの 1 つを呼び出します。API はサーバーツールを実行せずに返すため、先にクライアントツールを実行できます。この状態を示す他のマーカーはありません。各 server_tool_use または mcp_tool_use ブロックの id に対応する結果ブロックがあるかどうかを確認して検出してください。
{
"stop_reason": "tool_use",
"content": [
{
"type": "server_tool_use",
"id": "srvtoolu_01HxbWnMRmbWyMfUtJKC45rA",
"name": "web_search",
"input": { "query": "example article" }
},
{
"type": "tool_use",
"id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"name": "run_command",
"input": { "command": "uname -a" }
}
]
}継続は、レスポンス内のすべての tool_use ブロックに対して 1 つずつの tool_result ブロックからなるユーザーメッセージです(ツール呼び出しの処理を参照)。ただし、2 つの追加ルールがあります。そのメッセージには tool_result ブロック以外を含めてはならず、リクエストは同じ tools 配列を保持しなければなりません。待機中のサーバーツールを定義しなくなった再開リクエストは、メッセージが but no `web_search` tool was provided で終わる 400 で失敗します。API は結果をまだ開いているアシスタントのターンに付加し、遅延されたサーバーツールを実行し(一時停止されたコード実行の場合は再開し)、ターンを継続します。Claude が直接呼び出したサーバーツールの場合、次のレスポンスの content は、前のレスポンスの server_tool_use の id に応答する結果ブロックから始まります。
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
}
]
}そのユーザーメッセージの tool_result ブロックの後にテキストなど何かを追加すると、アシスタントのターンが終了します。Claude が直接呼び出したサーバーツールの場合、リクエストは未解決のサーバーツールを指す 400 invalid_request_error で失敗します:
`web_search` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_search_tool_result` blocktool_result を省略したり、他のコンテンツの後に配置したりすると、代わりに標準の tool_use ids were found without tool_result blocks immediately after エラーでより早い段階で失敗します。Claude にさらに入力を与えるには、ターンが完了した後に別のユーザーメッセージとして送信してください。
pause_turn
ウェブ検索などのサーバーツールの実行中に、サーバー側のサンプリングループが反復回数の上限に達した場合に返されます。デフォルトの上限はリクエストあたり 10 回の反復です。
これが発生した場合、レスポンスには対応する結果ブロックのない server_tool_use ブロックが含まれることがあります。Claude に処理を完了させるには、レスポンスをそのまま送り返して会話を継続してください。クライアントの tool_use ブロックがあなたの対応を待っている状態のレスポンスが pause_turn の stop_reason を持つことはありません。Claude があなたのツールを呼び出すために停止した場合、stop_reason は tool_use であり、レスポンス自体ではなくクライアントの tool_result ブロックを送信して継続します。
response = client.messages.create(
model="claude-opus-5",
max_tokens=4096,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
messages=[{"role": "user", "content": "Search for latest AI news"}],
)
if response.stop_reason == "pause_turn":
# レスポンスを送り返して会話を続ける
messages = [
{"role": "user", "content": "Search for latest AI news"},
{"role": "assistant", "content": response.content},
]
continuation = client.messages.create(
model="claude-opus-5",
max_tokens=4096,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)refusal
Claude がレスポンスの生成を拒否しました。安全性分類器はこの停止理由をエラーではなく、通常の HTTP 200 レスポンスとして返します。
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "[Unsafe request]"}],
)
if response.stop_reason == "refusal":
# Claudeが応答を拒否しました
print("Claude was unable to process this request")
# リクエストの言い換えや修正を検討してください拒否の場合、stop_details オブジェクトはそれをトリガーしたポリシーカテゴリを特定します。カテゴリと完全な拒否レスポンスの形については、拒否とフォールバックで説明しています。stop_details は refusal 以外のすべての停止理由では null です。
Claude Fable 5.1、Claude Fable 5、または Claude Opus 5 で拒否されたリクエストは、通常、別の Claude モデルでリトライすることで処理できます。拒否とフォールバックでは、サーバー側またはクライアントでそのリトライを設定する方法を示しています。Claude Fable 5.1、Claude Fable 5、または Claude Opus 5 からのリトライを自分で構築する場合は、フォールバッククレジットでプロンプトキャッシングのコストを二重に支払うことを避ける方法を説明しています。
model_context_window_exceeded
モデルのコンテキストウィンドウの上限に達したため、Claude が停止しました。これにより、正確な入力サイズを知らなくても、可能な最大トークン数をリクエストできます。
# 可能な限り多く取得するため最大トークン数でリクエスト
response = client.beta.messages.create(
model="claude-opus-5",
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
messages=[
{"role": "user", "content": "Large input that uses most of context window..."}
],
)
if response.stop_reason == "model_context_window_exceeded":
# レスポンスがmax_tokensより先にコンテキストウィンドウの上限に達しました
print("Response reached model's context window limit")
# レスポンスは有効ですが、コンテキストウィンドウによって制限されました停止理由を処理するためのベストプラクティス
常に stop_reason を確認する
レスポンス処理ロジックで stop_reason を確認することを習慣にしてください:
def handle_response(response):
if response.stop_reason == "tool_use":
return handle_tool_use(response)
elif response.stop_reason == "max_tokens":
return handle_truncation(response)
elif response.stop_reason == "model_context_window_exceeded":
return handle_context_limit(response)
elif response.stop_reason == "pause_turn":
return handle_pause(response)
elif response.stop_reason == "refusal":
return handle_refusal(response)
else:
# end_turnおよびその他のケースを処理
return next(
(block.text for block in response.content if block.type == "text"), ""
)切り捨てられたレスポンスを適切に処理する
トークン制限またはコンテキストウィンドウのためにレスポンスが切り捨てられた場合は、出力が不完全であることを読者がわかるように通知を追加してください。代わりにレスポンスが途切れた箇所から生成を継続するには、完全なレスポンスを確保するを参照してください。
def handle_truncated_response(response):
text = next((block.text for block in response.content if block.type == "text"), "")
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
if response.stop_reason == "max_tokens":
note = "[Response truncated due to max_tokens limit]"
else:
note = "[Response truncated due to context window limit]"
return f"{text}\n\n{note}"
return textpause_turn のリトライロジックを実装する
サーバーツールを使用する場合、サーバー側のサンプリングループが反復回数の上限(デフォルトは 10)に達すると、API は pause_turn を返すことがあります。会話を継続することでこれを処理してください:
def handle_server_tool_conversation(client, user_query, tools, max_continuations=5):
"""
Handle server tool conversations that may require multiple continuations.
The server runs a sampling loop when executing server tools. If the loop
reaches its iteration limit, the API returns pause_turn. Continue the
conversation by sending the response back to let Claude finish.
"""
messages = [{"role": "user", "content": user_query}]
for _ in range(max_continuations):
response = client.messages.create(
model="claude-opus-5", max_tokens=4096, messages=messages, tools=tools
)
if response.stop_reason != "pause_turn":
# Claudeの処理が完了 - 最終レスポンスを返す
return response
# pause_turn: ロールの交互性を保つためメッセージリスト全体を置き換える
messages = [
{"role": "user", "content": user_query},
{"role": "assistant", "content": response.content},
]
# 最大継続回数に到達 - 最後のレスポンスを返す
return response停止理由とエラーの違い
stop_reason の値と実際のエラーを区別することが重要です:
停止理由(成功したレスポンス)
- レスポンスボディの一部
- 生成が正常に停止した理由を示す
- レスポンスには有効なコンテンツが含まれる
エラー(失敗したリクエスト)
- HTTP ステータスコード 4xx または 5xx
- リクエスト処理の失敗を示す
- レスポンスにはエラーの詳細が含まれる
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# stop_reasonを含む正常なレスポンスを処理
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIStatusError as e:
# 実際のエラーを処理
if e.status_code == 429:
print("Rate limit exceeded")
elif e.status_code == 500:
print("Server error")ストリーミングに関する考慮事項
ストリーミングを使用する場合、stop_reason は次のようになります:
- 最初の
message_startイベントではnull message_deltaイベントで提供される- その他のイベントでは提供されない
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
) as stream:
for event in stream:
if event.type == "message_delta":
stop_reason = event.delta.stop_reason
if stop_reason:
print(f"Stream ended with: {stop_reason}")一般的なパターン
ツール使用ワークフローの処理
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-5", max_tokens=1024, messages=messages, tools=tools
)
if response.stop_reason == "tool_use":
# ツールを実行して続行
tool_results = execute_tools(response.content)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# 最終レスポンス
return response完全なレスポンスを確保する
def get_complete_response(client, prompt, max_attempts=3):
messages = [{"role": "user", "content": prompt}]
full_response = ""
for _ in range(max_attempts):
response = client.messages.create(
model="claude-opus-5", messages=messages, max_tokens=4096
)
full_response += next(
(block.text for block in response.content if block.type == "text"), ""
)
if response.stop_reason != "max_tokens":
break
# 中断したところから続行
messages = [
{"role": "user", "content": prompt},
{"role": "assistant", "content": full_response},
{"role": "user", "content": "Please continue from where you left off."},
]
return full_response入力サイズを知らずに最大トークン数を取得する
model_context_window_exceeded の停止理由を使用すると、入力サイズを計算せずに可能な最大トークン数をリクエストできます:
def get_max_possible_tokens(client, prompt):
"""
Get as many tokens as possible within the model's context window
without needing to calculate input token count
"""
response = client.beta.messages.create(
model="claude-opus-5",
messages=[{"role": "user", "content": prompt}],
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
)
if response.stop_reason == "model_context_window_exceeded":
# 入力サイズに対して可能な最大トークン数を取得
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
elif response.stop_reason == "max_tokens":
# 要求したトークン数を正確に取得
print(f"Generated {response.usage.output_tokens} tokens (max_tokens reached)")
else:
# 自然な完了
print(f"Generated {response.usage.output_tokens} tokens (natural completion)")
return next((block.text for block in response.content if block.type == "text"), "")次のステップ
拒否されたリクエストを、サーバー側またはクライアントでフォールバックモデルを使ってリトライします。
tool_use ループ、結果のフォーマット、リトライを SDK に任せます。
ストリーミング時に message_delta イベントから stop_reason を読み取ります。
停止理由とは異なる 4xx および 5xx の HTTP エラーを処理します。
Was this page helpful?