Raisons d'arrêt et repli
Découvrez ce que signifie chaque valeur de stop_reason et comment gérer la troncature, l'utilisation d'outils, les tours mis en pause et les refus dans votre application.
Chaque réponse de l'API Messages inclut un champ stop_reason qui vous indique pourquoi Claude a cessé de générer. Vérifiez ce champ pour décider s'il faut utiliser la réponse telle quelle, poursuivre la conversation, réessayer ou vous replier sur un autre modèle.
Pour le schéma de réponse complet, consultez la référence de l'API Messages.
Référence rapide
| Valeur | Quand elle survient | Que faire |
|---|---|---|
end_turn | Claude a terminé sa réponse naturellement. | Utilisez la réponse. |
max_tokens | La réponse a atteint votre limite max_tokens. | Augmentez max_tokens ou poursuivez la réponse. |
stop_sequence | Claude a émis l'une de vos stop_sequences. | Lisez stop_sequence pour voir laquelle s'est déclenchée. |
tool_use | Claude appelle un outil. | Exécutez l'outil et renvoyez le résultat. Un appel d'outil serveur auquel il manque encore son bloc de résultat se termine dans une réponse ultérieure. |
pause_turn | Une boucle d'outil serveur a atteint sa limite d'itérations. | Renvoyez le contenu de l'assistant pour continuer. |
refusal | Claude a refusé de répondre. | Lisez stop_details et réessayez sur un modèle de repli. |
model_context_window_exceeded | La réponse a rempli la fenêtre de contexte du modèle. | Traitez la réponse comme tronquée. |
Le champ stop_reason
Le champ stop_reason fait partie de chaque réponse réussie de l'API Messages. Contrairement aux erreurs, qui indiquent des échecs dans le traitement de votre requête, stop_reason vous indique pourquoi Claude a terminé la génération de sa réponse.
{
"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
}
}Valeurs de stop_reason
end_turn
La raison d'arrêt la plus courante. Indique que Claude a terminé sa réponse naturellement.
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":
# Traiter la réponse complète
for block in response.content:
if block.type == "text":
print(block.text)Parfois, Claude renvoie une réponse vide (exactement 2 à 3 tokens sans contenu) avec stop_reason: "end_turn". Cela se produit généralement lorsque Claude interprète que le tour de l'assistant est terminé, en particulier après des résultats d'outils.
Causes courantes :
- Ajouter des blocs de texte immédiatement après les résultats d'outils (Claude apprend à s'attendre à ce que l'utilisateur insère toujours du texte après les résultats d'outils, il termine donc son tour pour suivre ce schéma)
- Renvoyer la réponse terminée de Claude sans rien ajouter (Claude a déjà déterminé qu'il avait terminé, il restera donc dans cet état)
Comment éviter les réponses vides :
# INCORRECT : ajout de texte immédiatement après 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
},
],
},
]
# CORRECT : envoyer les résultats d'outils directement, sans texte supplémentaire
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
]Si vous obtenez toujours des réponses vides après avoir corrigé la structure des messages, ajoutez un prompt de continuation dans un nouveau message utilisateur plutôt que de réessayer avec la réponse vide :
def handle_empty_response(client, messages):
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
# Vérifier si la réponse est vide.
if response.stop_reason == "end_turn" and not response.content:
# INCORRECT : ne vous contentez pas de réessayer avec la réponse vide.
# Cela ne fonctionnera pas, car Claude a déjà décidé qu'il avait terminé.
# CORRECT : ajoutez un prompt de continuation dans un NOUVEAU message utilisateur.
messages.append({"role": "user", "content": "Please continue"})
response = client.messages.create(
model="claude-opus-5-5", max_tokens=1024, messages=messages
)
return responseBonnes pratiques :
- N'ajoutez jamais de blocs de texte immédiatement après les résultats d'outils : cela apprend à Claude à attendre une saisie de l'utilisateur après chaque utilisation d'outil.
- Ne réessayez pas les réponses vides sans modification : renvoyer la réponse vide ne servira à rien.
- Utilisez les prompts de continuation en dernier recours : uniquement si ces correctifs ne résolvent pas le problème.
max_tokens
Claude s'est arrêté parce qu'il a atteint la limite max_tokens spécifiée dans votre requête.
client = anthropic.Anthropic()
# Requête avec un nombre de tokens limité.
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":
# La réponse a été tronquée.
print("Response was cut off at token limit")
# Envisagez d'effectuer une autre requête pour continuer.Si la réponse de Claude est coupée parce qu'elle a atteint la limite max_tokens, et que la réponse tronquée contient un bloc d'utilisation d'outil incomplet, vous devrez réessayer la requête avec une valeur max_tokens plus élevée pour obtenir l'utilisation d'outil complète.
# Vérifier si la réponse a été tronquée pendant l'utilisation d'outils.
if response.stop_reason == "max_tokens":
# Vérifier si le dernier bloc de contenu est un tool_use incomplet.
last_block = response.content[-1]
if last_block.type == "tool_use":
# Renvoyer la requête avec une valeur max_tokens plus élevée.
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096, # Increased limit
messages=messages,
tools=tools,
)stop_sequence
Claude a rencontré l'une de vos séquences d'arrêt personnalisées.
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 appelle un outil et attend que vous l'exécutiez.
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":
# Extraire et exécuter l'outil.
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
# Renvoyer le résultat à Claude pour la réponse finale.Une réponse tool_use peut également contenir un bloc server_tool_use dont l'id n'a pas de bloc de résultat correspondant. Cet appel d'outil serveur n'est pas terminé, et cette réponse ne contient pas son résultat. Dans le cas courant, Claude appelle un outil serveur et l'un de vos outils client dans le même groupe d'appels d'outils parallèles : l'API renvoie la réponse sans exécuter l'outil serveur afin que vous puissiez d'abord exécuter les outils client. Il n'existe aucun autre marqueur pour cet état ; détectez-le en vérifiant, pour chaque bloc server_tool_use ou mcp_tool_use, si son id possède un bloc de résultat correspondant.
{
"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" }
}
]
}La continuation est un message utilisateur composé de blocs tool_result, un pour chaque bloc tool_use de la réponse (voir Gérer les appels d'outils), avec deux règles supplémentaires : ce message ne doit rien contenir d'autre que les blocs tool_result, et la requête doit conserver le même tableau tools. Une requête de reprise qui ne définit plus l'outil serveur en attente échoue avec une erreur 400 dont le message se termine par but no `web_search` tool was provided. L'API rattache vos résultats au tour de l'assistant encore ouvert, exécute l'outil serveur différé (pour une exécution de code en pause, la reprend) et poursuit le tour. Pour un outil serveur que Claude a appelé directement, le content de la réponse suivante commence par le bloc de résultat qui répond à l'id server_tool_use de la réponse précédente.
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
"content": "Linux demo-host 6.8.0-52-generic x86_64 GNU/Linux"
}
]
}Ajouter quoi que ce soit après les blocs tool_result dans ce message utilisateur, comme du texte, met fin au tour de l'assistant ; pour un outil serveur que Claude a appelé directement, la requête échoue alors avec une erreur 400 invalid_request_error qui nomme l'outil serveur non résolu :
`web_search` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_search_tool_result` blockOmettre un tool_result, ou en placer un après un autre contenu, échoue plus tôt avec l'erreur standard tool_use ids were found without tool_result blocks immediately after à la place. Pour fournir davantage d'informations à Claude, envoyez-les dans un message utilisateur distinct une fois le tour terminé.
pause_turn
Renvoyé lorsque la boucle d'échantillonnage côté serveur atteint sa limite d'itérations lors de l'exécution d'outils serveur tels que la recherche web. La limite par défaut est de 10 itérations par requête.
Lorsque cela se produit, la réponse peut contenir un bloc server_tool_use sans bloc de résultat correspondant. Pour permettre à Claude de terminer le traitement, poursuivez la conversation en renvoyant la réponse telle quelle. Une réponse qui laisse un bloc tool_use client en attente de votre part n'a jamais un stop_reason égal à pause_turn : lorsque Claude s'arrête pour appeler vos outils, stop_reason vaut tool_use, et vous la poursuivez en envoyant les blocs tool_result client au lieu de la réponse elle-même.
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":
# Poursuivez la conversation en renvoyant la réponse.
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 a refusé de générer une réponse. Les classificateurs de sécurité renvoient cette raison d'arrêt sous la forme d'une réponse HTTP 200 normale, et non d'une erreur.
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 a refusé de répondre.
print("Claude was unable to process this request")
# Envisagez de reformuler ou de modifier la requête.En cas de refus, l'objet stop_details identifie la catégorie de politique qui l'a déclenché. Les catégories et la forme complète de la réponse de refus sont décrites sur la page Refus et repli. stop_details vaut null pour toutes les raisons d'arrêt autres que refusal.
Une requête refusée sur Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5 ou Claude Opus 5 peut généralement être traitée en réessayant sur un autre modèle Claude. La page Refus et repli montre comment configurer cette nouvelle tentative, côté serveur ou dans votre client. Si vous construisez vous-même la nouvelle tentative à partir de Claude Fable 5.1, Claude Fable 5, Claude Opus 5.5 ou Claude Opus 5, la page crédit de repli explique comment éviter de payer deux fois le coût de la mise en cache des prompts.
model_context_window_exceeded
Claude s'est arrêté parce qu'il a atteint la limite de la « context window » (fenêtre de contexte) du modèle. Cela vous permet de demander le nombre maximal de tokens possible sans connaître la taille exacte de l'entrée.
# Requête avec le nombre maximal de tokens pour obtenir le plus de contenu possible.
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":
# La réponse a atteint la limite de la fenêtre de contexte avant max_tokens.
print("Response reached model's context window limit")
# La réponse reste valide, mais a été limitée par la fenêtre de contexte.Bonnes pratiques pour gérer les raisons d'arrêt
Vérifiez toujours stop_reason
Prenez l'habitude de vérifier le stop_reason dans votre logique de traitement des réponses :
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 _:
# Gérer end_turn et les autres cas.
return next(
(block.text for block in response.content if block.type == "text"),
"",
)Gérez les réponses tronquées avec élégance
Lorsqu'une réponse est tronquée en raison des limites de tokens ou de la fenêtre de contexte, ajoutez un avertissement afin que le lecteur sache que la sortie est incomplète. Pour continuer la génération à partir de l'endroit où la réponse s'est arrêtée, consultez plutôt Garantir des réponses complètes.
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 textImplémentez une logique de nouvelle tentative pour pause_turn
Lors de l'utilisation d'outils serveur, l'API peut renvoyer pause_turn si la boucle d'échantillonnage côté serveur atteint sa limite d'itérations (10 par défaut). Gérez cela en poursuivant la conversation :
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 a terminé le traitement - renvoyer la réponse finale.
return response
# pause_turn : remplacer toute la liste des messages pour conserver l'alternance des rôles.
messages = [
{"role": "user", "content": user_query},
{"role": "assistant", "content": response.content},
]
# Nombre maximal de continuations atteint - renvoyer la dernière réponse.
return responseRaisons d'arrêt et erreurs
Il est important de distinguer les valeurs de stop_reason des erreurs réelles :
Raisons d'arrêt (réponses réussies)
- Font partie du corps de la réponse
- Indiquent pourquoi la génération s'est arrêtée normalement
- La réponse contient un contenu valide
Erreurs (requêtes échouées)
- Codes de statut HTTP 4xx ou 5xx
- Indiquent des échecs de traitement de la requête
- La réponse contient les détails de l'erreur
client = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# Gérer la réponse réussie avec stop_reason
if response.stop_reason == "max_tokens":
print("Response was truncated")
except anthropic.APIStatusError as e:
# Gérer les erreurs réelles
match e.status_code:
case 429:
print("Rate limit exceeded")
case 500:
print("Server error")Considérations relatives au streaming
Lors de l'utilisation du streaming, stop_reason est :
nulldans l'événement initialmessage_start- Fourni dans l'événement
message_delta - Non fourni dans aucun autre événement
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}")Modèles courants
Gérer les flux de travail d'utilisation d'outils
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":
# Exécuter les outils et continuer
tool_results = execute_tools(response.content)
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
else:
# Réponse finale
return responseGarantir des réponses complètes
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
# Reprendre là où il s'est arrêté
messages = [
{"role": "user", "content": prompt},
{"role": "assistant", "content": full_response},
{"role": "user", "content": "Please continue from where you left off."},
]
return full_responseObtenir le maximum de tokens sans connaître la taille de l'entrée
Avec la raison d'arrêt model_context_window_exceeded, vous pouvez demander le nombre maximal de tokens possible sans calculer la taille de l'entrée :
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":
# Nombre maximal de tokens possible obtenu compte tenu de la taille de l'entrée.
print(
f"Generated {response.usage.output_tokens} tokens (context limit reached)"
)
case "max_tokens":
# Nombre exact de tokens demandés obtenu.
print(
f"Generated {response.usage.output_tokens} tokens (max_tokens reached)"
)
case _:
# Fin naturelle de la génération.
print(
f"Generated {response.usage.output_tokens} tokens (natural completion)"
)
return next((block.text for block in response.content if block.type == "text"), "")Étapes suivantes
Réessayez les requêtes refusées sur un modèle de repli, côté serveur ou dans votre client.
Laissez le SDK gérer pour vous la boucle tool_use, le formatage des résultats et les nouvelles tentatives.
Lisez stop_reason depuis l'événement message_delta lors du streaming.
Gérez les erreurs HTTP 4xx et 5xx, qui sont distinctes des raisons d'arrêt.
Was this page helpful?