Retour au blog

DépannageGuide

Réponse IA tronquée ? Corrigez finish_reason « length » et max_tokens

Votre réponse API s'arrête en pleine phrase ou renvoie du JSON cassé ? Vérifiez finish_reason, augmentez max_tokens dans la limite et ajoutez une étape de poursuite.

Par CheapTokens TeamPublié le 6 min de lecture

Couverture façon terminal montrant finish_reason length dans une réponse API
Sur cette page
  1. À quoi ressemble une réponse tronquée à l'écran
  2. Étape 1 : lisez le champ qui vous renseigne
  3. Étape 2 : augmentez max_tokens, mais sans dépasser le plafond
  4. Étape 3 : du JSON qui s'arrête à mi-chemin
  5. Étape 4 : laissez le modèle continuer
  6. Ce que coûte une longue réponse
  7. Avant de livrer : une courte checklist

Votre script s'est exécuté, le modèle a répondu, et la fonction se termine au milieu d'une ligne. Ou le parseur JSON plante sur une réponse qui semble parfaitement normale. Le plus souvent, le modèle n'a pas échoué : il a atteint la limite max_tokens que vous avez fixée (ou une valeur par défaut que vous n'avez jamais vue) et l'API l'a coupé en plein mot.

Le correctif rapide : lisez finish_reason dans la réponse. S'il vaut "length" (API compatible OpenAI) ou si stop_reason vaut "max_tokens" (Anthropic Messages), la réponse a été tronquée. Augmentez max_tokens jusqu'à la limite de sortie du modèle, ou demandez au modèle de continuer. Le reste de cette page montre les deux en code que vous pouvez copier-coller.

Si vous voulez la version en langage clair de pourquoi cela se produit, commencez par pourquoi une réponse IA s'arrête en pleine phrase. Cette page est la version débogage.

À quoi ressemble une réponse tronquée à l'écran#

Les symptômes varient selon ce que vous avez demandé, mais la cause est la même. Essayez de faire correspondre ces cas à ce que vous avez observé :

Associer les paires

Faites correspondre le symptôme à sa cause probable

  • Une fonction se termine par return resuLa réponse de code a atteint max_tokens en pleine ligne
  • json.loads échoue avec « Unterminated string » ou « Expecting ',' delimiter »Le JSON a été coupé en cours de route
  • Une traduction s'arrête après la première moitié du texteLa limite de sortie est inférieure à la traduction
  • La réponse est complète et le champ indique stopLe modèle a terminé de lui-même ; cherchez ailleurs

Cette dernière ligne compte. Si finish_reason vaut stop, la limite n'est pas votre problème, et augmenter max_tokens ne changera rien.

Étape 1 : lisez le champ qui vous renseigne#

Chaque réponse indique pourquoi la génération s'est arrêtée. Dans une réponse compatible OpenAI, c'est choices[0].finish_reason : "stop" quand le modèle a terminé, "length" quand il a épuisé ses tokens. Dans l'API Anthropic Messages, le champ est stop_reason, et la valeur de troncature est "max_tokens". Le guide des stop reasons d'Anthropic (vérifié le 28 octobre 2026) recommande exactement les deux correctifs ci-dessous : augmenter max_tokens ou poursuivre la réponse.

Voici la même vérification sous trois formes. L'URL de base est https://api.cheaptokens.dev/v1 pour le SDK OpenAI et https://api.cheaptokens.dev (sans /v1) pour le SDK Anthropic. Le max_tokens=200 est volontairement petit, pour que vous puissiez constater la troncature vous-même.

Une vérification, trois façons

Python (SDK OpenAI)

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

resp = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

choice = resp.choices[0]
if choice.finish_reason == "length":
    print("Cut off at the token limit")
print(choice.message.content)
print(resp.usage.completion_tokens)

Python (SDK Anthropic)

python
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.cheaptokens.dev",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

msg = client.messages.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

if msg.stop_reason == "max_tokens":
    print("Cut off at the token limit")
print(msg.content[0].text)

curl

bash
curl https://api.cheaptokens.dev/v1/chat/completions \
  -H "Authorization: Bearer $CHEAPTOKENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-ai/DeepSeek-V4-Flash-0731",
    "max_tokens": 200,
    "messages": [{"role": "user", "content": "Write a Python class for a todo list with tests."}]
  }'

Cherchez "finish_reason": "length" dans le JSON renvoyé.

Quand cela fonctionne comme diagnostic, vous verrez cette paire : une limite basse donne length, une limite généreuse donne stop.

Même requête avec un max_tokens bas et élevé : finish_reason passe de length à stop
Les deux valeurs de finish_reason que vous rencontrerez le plus souvent

Étape 2 : augmentez max_tokens, mais sans dépasser le plafond#

