Prefill et contrôle du format de sortie
Le prefill était LA technique pour forcer un format. Sur les modèles récents il renvoie une erreur. Voici ce qui le remplace, et c'est mieux.
Temps de lecture : 11 min | Niveau : Avancé
Ce que tu sauras faire après
- Comprendre ce qu'était le prefill et pourquoi il ne marche plus.
- Remplacer chaque usage du prefill par la technique actuelle équivalente.
- Obtenir du JSON strictement conforme à un schéma, sans post-traitement.
- Utiliser
stop_sequencespour couper une génération au bon endroit. - Contraindre le style d'une sortie sans casser la qualité.
1. Ce qu'était le prefill
Le prefill consistait à commencer la réponse à la place du modèle. Tu ajoutais un message assistant partiel à la fin de la conversation, et le modèle continuait à partir de là.
# ANCIEN - ne fonctionne plus sur les modeles recents
messages = [
{"role": "user", "content": "Extrais les metriques de ce rapport."},
{"role": "assistant", "content": "{"}, # <- le prefill
]Le modèle étant forcé de continuer après {, il ne pouvait plus écrire "Bien sûr, voici les métriques :". Il devait produire du JSON.
C'était astucieux. C'était aussi fragile.
2. Ce qui a changé : prefill non supporté
La doc est explicite :
"Starting with Claude 4.6 models and Claude Mythos Preview, prefilled responses (providing a partial assistant message for Claude to continue from) on the last assistant turn are no longer supported. Requests with prefilled assistant messages to these models return a 400 error."
Trois précisions utiles :
- Les modèles antérieurs continuent de supporter le prefill.
- Ajouter des messages
assistantailleurs dans la conversation n'est pas concerné. C'est seulement le dernier tour assistant qui est interdit. - La raison donnée : "Model intelligence and instruction following have advanced such that most use cases of prefill no longer require it."
À noter aussi, indépendamment : tu ne peux pas prefill pendant que la réflexion est active. La page Thinking le confirme : "You can't prefill the assistant response while thinking is on." Et sur les modèles récents la réflexion est souvent toujours active.
Autre disparition à connaître : la page dédiée prompt-engineering/prefill-claudes-response n'existe plus. Elle redirige vers la page Prompt engineering overview. Le contenu sur le prefill vit maintenant dans la page Prompting best practices, section Migrating away from prefilled responses.
3. Les usages du prefill et leur remplaçant
La doc liste cinq scénarios classiques et leur migration. Voici la table de correspondance. Le premier scénario (forcer un format) est coupé en deux lignes, parce qu'il se traite différemment selon que tu veux du JSON libre ou une classification.
| Ancien usage du prefill | Remplacement actuel |
|---|---|
| Forcer un format JSON / YAML | Sorties structurées (output_config.format) ou tool use |
| Forcer une classification | Un outil avec un champ enum, ou sorties structurées |
| Supprimer le préambule ("Voici...") | Instruction directe dans le prompt système, ou balises XML |
| Contourner un refus abusif | Prompt clair dans le message user ; les refus sont mieux calibrés |
| Reprendre une génération interrompue | Déplacer la reprise dans le message user |
| Réinjecter du contexte régulièrement | Message user, ou outils, ou compaction de contexte |
Regardons les trois qui te concernent vraiment.
4. Remplaçant n°1 : les sorties structurées
C'est le remplaçant officiel pour "je veux du JSON conforme".
Le paramètre est output_config.format, de type json_schema :
{
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
},
"required": ["name", "email"],
"additionalProperties": false
}
}
}
}Points à retenir :
- La fonctionnalité est GA, aucun en-tête beta requis.
- L'ancien paramètre
output_format(à plat) est déprécié ; il a été déplacé dansoutput_config.format. Si tu l'utilises encore, il te faut l'en-tête betastructured-outputs-2025-11-13, sinon l'API renvoie une erreur 400. Et le SDK Python (v1.0 et suivantes) lève uneTypeErrorsi tu passesoutput_format={...}àclient.beta.messages.create()ou àcount_tokens(). - C'est différent de strict tool use : les sorties structurées contraignent la réponse,
strict: truecontraint les paramètres d'appel d'outil. Tu peux combiner les deux.
Exemple data : extraction de métadonnées d'un modèle dbt
schema_sortie = {
"type": "object",
"properties": {
"nom_modele": {"type": "string"},
"materialisation": {"type": "string", "enum": ["view", "table", "incremental", "ephemeral"]},
"sources": {"type": "array", "items": {"type": "string"}},
"colonnes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"nom": {"type": "string"},
"type_sql": {"type": "string"},
"description": {"type": "string"},
"est_cle": {"type": "boolean"},
},
"required": ["nom", "type_sql", "description", "est_cle"],
"additionalProperties": False,
},
},
"risques": {"type": "array", "items": {"type": "string"}},
},
"required": ["nom_modele", "materialisation", "sources", "colonnes", "risques"],
"additionalProperties": False,
}
reponse = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
output_config={"format": {"type": "json_schema", "schema": schema_sortie}},
messages=[{"role": "user", "content": f"Analyse ce modele dbt :\n{code_sql}"}],
)Tu paries sur un contrat machine, pas sur une formulation. C'est exactement ce qu'il te faut dans un pipeline.
Vérifie la liste des modèles supportés avant d'intégrer. Au 26 septembre 2026, la gamme est large : Fable 5.1, Mythos 5.1, Fable 5, Mythos 5, Mythos Preview, Opus 5.5, Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, Opus 4.5, Sonnet 5, Sonnet 4.6, Sonnet 4.5 et Haiku 4.5. Cette liste change à chaque génération de modèles.
5. Remplaçant n°2 : l'instruction directe
Pour supprimer les préambules du type "Voici le résumé demandé :", la doc recommande une instruction dans le prompt système :
"Respond directly without preamble. Do not start with phrases like 'Here is...', 'Based on...', etc."
Version française, à coller dans ton prompt système :
Reponds directement, sans preambule. Ne commence jamais par des formules comme
"Voici...", "Bien sur...", "D'apres...". Commence immediatement par le contenu demande.La doc ajoute trois alternatives : sortir dans des balises XML, utiliser les sorties structurées, ou utiliser un appel d'outil. Et une note pragmatique : si un préambule passe quand même de temps en temps, retire-le en post-traitement.
Le principe général : dire ce qu'il faut faire, pas ce qu'il ne faut pas faire
C'est le conseil numéro un de la doc sur le formatage :
- Au lieu de : "Do not use markdown in your response"
- Écris : "Your response should be composed of smoothly flowing prose paragraphs."
Deuxième levier, souvent ignoré : le style de ton prompt influence le style de la réponse. La doc : "If you are still experiencing steerability issues with output formatting, try matching your prompt style to your desired output style as closely as possible. For example, removing markdown from your prompt can reduce the volume of markdown in the output."
Si tu veux une sortie sans markdown, écris ton prompt sans markdown.
6. Remplaçant n°3 : déplacer la reprise dans le message user
Si une génération a été coupée (limite de tokens, timeout), l'ancien réflexe était de prefill avec la fin du texte. Maintenant, tu le mets dans le message user. Formulation officielle :
"Your previous response was interrupted and ended with
[previous_response]. Continue from where you left off."
Version française :
Ta reponse precedente a ete interrompue. Elle se terminait par :
<fin_precedente>
[LES_200_DERNIERS_CARACTERES]
</fin_precedente>
Reprends exactement la ou tu t'es arrete. Ne repete pas ce qui precede.
N'ecris aucune introduction.La doc ajoute une alternative souvent meilleure : si ça fait partie de ta gestion d'erreur et qu'il n'y a pas de pénalité côté expérience utilisateur, relance simplement la requête.
7. Les stop sequences
stop_sequences existe toujours et n'est pas concerné par la dépréciation du prefill.
Définition officielle : "Custom text sequences that will cause the model to stop generating."
- Type : tableau de chaînes de caractères.
- Quand une séquence est rencontrée,
stop_reasonvaut"stop_sequence"et le champstop_sequencede la réponse contient la séquence qui a déclenché l'arrêt. - Sans stop sequence, l'arrêt naturel donne
stop_reason: "end_turn".
À quoi ça sert vraiment
reponse = client.messages.create(
model="claude-opus-5-5",
max_tokens=2000,
stop_sequences=["</sql>", "\n\n---"],
messages=[{"role": "user", "content": prompt}],
)
if reponse.stop_reason == "stop_sequence":
print("arrete sur :", reponse.stop_sequence)Trois usages concrets :
- Couper après un bloc. Tu demandes du SQL dans
<sql>...</sql>, tu mets</sql>en stop sequence. Le modèle ne peut pas ajouter trois paragraphes d'explication après. Attention : la séquence d'arrêt n'est pas renvoyée dans le texte. Tu la retrouves dans le champreponse.stop_sequence, et tu dois la rajouter toi-même si tu reconstruis le XML. Ce point n'est pas écrit noir sur blanc dans la doc : fais le test une fois avec unprint(repr(reponse.content[0].text))avant de construire un parseur dessus. - Empêcher le modèle d'inventer la suite d'un dialogue. Dans un format
Question:/Reponse:, tu mets"\nQuestion:"en stop sequence. - Borner un coût. Combiné avec
max_tokens, ça évite les réponses qui dérivent.
Les limites
- Une stop sequence est une correspondance exacte de texte. Une variation d'espace ou de casse et ça ne déclenche pas.
- Elle ne garantit pas que le contenu avant l'arrêt soit valide. Tu peux recevoir un JSON coupé en deux. Pour du JSON, préfère les sorties structurées.
- Elle n'empêche pas le modèle de commencer par un préambule. Elle coupe la fin, pas le début.
8. Ce qui ne marche plus non plus : température
Réflexe fréquent : "je mets temperature=0 pour que ma génération SQL soit déterministe".
La référence de l'API est claire : "Deprecated. Models released after Claude Opus 4.6 do not support setting temperature. A value of 1.0 will be accepted for backwards compatibility, all other values will be rejected with a 400 error."
La page Thinking le confirme pour toute une liste de modèles récents : toute valeur non par défaut de temperature, top_p ou top_k renvoie une 400, que la réflexion soit active ou non.
Ce qui remplace la température basse :
| Ton objectif | La technique actuelle |
|---|---|
| Sortie au format exact | Sorties structurées (output_config.format) |
| Appels d'outils conformes | strict: true sur la définition d'outil |
| Moins de variabilité de style | Consignes précises + exemples (few-shot) |
| Moins de bavardage | effort plus bas + consigne "réponds directement" |
9. Ton template prêt à copier
Template A - Sortie JSON stricte
schema = {
"type": "object",
"properties": {
"[CHAMP_1]": {"type": "string", "description": "[CE_QUE_C_EST]"},
"[CHAMP_2]": {"type": "string", "enum": ["[VAL_1]", "[VAL_2]"]},
"[CHAMP_3]": {"type": "array", "items": {"type": "string"}},
},
"required": ["[CHAMP_1]", "[CHAMP_2]", "[CHAMP_3]"],
"additionalProperties": False,
}
reponse = client.messages.create(
model="[MODELE]",
max_tokens=[MAX_TOKENS],
output_config={"format": {"type": "json_schema", "schema": schema}},
messages=[{"role": "user", "content": "[TA_DEMANDE]"}],
)Template B - Prompt système anti-bavardage
Tu es [ROLE] et tu produis des sorties destinees a etre consommees par [DESTINATION,
ex: un script Python].
Regles de sortie, non negociables :
- Reponds directement, sans preambule. Ne commence jamais par "Voici", "Bien sur",
"D'apres".
- N'ajoute aucune conclusion, aucun resume, aucune offre d'aide supplementaire.
- Produis exactement [LE_FORMAT_ATTENDU, ex: un bloc SQL, rien d'autre].
- Si tu ne peux pas produire ce format, ecris uniquement : ERREUR: [raison en une phrase].Template C - Sortie encadrée par des balises + stop sequence
prompt = """[TA_DEMANDE]
Ecris uniquement [LE_LIVRABLE] entre les balises <[TA_BALISE]> et </[TA_BALISE]>.
N'ecris rien avant la balise ouvrante."""
reponse = client.messages.create(
model="[MODELE]",
max_tokens=[MAX_TOKENS],
stop_sequences=["</[TA_BALISE]>"],
messages=[{"role": "user", "content": prompt}],
)
texte = reponse.content[0].text
livrable = texte.split("<[TA_BALISE]>", 1)[-1].strip()Template D - Reprise d'une génération coupée
Ta reponse precedente a ete interrompue. Elle se terminait par :
<fin_precedente>
[LES_200_DERNIERS_CARACTERES_RECUS]
</fin_precedente>
Reprends exactement la ou tu t'es arrete. Ne repete pas ce qui precede.
N'ecris aucune introduction.À retenir
- Le prefill sur le dernier tour assistant renvoie une erreur 400 à partir des modèles Claude 4.6. Il n'est pas non plus compatible avec la réflexion active.
- Pour un format strict, utilise les sorties structurées :
output_config.formatde typejson_schema. C'est GA, sans en-tête beta. - L'ancien paramètre à plat
output_formatest déprécié, déplacé dansoutput_config.format. - Pour supprimer un préambule, une instruction directe suffit aujourd'hui. Dis ce qu'il faut faire, pas ce qu'il ne faut pas faire.
stop_sequencesfonctionne toujours :stop_reasonpasse à"stop_sequence"et la séquence n'apparaît pas dans le texte.temperatureest dépréciée sur les modèles sortis après Opus 4.6 : seule la valeur 1.0 est acceptée. Le déterminisme se pilote par le schéma, plus par la température.