Lorsque vous faites une requête à l’API Messages, la réponse de Claude inclut un champ stop_reason qui indique pourquoi le modèle a arrêté de générer sa réponse. Comprendre ces valeurs est crucial pour construire des applications robustes qui gèrent différents types de réponses de manière appropriée. Pour plus de détails sur stop_reason dans la réponse de l’API, consultez la référence de l’API Messages.

Qu’est-ce que 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é avec succès la génération de sa réponse.
Exemple de réponse
{
  "id": "msg_01234",
  "type": "message",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "Voici la réponse à votre question..."
    }
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 100,
    "output_tokens": 50
  }
}

Valeurs des raisons d’arrêt

end_turn

La raison d’arrêt la plus courante. Indique que Claude a terminé sa réponse naturellement.
if response.stop_reason == "end_turn":
    # Traiter la réponse complète
    print(response.content[0].text)

Réponses vides avec end_turn

Parfois Claude retourne une réponse vide (exactement 2-3 tokens sans contenu) avec stop_reason: "end_turn". Cela arrive généralement quand Claude interprète que le tour de l’assistant est terminé, particulièrement après les résultats d’outils. Causes communes :
  • 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, donc il termine son tour pour suivre le modèle)
  • Renvoyer la réponse complète de Claude sans rien ajouter (Claude a déjà décidé qu’il avait terminé, donc il restera terminé)
Comment prévenir les réponses vides :
# INCORRECT : Ajouter du texte immédiatement après tool_result
messages = [
    {"role": "user", "content": "Calculez la somme de 1234 et 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": "Voici le résultat"  # N'ajoutez pas de texte après tool_result
        }
    ]}
]

# CORRECT : Envoyer les résultats d'outils directement sans texte supplémentaire
messages = [
    {"role": "user", "content": "Calculez la somme de 1234 et 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"
        }
    ]}  # Juste le tool_result, pas de texte supplémentaire
]

# Si vous obtenez encore des réponses vides après avoir corrigé ce qui précède :
def handle_empty_response(client, messages):
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        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 pas simplement réessayer avec la réponse vide
        # Cela ne fonctionnera pas car Claude a déjà décidé qu'il avait terminé

        # CORRECT : Ajouter une invite de continuation dans un NOUVEAU message utilisateur
        messages.append({"role": "user", "content": "Veuillez continuer"})

        response = client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            messages=messages
        )

    return response
Meilleures pratiques :
  1. Ne jamais ajouter de blocs de texte immédiatement après les résultats d’outils - Cela enseigne à Claude à s’attendre à une entrée utilisateur après chaque utilisation d’outil
  2. Ne pas réessayer les réponses vides sans modification - Simplement renvoyer la réponse vide n’aidera pas
  3. Utiliser les invites de continuation en dernier recours - Seulement si les corrections ci-dessus 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.
# Requête avec tokens limités
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=10,
    messages=[{"role": "user", "content": "Expliquez la physique quantique"}]
)

if response.stop_reason == "max_tokens":
    # La réponse a été tronquée
    print("La réponse a été coupée à la limite de tokens")
    # Considérez faire une autre requête pour continuer

stop_sequence

Claude a rencontré une de vos séquences d’arrêt personnalisées.
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    stop_sequences=["FIN", "STOP"],
    messages=[{"role": "user", "content": "Générez du texte jusqu'à ce que vous disiez FIN"}]
)

if response.stop_reason == "stop_sequence":
    print(f"Arrêté à la séquence : {response.stop_sequence}")

tool_use

Claude appelle un outil et s’attend à ce que vous l’exécutiez.
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=[weather_tool],
    messages=[{"role": "user", "content": "Quel temps fait-il ?"}]
)

if response.stop_reason == "tool_use":
    # Extraire et exécuter l'outil
    for content in response.content:
        if content.type == "tool_use":
            result = execute_tool(content.name, content.input)
            # Retourner le résultat à Claude pour la réponse finale

pause_turn

Utilisé avec les outils serveur comme la recherche web quand Claude doit mettre en pause une opération de longue durée.
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    tools=[{"type": "web_search_20250305", "name": "web_search"}],
    messages=[{"role": "user", "content": "Recherchez les dernières nouvelles sur l'IA"}]
)

if response.stop_reason == "pause_turn":
    # Continuer la conversation
    messages = [
        {"role": "user", "content": original_query},
        {"role": "assistant", "content": response.content}
    ]
    continuation = client.messages.create(
        model="claude-sonnet-4-5",
        messages=messages,
        tools=[{"type": "web_search_20250305", "name": "web_search"}]
    )

refusal

Claude a refusé de générer une réponse en raison de préoccupations de sécurité.
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "[Requête non sécurisée]"}]
)

if response.stop_reason == "refusal":
    # Claude a refusé de répondre
    print("Claude n'a pas pu traiter cette requête")
    # Considérez reformuler ou modifier la requête
Si vous rencontrez fréquemment des raisons d’arrêt refusal en utilisant Claude Sonnet 4.5 ou Opus 4.1, vous pouvez essayer de mettre à jour vos appels API pour utiliser Sonnet 4 (claude-sonnet-4-20250514), qui a des restrictions d’utilisation différentes. En savoir plus sur comprendre les filtres de sécurité API de Sonnet 4.5.
Pour en savoir plus sur les refus déclenchés par les filtres de sécurité API pour Claude Sonnet 4.5, consultez Comprendre les filtres de sécurité API de Sonnet 4.5.

model_context_window_exceeded

