Wenn du eine Anfrage an die Messages API stellst, enthält Claudes Antwort ein stop_reason-Feld, das angibt, warum das Modell die Generierung seiner Antwort beendet hat. Das Verständnis dieser Werte ist entscheidend für die Entwicklung robuster Anwendungen, die verschiedene Antworttypen angemessen behandeln.
Details zu stop_reason in der API-Antwort findest du in der Messages API-Referenz.
Das stop_reason-Feld ist Teil jeder erfolgreichen Messages API-Antwort. Im Gegensatz zu Fehlern, 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,
"usage": {
"input_tokens": 100,
"output_tokens": 50
}
}Der häufigste Stop-Grund. Zeigt an, dass Claude seine Antwort auf natürliche Weise beendet hat.
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
if response.stop_reason == "end_turn":
# Verarbeite die vollständige Antwort
print(response.content[0].text)Manchmal gibt Claude eine leere Antwort zurück (genau 2–3 Token ohne Inhalt) mit stop_reason: "end_turn". Dies passiert typischerweise, wenn Claude interpretiert, dass der Assistant-Turn abgeschlossen ist, insbesondere nach Tool-Ergebnissen.
Häufige Ursachen:
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: Sende Tool-Ergebnisse direkt ohne zusätzlichen Text
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
]
# Falls du nach Korrektur der Nachrichtenstruktur weiterhin leere Antworten erhältst:
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-4-8", max_tokens=1024, messages=messages
)
# Prüfe, ob die Antwort leer ist
if response.stop_reason == "end_turn" and not response.content:
# FALSCH: Nicht einfach mit der leeren Antwort erneut versuchen
# 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-4-8", max_tokens=1024, messages=messages
)
return responseBest Practices:
Claude hat gestoppt, weil es das in deiner Anfrage angegebene max_tokens-Limit erreicht hat.
# Anfrage mit begrenzten Tokens
response = client.messages.create(
model="claude-opus-4-8",
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 aufgrund des Erreichens des max_tokens-Limits abgeschnitten wird und die abgeschnittene Antwort einen unvollständigen Tool-Use-Block enthält, musst du die Anfrage mit einem höheren max_tokens-Wert wiederholen, 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-4-8",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)Claude ist auf eine deiner benutzerdefinierten Stop-Sequenzen gestoßen.
response = client.messages.create(
model="claude-opus-4-8",
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}")Claude ruft ein Tool auf und erwartet, dass du es ausführst.
Für die meisten Tool-Use-Implementierungen verwende den Tool Runner, der die Tool-Ausführung, Ergebnisformatierung und Konversationsverwaltung automatisch übernimmt.
from anthropic import Anthropic
client = 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-sonnet-4-20250514",
max_tokens=1024,
tools=[weather_tool],
messages=[{"role": "user", "content": "What's the weather?"}],
)
if response.stop_reason == "tool_use":
# Extrahiere und führe das Tool aus
for content in response.content:
if content.type == "tool_use":
result = execute_tool(content.name, content.input)
# Gib das Ergebnis an Claude für die finale Antwort zurückWird zurückgegeben, wenn die serverseitige Sampling-Schleife ihr Iterationslimit erreicht, während sie Server-Tools wie Websuche oder Web-Fetch ausführt. Das Standardlimit beträgt 10 Iterationen pro Anfrage.
Wenn dies passiert, kann die Antwort einen server_tool_use-Block ohne ein entsprechendes server_tool_result enthalten. Um Claude die Verarbeitung abschließen zu lassen, setze die Konversation fort, indem du die Antwort unverändert zurücksendest.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
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 Konversation fort, indem du die Antwort zurücksendest
messages = [
{"role": "user", "content": original_query},
{"role": "assistant", "content": response.content},
]
continuation = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=messages,
tools=[{"type": "web_search_20250305", "name": "web_search"}],
)Deine Anwendung sollte pause_turn in jeder Agent-Schleife behandeln, die Server-Tools verwendet. Füge einfach die Antwort des Assistenten zu deinem Messages-Array hinzu und stelle eine weitere API-Anfrage, damit Claude fortfahren kann.
Claude hat es abgelehnt, eine Antwort zu generieren. Bei Claude Fable 5 geben Sicherheitsklassifikatoren diesen Stop-Grund als normale HTTP-200-Antwort zurück, nicht als Fehler.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=[{"role": "user", "content": "[Unsafe request]"}],
)
if response.stop_reason == "refusal":
# Claude hat die Antwort abgelehnt
print("Claude was unable to process this request")
# Erwäge, die Anfrage umzuformulieren oder anzupassenWenn du bei der Verwendung von Claude Sonnet 4.5 oder Opus 4.1 (veraltet) häufig auf refusal-Stop-Gründe stößt, kannst du versuchen, deine API-Aufrufe auf Haiku 4.5 (claude-haiku-4-5-20251001) umzustellen, das andere Nutzungsbeschränkungen hat. Erfahre mehr über das Verständnis der API-Sicherheitsfilter von Sonnet 4.5.
Bei einer Ablehnung identifiziert das stop_details-Objekt die Richtlinienkategorie, die sie ausgelöst hat. Die Kategorien und die vollständige Struktur der Ablehnungsantwort werden unter Ablehnungen und Fallback behandelt. stop_details ist null für alle Stop-Gründe außer refusal.
Eine abgelehnte Anfrage bei Claude Fable 5 kann in der Regel durch einen erneuten Versuch mit einem anderen Claude-Modell bedient werden, und Ablehnungen und Fallback zeigt, wie du diesen Retry einrichtest – serverseitig oder in deinem Client. Fallback-Guthaben erklärt, wie du vermeidest, die Prompt-Cache-Kosten doppelt zu bezahlen, wenn du den Retry selbst implementierst.
Claude hat gestoppt, weil es das Kontextfenster-Limit des Modells erreicht hat. Dies ermöglicht es dir, die maximal möglichen Token anzufordern, ohne die genaue Eingabegröße zu kennen.
# Anfrage mit maximalen Tokens, um so viel wie möglich zu erhalten
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=20000, # Python SDK requires streaming for max_tokens above ~21k (Opus 4.8 supports 128k with streaming)
messages=[
{"role": "user", "content": "Large input that uses most of context window..."}
],
)
if response.stop_reason == "model_context_window_exceeded":
# Antwort hat das Kontextfenster-Limit vor max_tokens erreicht
print("Response reached model's context window limit")
# Die Antwort ist weiterhin gültig, wurde aber durch das Kontextfenster begrenztDieser Stop-Grund ist standardmäßig in Sonnet 4.5 und neueren Modellen verfügbar. Für frühere Modelle verwende den Beta-Header model-context-window-exceeded-2025-08-26, um dieses Verhalten zu aktivieren.
Mache es dir zur Gewohnheit, stop_reason in deiner Antwortverarbeitungslogik zu überprüfen:
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:
# Behandle end_turn und andere Fälle
return response.content[0].textWenn eine Antwort aufgrund von Token-Limits oder des Kontextfensters abgeschnitten wird:
def handle_truncated_response(response):
if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
# Option 1: Warne den Nutzer vor dem spezifischen Limit
if response.stop_reason == "max_tokens":
message = "[Response truncated due to max_tokens limit]"
else:
message = "[Response truncated due to context window limit]"
return f"{response.content[0].text}\n\n{message}"
# Option 2: Setze die Generierung fort
messages = [
{"role": "user", "content": original_prompt},
{"role": "assistant", "content": response.content[0].text},
]
continuation = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=messages + [{"role": "user", "content": "Please continue"}],
)
return response.content[0].text + continuation.content[0].textBei der Verwendung von Server-Tools kann die API pause_turn zurückgeben, wenn die serverseitige Sampling-Schleife ihr Iterationslimit erreicht (Standard: 10). 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-4-8", max_tokens=1024, messages=messages, tools=tools
)
if response.stop_reason != "pause_turn":
# Claude hat die Verarbeitung abgeschlossen – gib die finale Antwort zurück
return response
# pause_turn: ersetze die gesamte Nachrichtenliste, um abwechselnde Rollen beizubehalten
messages = [
{"role": "user", "content": user_query},
{"role": "assistant", "content": response.content},
]
# Maximale Fortsetzungen erreicht – gib die letzte Antwort zurück
return responseEs ist wichtig, zwischen stop_reason-Werten und tatsächlichen Fehlern zu unterscheiden:
import anthropic
from anthropic import Anthropic
client = Anthropic()
try:
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# Behandle erfolgreiche Antwort mit stop_reason
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIStatusError as e:
# Behandle tatsächliche Fehler
if e.status_code == 429:
print("Rate limit exceeded")
elif e.status_code == 500:
print("Server error")Bei der Verwendung von Streaming ist stop_reason:
null im initialen message_start-Eventmessage_delta-Event bereitgestelltfrom anthropic import Anthropic
client = Anthropic()
with client.messages.stream(
model="claude-sonnet-4-20250514",
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}")Einfacher mit dem Tool Runner: Das folgende Beispiel zeigt die manuelle Tool-Behandlung. Für die meisten Anwendungsfälle übernimmt der Tool Runner die Tool-Ausführung automatisch mit deutlich weniger Code.
def complete_tool_workflow(client, user_query, tools):
messages = [{"role": "user", "content": user_query}]
while True:
response = client.messages.create(
model="claude-opus-4-8", max_tokens=1024, messages=messages, tools=tools
)
if response.stop_reason == "tool_use":
# Führe Tools aus und fahre fort
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 responsedef 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-4-8", messages=messages, max_tokens=4096
)
full_response += response.content[0].text
if response.stop_reason != "max_tokens":
break
# Fahre dort fort, 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_responseMit 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.messages.create(
model="claude-opus-4-8",
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":
# Maximale mögliche Token-Anzahl bei gegebener Eingabegröße erhalten
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
elif response.stop_reason == "max_tokens":
# Genau die angeforderte Token-Anzahl erhalten
print(f"Generated {response.usage.output_tokens} tokens (max_tokens reached)")
else:
# Natürlicher Abschluss
print(f"Generated {response.usage.output_tokens} tokens (natural completion)")
return response.content[0].textDurch die korrekte Behandlung von stop_reason-Werten kannst du robustere Anwendungen entwickeln, die verschiedene Antwortszenarien elegant handhaben und bessere Nutzererfahrungen bieten.
Was this page helpful?