IAMaîtriser l'IA générative Plan du corpus
Accueil/Prompt engineering : techniques avancees/Prefill et contrôle du format de sortie

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_sequences pour 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 assistant ailleurs 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 prefillRemplacement actuel
Forcer un format JSON / YAMLSorties structurées (output_config.format) ou tool use
Forcer une classificationUn 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 abusifPrompt clair dans le message user ; les refus sont mieux calibrés
Reprendre une génération interrompueDéplacer la reprise dans le message user
Réinjecter du contexte régulièrementMessage 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é dans output_config.format. Si tu l'utilises encore, il te faut l'en-tête beta structured-outputs-2025-11-13, sinon l'API renvoie une erreur 400. Et le SDK Python (v1.0 et suivantes) lève une TypeError si tu passes output_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: true contraint 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_reason vaut "stop_sequence" et le champ stop_sequence de 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 :

  1. 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 champ reponse.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 un print(repr(reponse.content[0].text)) avant de construire un parseur dessus.
  2. Empêcher le modèle d'inventer la suite d'un dialogue. Dans un format Question: / Reponse:, tu mets "\nQuestion:" en stop sequence.
  3. 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 objectifLa technique actuelle
Sortie au format exactSorties structurées (output_config.format)
Appels d'outils conformesstrict: true sur la définition d'outil
Moins de variabilité de styleConsignes précises + exemples (few-shot)
Moins de bavardageeffort 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.format de type json_schema. C'est GA, sans en-tête beta.
  • L'ancien paramètre à plat output_format est déprécié, déplacé dans output_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_sequences fonctionne toujours : stop_reason passe à "stop_sequence" et la séquence n'apparaît pas dans le texte.
  • temperature est 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.

Sources

Corpus personnel de formation · genere le 26/09/2026 · source : 06-prefill-et-controle.md