Claude s’est arrêté parce qu’il a atteint la limite de la fenêtre de contexte du modèle. Cela vous permet de demander le maximum de tokens possible sans connaître la taille exacte de l’entrée.
# Requête avec le maximum de tokens pour obtenir autant que possible
response = client.messages.create(
    model="claude-sonnet-4-5",
    max_tokens=64000,  # Tokens de sortie maximum du modèle
    messages=[{"role": "user", "content": "Grande entrée qui utilise la plupart de la fenêtre de contexte..."}]
)

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("La réponse a atteint la limite de la fenêtre de contexte du modèle")
    # La réponse est toujours valide mais était limitée par la fenêtre de contexte
Cette raison d’arrêt est disponible par défaut dans Sonnet 4.5 et les modèles plus récents. Pour les modèles antérieurs, utilisez l’en-tête bêta model-context-window-exceeded-2025-08-26 pour activer ce comportement.

Meilleures pratiques pour gérer les raisons d’arrêt

1. Toujours vérifier stop_reason

Prenez l’habitude de vérifier le stop_reason dans votre logique de gestion des réponses :
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:
        # Gérer end_turn et autres cas
        return response.content[0].text

2. Gérer les réponses tronquées avec élégance

Quand une réponse est tronquée en raison de limites de tokens ou de fenêtre de contexte :
def handle_truncated_response(response):
    if response.stop_reason in ["max_tokens", "model_context_window_exceeded"]:
        # Option 1 : Avertir l'utilisateur de la limite spécifique
        if response.stop_reason == "max_tokens":
            message = "[Réponse tronquée en raison de la limite max_tokens]"
        else:
            message = "[Réponse tronquée en raison de la limite de fenêtre de contexte]"
        return f"{response.content[0].text}\n\n{message}"

        # Option 2 : Continuer la génération
        messages = [
            {"role": "user", "content": original_prompt},
            {"role": "assistant", "content": response.content[0].text}
        ]
        continuation = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=1024,
            messages=messages + [{"role": "user", "content": "Veuillez continuer"}]
        )
        return response.content[0].text + continuation.content[0].text

3. Implémenter une logique de nouvelle tentative pour pause_turn

Pour les outils serveur qui peuvent se mettre en pause :
def handle_paused_conversation(initial_response, max_retries=3):
    response = initial_response
    messages = [{"role": "user", "content": original_query}]
    
    for attempt in range(max_retries):
        if response.stop_reason != "pause_turn":
            break
            
        messages.append({"role": "assistant", "content": response.content})
        response = client.messages.create(
            model="claude-sonnet-4-5",
            messages=messages,
            tools=original_tools
        )
    
    return response

Raisons d’arrêt vs erreurs

Il est important de distinguer entre les valeurs stop_reason et les erreurs réelles :

Raisons d’arrêt (réponses réussies)

  • Partie du corps de la réponse
  • Indiquent pourquoi la génération s’est arrêtée normalement
  • La réponse contient du contenu valide

Erreurs (requêtes échouées)

  • Codes de statut HTTP 4xx ou 5xx
  • Indiquent des échecs de traitement de requête
  • La réponse contient des détails d’erreur
try:
    response = client.messages.create(...)
    
    # Gérer la réponse réussie avec stop_reason
    if response.stop_reason == "max_tokens":
        print("La réponse a été tronquée")
    
except anthropic.APIError as e:
    # Gérer les erreurs réelles
    if e.status_code == 429:
        print("Limite de taux dépassée")
    elif e.status_code == 500:
        print("Erreur serveur")

Considérations de streaming

Lors de l’utilisation du streaming, stop_reason est :
  • null dans l’événement initial message_start
  • Fourni dans l’événement message_delta
  • Non fourni dans aucun autre événement
with client.messages.stream(...) as stream:
    for event in stream:
        if event.type == "message_delta":
            stop_reason = event.delta.stop_reason
            if stop_reason:
                print(f"Stream terminé avec : {stop_reason}")

Modèles courants

Gestion des 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-sonnet-4-5",
            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 response

Assurer 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-sonnet-4-5",
            messages=messages,
            max_tokens=4096
        )

        full_response += response.content[0].text

        if response.stop_reason != "max_tokens":
            break

        # Continuer d'où il s'est arrêté
        messages = [
            {"role": "user", "content": prompt},
            {"role": "assistant", "content": full_response},
            {"role": "user", "content": "Veuillez continuer d'où vous vous êtes arrêté."}
        ]

    return full_response

Obtenir le maximum de tokens sans connaître la taille d’entrée

Avec la raison d’arrêt model_context_window_exceeded, vous pouvez demander le maximum de tokens possible sans calculer la taille d’entrée :
def get_max_possible_tokens(client, prompt):
    """
    Obtenir autant de tokens que possible dans la fenêtre de contexte du modèle
    sans avoir besoin de calculer le nombre de tokens d'entrée
    """
    response = client.messages.create(
        model="claude-sonnet-4-5",
        messages=[{"role": "user", "content": prompt}],
        max_tokens=64000  # Définir au maximum de tokens de sortie du modèle
    )

    if response.stop_reason == "model_context_window_exceeded":
        # A obtenu le maximum de tokens possible étant donnée la taille d'entrée
        print(f"Généré {response.usage.output_tokens} tokens (limite de contexte atteinte)")
    elif response.stop_reason == "max_tokens":
        # A obtenu exactement les tokens demandés
        print(f"Généré {response.usage.output_tokens} tokens (max_tokens atteint)")
    else:
        # Achèvement naturel
        print(f"Généré {response.usage.output_tokens} tokens (achèvement naturel)")

    return response.content[0].text
En gérant correctement les valeurs stop_reason, vous pouvez construire des applications plus robustes qui gèrent avec élégance différents scénarios de réponse et offrent de meilleures expériences utilisateur.