Le correctif évident est un nombre plus grand. Le piège : chaque modèle a son propre plafond sur la réponse. Ne fixez pas une valeur au-dessus : la limite est propre à chaque modèle, alors consultez d'abord le tableau ci-dessous. Voici les limites par réponse sur CheapTokens (faits vérifiés le 28 octobre 2026) :

Modèle Sortie max par réponse Contexte
DeepSeek-V4-Flash 32 768 tokens 380K
GLM-5.3-Flash 8 192 tokens 390K
MiniMax-M2.7 8 192 tokens 180K

Une traduction de 9 000 tokens nécessite un modèle avec une limite par réponse plus grande — DeepSeek-V4-Flash accepte jusqu'à 32 768 tokens par réponse. Pour du code long, du JSON long ou un document entier, deepseek-ai/DeepSeek-V4-Flash-0731 a le plus de marge : jusqu'à 32 768 tokens par réponse, à un prix unique à toute heure de la journée. Prenez les identifiants exacts des modèles dans la documentation CheapTokens et sur la page des modèles, car les fournisseurs les nomment différemment.

Une règle pratique : fixez max_tokens à la réponse la plus longue que vous accepteriez, pas au maximum. Vous payez les tokens que le modèle écrit réellement, donc un plafond élevé ne coûte rien quand les réponses sont courtes.

Étape 3 : du JSON qui s'arrête à mi-chemin#

Une réponse JSON tronquée est le cas le plus pénible, car le texte semble correct jusqu'à ce que le parseur atteigne la fin. Supposez que vous extrayiez des champs de 40 factures en un seul appel et demandiez un tableau. Le modèle écrit 31 objets, atteint la limite, et le tableau ne se ferme jamais.

Une erreur json.loads sur un tableau coupé à la limite de tokens
À quoi ressemble un tableau JSON tronqué dans un parseur

N'essayez pas de réparer la chaîne en ajoutant ]}. Vous obtiendrez un JSON valide avec des enregistrements manquants, ce qui est pire qu'une erreur. Vérifiez d'abord finish_reason ; s'il vaut length, augmentez la limite et relancez l'appel, ou découpez l'entrée en lots plus petits. Pour imposer le format lui-même, voir du JSON valide depuis une API LLM.

Étape 4 : laissez le modèle continuer#

Parfois la réponse est réellement plus longue que n'importe quelle limite : un chapitre de livre, un gros refactoring. Alors renvoyez ce que vous avez et demandez au modèle de poursuivre. La boucle ci-dessous conserve le texte déjà obtenu, demande la suite, et abandonne après quelques tours pour qu'un bug ne tourne pas indéfiniment.

python
def complete(base_messages, model, max_tokens=4096, rounds=4):
    text = ""
    for _ in range(rounds):
        messages = list(base_messages)
        if text:
            messages += [
                {"role": "assistant", "content": text},
                {"role": "user", "content": "Continue exactly where you stopped. Do not repeat anything."},
            ]
        r = client.chat.completions.create(model=model, max_tokens=max_tokens, messages=messages)
        choice = r.choices[0]
        text += choice.message.content or ""
        if choice.finish_reason != "length":
            return text, True   # finished on its own
    return text, False          # still cut after all rounds

Deux habitudes gardent cela honnête. Renvoyez le drapeau True/False et affichez un avertissement à l'utilisateur sur False, plutôt que de présenter un demi-document comme terminé. Et rappelez-vous que chaque tour renvoie le texte précédent en entrée, donc une réponse poursuivie coûte plus cher qu'une seule grande réponse. Pour des données structurées, préférez une limite plus grande ou des lots plus petits plutôt que la poursuite.

Ce que coûte une longue réponse#

Supposez qu'une longue réponse fasse 2 000 tokens en entrée et 8 000 tokens en sortie. Sur DeepSeek-V4-Flash, cette requête coûte $0.00083 au prix du jour, et au total, entrée et sortie comptées ensemble, c'est moins cher qu'OpenRouter. Remplacez les chiffres par votre propre charge :

Calculateur de coût

Une journée de 50 longues réponses

Tokens d'entrée par requête: 2 000 · Tokens de sortie par requête: 8 000 · Requêtes par jour: 50

ModèlePar jourPar mois
GLM-5.3-Flash$0.018$0.54
DeepSeek-V4-Flash$0.0415$1.25
MiniMax-M2.7$0.065$1.95

À retenir : le côté sortie domine dans ce scénario, donc de la marge dans max_tokens est une assurance bon marché contre une relance. Une réponse tronquée que vous devez régénérer est payée deux fois. Pour en savoir plus sur les deux côtés de la facture, voir tokens d'entrée vs de sortie, et la liste complète des modèles avec les prix est sur la page de tarification.

Avant de livrer : une courte checklist#

Liste de contrôle

Checklist de troncature pour votre code