Stop-Gründe und Fallback
Erfahre, was jeder stop_reason-Wert bedeutet und wie du Abschneidung, Tool-Nutzung, pausierte Turns und Ablehnungen in deiner Anwendung behandelst.
Jede Antwort der Messages API enthält ein Feld stop_reason, das dir mitteilt, warum Claude die Generierung beendet hat. Prüfe dieses Feld, um zu entscheiden, ob du die Antwort unverändert verwendest, die Konversation fortsetzt, es erneut versuchst oder auf ein anderes Modell zurückfällst.
Das vollständige Antwortschema findest du in der Messages-API-Referenz.
Kurzübersicht
| Wert | Wann er auftritt | Was zu tun ist |
|---|---|---|
end_turn | Claude hat seine Antwort auf natürliche Weise beendet. | Verwende die Antwort. |
max_tokens | Die Antwort hat dein max_tokens-Limit erreicht. | Erhöhe max_tokens oder setze die Antwort fort. |
stop_sequence | Claude hat eine deiner stop_sequences ausgegeben. | Lies stop_sequence, um zu sehen, welche ausgelöst wurde. |
tool_use | Claude ruft ein Tool auf. | Führe das Tool aus und gib das Ergebnis zurück. Ein Server-Tool-Aufruf, dem sein Ergebnisblock noch fehlt, wird in einer späteren Antwort abgeschlossen. |
pause_turn | Eine Server-Tool-Schleife hat ihr Iterationslimit erreicht. | Sende den Assistant-Inhalt zurück, um fortzufahren. |
refusal | Claude hat eine Antwort abgelehnt. | Lies stop_details und versuche es erneut mit einem Fallback-Modell. |
model_context_window_exceeded | Die Antwort hat das Kontextfenster des Modells gefüllt. | Behandle die Antwort als abgeschnitten. |
Das Feld stop_reason
Das Feld stop_reason ist Teil jeder erfolgreichen Antwort der Messages API. Anders als Fehler, die auf Probleme bei der Verarbeitung deiner Anfrage hinweisen, teilt dir stop_reason mit, warum Claude die Generierung seiner Antwort abgeschlossen hat.
{
"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
}
}Werte von stop_reason
end_turn
Der häufigste Stop-Grund. Zeigt an, dass Claude seine Antwort auf natürliche Weise beendet hat.
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# Verarbeite die vollständige Antwort
for block in response.content:
if block.type == "text":
print(block.text)Manchmal gibt Claude eine leere Antwort (genau 2–3 Token ohne Inhalt) mit stop_reason: "end_turn" zurück. Dies tritt typischerweise auf, wenn Claude interpretiert, dass der Assistant-Turn abgeschlossen ist, insbesondere nach Tool-Ergebnissen.
Häufige Ursachen:
- Hinzufügen von Textblöcken unmittelbar nach Tool-Ergebnissen (Claude lernt zu erwarten, dass der Nutzer nach Tool-Ergebnissen immer Text einfügt, und beendet daher seinen Turn, um dem Muster zu folgen)
- Zurücksenden von Claudes abgeschlossener Antwort, ohne etwas hinzuzufügen (Claude hat bereits festgestellt, dass es fertig ist, also bleibt es fertig)
So verhinderst du leere Antworten:
# FALSCH: Text direkt nach tool_result hinzufügen
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
},
],
},
]
# RICHTIG: Tool-Ergebnisse direkt ohne zusätzlichen Text senden
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
]Wenn du nach der Korrektur der Nachrichtenstruktur weiterhin leere Antworten erhältst, füge einen Fortsetzungs-Prompt in einer neuen User-Nachricht hinzu, anstatt es mit der leeren Antwort erneut zu versuchen:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
# Prüfe, ob die Antwort leer ist
if response.stop_reason == "end_turn" and not response.content:
# FALSCH: Wiederhole die Anfrage nicht einfach mit der leeren Antwort
# Das funktioniert nicht, weil Claude bereits entschieden hat, dass es fertig ist
# RICHTIG: Füge einen Fortsetzungs-Prompt in einer NEUEN User-Nachricht hinzu
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
return responseBest Practices:
- Füge niemals Textblöcke unmittelbar nach Tool-Ergebnissen hinzu: Dies bringt Claude bei, nach jeder Tool-Nutzung eine Nutzereingabe zu erwarten.
- Versuche leere Antworten nicht ohne Änderung erneut: Das Zurücksenden der leeren Antwort hilft nicht.
- Verwende Fortsetzungs-Prompts als letztes Mittel: Nur wenn diese Korrekturen das Problem nicht lösen.
max_tokens
Claude hat gestoppt, weil es das in deiner Anfrage angegebene max_tokens-Limit erreicht hat.
client = anthropic.Anthropic()
# Anfrage mit begrenzten Tokens
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=10,
messages=[{"role": "user", "content": "Explain quantum physics"}],
)
if response.stop_reason == "max_tokens":
# Antwort wurde abgeschnitten
print("Response was cut off at token limit")
# Erwäge eine weitere Anfrage, um fortzufahrenWenn Claudes Antwort abgeschnitten wird, weil sie das max_tokens-Limit erreicht hat, und die abgeschnittene Antwort einen unvollständigen Tool-Use-Block enthält, musst du die Anfrage mit einem höheren max_tokens-Wert erneut senden, um die vollständige Tool-Nutzung zu erhalten.
# Prüfe, ob die Antwort während der Tool-Nutzung abgeschnitten wurde
if response.stop_reason == "max_tokens":
# Prüfe, ob der letzte Content-Block ein unvollständiges tool_use ist
last_block = response.content[-1]
if last_block.type == "tool_use":
# Sende die Anfrage mit höherem max_tokens
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)stop_sequence
Claude ist auf eine deiner benutzerdefinierten Stop-Sequenzen gestoßen.
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-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 ruft ein Tool auf und erwartet, dass du es ausführst.
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-5",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What is the weather in San Francisco?"}],
)
if response.stop_reason == "tool_use":
# Tool extrahieren und ausführen
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
# Ergebnis für die finale Antwort an Claude zurückgebenEine tool_use-Antwort kann auch einen server_tool_use-Block enthalten, dessen id keinen passenden Ergebnisblock hat. Dieser Server-Tool-Aufruf ist nicht abgeschlossen, und diese Antwort enthält sein Ergebnis nicht. Im häufigsten Fall ruft Claude ein Server-Tool und eines deiner Client-Tools in derselben Gruppe paralleler Tool-Aufrufe auf: Die API kehrt zurück, ohne das Server-Tool auszuführen, damit du zuerst die Client-Tools ausführen kannst. Es gibt keine andere Markierung für diesen Zustand; erkenne ihn, indem du für die id jedes server_tool_use- oder mcp_tool_use-Blocks prüfst, ob ein passender Ergebnisblock vorhanden ist.
{
"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" }
}
]
}Die Fortsetzung ist eine User-Nachricht aus tool_result-Blöcken, einer für jeden tool_use-Block in der Antwort (siehe Tool-Aufrufe behandeln), mit zwei zusätzlichen Regeln: Diese Nachricht darf nichts außer den tool_result-Blöcken enthalten, und die Anfrage muss dasselbe tools-Array beibehalten. Eine Fortsetzungsanfrage, die das wartende Server-Tool nicht mehr definiert, schlägt mit einem 400 fehl, dessen Meldung mit but no `web_search` tool was provided endet. Die API hängt deine Ergebnisse an den noch offenen Assistant-Turn an, führt das aufgeschobene Server-Tool aus (bei pausierter Code-Ausführung setzt sie diese fort) und setzt den Turn fort. Bei einem Server-Tool, das Claude direkt aufgerufen hat, beginnt der content der nächsten Antwort mit dem Ergebnisblock, der die server_tool_use-id der vorherigen Antwort beantwortet.
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
}
]
}Wenn du in dieser User-Nachricht nach den tool_result-Blöcken irgendetwas hinzufügst, etwa Text, beendet das den Assistant-Turn; bei einem Server-Tool, das Claude direkt aufgerufen hat, schlägt die Anfrage dann mit einem 400 invalid_request_error fehl, der das nicht aufgelöste Server-Tool benennt:
`web_search` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_search_tool_result` blockDas Weglassen eines tool_result oder das Platzieren eines solchen nach anderem Inhalt schlägt stattdessen früher mit dem Standardfehler tool_use ids were found without tool_result blocks immediately after fehl. Um Claude weitere Eingaben zu geben, sende sie als separate User-Nachricht, nachdem der Turn abgeschlossen ist.
pause_turn
Wird zurückgegeben, wenn die serverseitige Sampling-Schleife ihr Iterationslimit erreicht, während sie Server-Tools wie die Websuche ausführt. Das Standardlimit beträgt 10 Iterationen pro Anfrage.
Wenn dies geschieht, kann die Antwort einen server_tool_use-Block ohne zugehörigen Ergebnisblock enthalten. Damit Claude die Verarbeitung abschließen kann, setze die Konversation fort, indem du die Antwort unverändert zurücksendest. Eine Antwort, die einen Client-tool_use-Block auf dich warten lässt, hat niemals den stop_reason pause_turn: Wenn Claude stoppt, um deine Tools aufzurufen, ist stop_reason tool_use, und du setzt sie fort, indem du die Client-tool_result-Blöcke anstelle der Antwort selbst sendest.
response = client.messages.create(
model="claude-opus-5-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":
# Setze die Unterhaltung fort, indem du die Antwort zurücksendest
messages = [
{"role": "user", "content": "Search for latest AI news"},
{"role": "assistant", "content": response.content},
]
continuation = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)refusal
Claude hat es abgelehnt, eine Antwort zu generieren. Sicherheitsklassifikatoren geben diesen Stop-Grund als normale HTTP-200-Antwort zurück, nicht als Fehler.
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "[Unsafe request]"}],
)
if response.stop_reason == "refusal":
# Claude hat eine Antwort abgelehnt
print("Claude was unable to process this request")
# Formuliere die Anfrage ggf. um oder passe sie anBei einer Ablehnung identifiziert das Objekt stop_details die Richtlinienkategorie, die sie ausgelöst hat. Die Kategorien und die vollständige Form der Ablehnungsantwort werden unter Ablehnungen und Fallback behandelt. stop_details ist für alle Stop-Gründe außer refusal null.
Eine abgelehnte Anfrage an Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5 oder Claude Sonnet 5.5 kann in der Regel durch einen erneuten Versuch mit einem anderen Claude-Modell bedient werden. Ablehnungen und Fallback zeigt, wie du diesen erneuten Versuch einrichtest, serverseitig oder in deinem Client. Wenn du den erneuten Versuch ausgehend von Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5, Claude Opus 5 oder Claude Sonnet 5.5 selbst aufbaust, erklärt Fallback-Gutschrift, wie du vermeidest, die Prompt-Cache-Kosten doppelt zu bezahlen.
model_context_window_exceeded
Claude hat gestoppt, weil es das Limit des „context window“ (Kontextfenster) des Modells erreicht hat. Dadurch kannst du die maximal möglichen Token anfordern, ohne die genaue Eingabegröße zu kennen.
# Anfrage mit maximaler Token-Anzahl, um so viel wie möglich zu erhalten
response = client.beta.messages.create(
model="claude-opus-5-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":
# Antwort hat das Limit des Kontextfensters vor max_tokens erreicht
print("Response reached model's context window limit")
# Die Antwort ist weiterhin gültig, wurde aber durch das Kontextfenster begrenztBest Practices für den Umgang mit Stop-Gründen
Prüfe immer stop_reason
Mache es dir zur Gewohnheit, den stop_reason in deiner Antwortverarbeitungslogik zu prüfen:
def handle_response(response):
match response.stop_reason:
case "tool_use":
return handle_tool_use(response)
case "max_tokens":
return handle_truncation(response)
case "model_context_window_exceeded":
return handle_context_limit(response)
case "pause_turn":
return handle_pause(response)
case "refusal":
return handle_refusal(response)
case _:
# Behandle end_turn und andere Fälle
return next(
(block.text for block in response.content if block.type == "text"),
"",
)Behandle abgeschnittene Antworten elegant
Wenn eine Antwort aufgrund von Token-Limits oder des Kontextfensters abgeschnitten wird, hänge einen Hinweis an, damit der Leser weiß, dass die Ausgabe unvollständig ist. Um stattdessen die Generierung dort fortzusetzen, wo die Antwort aufgehört hat, siehe Vollständige Antworten sicherstellen.
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 textImplementiere Retry-Logik für pause_turn
Bei der Verwendung von Server-Tools kann die API pause_turn zurückgeben, wenn die serverseitige Sampling-Schleife ihr Iterationslimit (standardmäßig 10) erreicht. Behandle dies, indem du die Konversation fortsetzt:
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-5",
max_tokens=4096,
messages=messages,
tools=tools,
)
if response.stop_reason != "pause_turn":
# Claude ist mit der Verarbeitung fertig – gib die finale Antwort zurück
return response
# pause_turn: Ersetze die gesamte Nachrichtenliste, damit die Rollen weiter abwechseln
messages = [
{"role": "user", "content": user_query},
{"role": "assistant", "content": response.content},
]
# Maximale Anzahl an Fortsetzungen erreicht – gib die letzte Antwort zurück
return responseStop-Gründe vs. Fehler
Es ist wichtig, zwischen stop_reason-Werten und tatsächlichen Fehlern zu unterscheiden:
Stop-Gründe (erfolgreiche Antworten)
- Teil des Antwort-Bodys
- Zeigen an, warum die Generierung normal gestoppt hat
- Antwort enthält gültigen Inhalt
Fehler (fehlgeschlagene Anfragen)
- HTTP-Statuscodes 4xx oder 5xx
- Zeigen Fehler bei der Anfrageverarbeitung an
- Antwort enthält Fehlerdetails
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# Erfolgreiche Antwort mit stop_reason verarbeiten
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIStatusError as e:
# Tatsächliche Fehler behandeln
match e.status_code:
case 429:
print("Rate limit exceeded")
case 500:
print("Server error")Überlegungen zum Streaming
Bei der Verwendung von Streaming ist stop_reason:
nullim initialenmessage_start-Event- Im
message_delta-Event enthalten - In keinem anderen Event enthalten
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-5-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}")Häufige Muster
Umgang mit Tool-Use-Workflows
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=messages,
tools=tools,
)
if response.stop_reason == "tool_use":
# Tools ausführen und fortfahren
tool_results = execute_tools(response.content)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# Endgültige Antwort
return responseVollständige Antworten sicherstellen
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-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
# Mach dort weiter, wo es aufgehört hat
messages = [
{"role": "user", "content": prompt},
{"role": "assistant", "content": full_response},
{"role": "user", "content": "Please continue from where you left off."},
]
return full_responseMaximale Token erhalten, ohne die Eingabegröße zu kennen
Mit dem Stop-Grund model_context_window_exceeded kannst du die maximal möglichen Token anfordern, ohne die Eingabegröße zu berechnen:
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-5",
messages=[{"role": "user", "content": prompt}],
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k
)
match response.stop_reason:
case "model_context_window_exceeded":
# Maximal mögliche Anzahl an Tokens für die Eingabegröße erhalten
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
case "max_tokens":
# Genau die angeforderte Anzahl an Tokens erhalten
print(
f"Generated {response.usage.output_tokens} tokens (max_tokens reached)"
)
case _:
# Natürlicher Abschluss
print(
f"Generated {response.usage.output_tokens} tokens (natural completion)"
)
return next((block.text for block in response.content if block.type == "text"), "")Nächste Schritte
Versuche abgelehnte Anfragen auf einem Fallback-Modell erneut, serverseitig oder in deinem Client.
Lass das SDK die tool_use-Schleife, die Ergebnisformatierung und erneute Versuche für dich verwalten.
Lies stop_reason beim Streaming aus dem message_delta-Event.
Behandle 4xx- und 5xx-HTTP-Fehler, die sich von Stop-Gründen unterscheiden.
Was this page helpful?