---
title: "Réponse dans un format imposé"
description: "Obtenir une réponse que le programme peut lire, à tous les coups."
url: https://meridal.fr/apprendre/format-impose/
language: fr
written: 2026-10-07
verified: 2026-10-07
license: CC BY 4.0
---

[Apprendre](https://meridal.fr/apprendre/) › [Construction et programmation](https://meridal.fr/apprendre/dossier/construire/)

S'exercer · Niveau 3, Construire · 20 minutes

# Réponse dans un format imposé

Faire répondre un modèle dans un format que le programme peut lire à tous les coups : décrire le format, le demander à l'éditeur, puis contrôler ce qui revient, parce qu'une réponse bien formée peut être fausse.

Écrit le 7 octobre 2026 · Vérifié le 7 octobre 2026

## Vous saurez

- Décrire un format de réponse avec Pydantic
- Le demander à OpenAI, Anthropic, Google ou Mistral
- Savoir ce que chaque éditeur garantit, et ce qu'il ne garantit pas
- Contrôler la réponse par des règles, sans faire calculer le modèle

## Dans le parcours

[Construction](https://meridal.fr/apprendre/parcours/construire/)

Étape 4 sur 12

Aussi dans : [Premiers pas en développement](https://meridal.fr/apprendre/format-impose/)

- [← Avant Premier appel à un modèle](https://meridal.fr/apprendre/premier-appel/)
- [↓ Plus concret L'appel d'outils](https://meridal.fr/apprendre/appel-d-outils/)
- [→ Ensuite Un RAG minimal en Python](https://meridal.fr/apprendre/rag-minimal-python/)

L'essentiel

1. Un programme ne lit pas une phrase : il lit des champs. Le format imposé lui donne les champs, à chaque réponse.
2. Le format se décrit une fois, avec Pydantic ; les quatre éditeurs l'acceptent.
3. OpenAI et Anthropic garantissent le respect du format ; Google et Mistral le font respecter, et demandent de vérifier.
4. Un format respecté ne veut pas dire un contenu juste : un montant mal lu passe le format sans encombre.
5. Les montants se contrôlent par des règles écrites dans le programme ; le modèle lit, il ne calcule rien.

## Objectif

Extraire d'une facture le fournisseur, le numéro, la date, les lignes et les montants, dans un objet que le programme lit sans erreur, puis arrêter par des règles une lecture fausse.

La facture est fabriquée. L'appel au modèle (étape 2) suit la documentation de chaque éditeur, lue le 7 octobre 2026, et n'a pas encore été exécuté par Meridal. Les étapes 1 et 3 ne font aucun appel : leurs sorties sont réelles.

La suite de [premier appel à un modèle](https://meridal.fr/apprendre/premier-appel/). La même chaîne, à l'échelle d'un cabinet : [la réalisation du dossier de vente](https://meridal.fr/realisations/document-remis-aux-acquereurs/).

## Données fournies

Le texte d'une facture, recopié ici et dans le fichier `facture.py` de l'étape 1 :

FACTURE n° 2026-0412, du 15 septembre 2026 Émise par : Cabinet Exemple, conseil aux entreprises (entreprise fictive) Conseil, 10 heures : 1 000,00 € HT Frais de déplacement : 250,00 € HT Total hors taxe : 1 250,00 € TVA à 20 % : 250,00 € Total TTC : 1 500,00 €

## Consignes

![La facture et son format, la classe Facture décrite avec Pydantic, partent au modèle. Deux lectures ont la même forme : la juste, avec un total TTC de 1 500,00, et une fausse, où il est lu 1 580,00. Les trois règles passent sur la première ; la règle total égale hors taxe plus TVA arrête la seconde, mise de côté pour qu'une personne la regarde.](https://meridal.fr/assets/apprendre/medias/apprendre-format-impose.svg)

*Le format donne la forme ; la règle arrête la lecture fausse.*

### Étape 1. Décrire le format

Une classe Pydantic par objet : chaque champ a un nom et un type ; une description dit au modèle ce qu'on attend, comme le format de la date. Les règles de contrôle vivent dans le même fichier.

`facture.py`

```
from pydantic import BaseModel, Field

TEXTE = """FACTURE n° 2026-0412, du 15 septembre 2026
Émise par : Cabinet Exemple, conseil aux entreprises (entreprise fictive)
Conseil, 10 heures : 1 000,00 € HT
Frais de déplacement : 250,00 € HT
Total hors taxe : 1 250,00 €
TVA à 20 % : 250,00 €
Total TTC : 1 500,00 €"""

class Ligne(BaseModel):
    libelle: str
    montant_ht: float

class Facture(BaseModel):
    fournisseur: str
    numero: str
    date: str = Field(description="Date de la facture, au format AAAA-MM-JJ")
    lignes: list[Ligne]
    total_ht: float
    taux_tva: float = Field(description="Taux de TVA en pour cent, par exemple 20")
    total_tva: float
    total_ttc: float

def controler(f, ecart=0.01):
    """Les règles qu'une facture juste respecte. Le modèle lit ; il ne calcule rien."""
    regles = [
        ("Hors taxe = somme des lignes", sum(l.montant_ht for l in f.lignes), f.total_ht),
        ("TVA = hors taxe × taux", f.total_ht * f.taux_tva / 100, f.total_tva),
        ("Total = hors taxe + TVA", f.total_ht + f.total_tva, f.total_ttc),
    ]
    return [(nom, abs(calcule - lu) <= ecart, calcule, lu) for nom, calcule, lu in regles]
```

Ce que Pydantic en tire, et qui part à l'éditeur : un schéma JSON, où chaque champ est obligatoire.

`schema.py`

```
import json

from facture import Facture

print(json.dumps(Facture.model_json_schema()["required"], ensure_ascii=False))
print(json.dumps(Facture.model_json_schema()["properties"]["date"], ensure_ascii=False))
```

La sortie`uv run schema.py`

```
["fournisseur", "numero", "date", "lignes", "total_ht", "taux_tva", "total_tva", "total_ttc"]
{"description": "Date de la facture, au format AAAA-MM-JJ", "title": "Date", "type": "string"}
```

Essayé le 7 octobre 2026 avec Python 3.12.7 et Pydantic 2.6.0.

### Étape 2. Demander le format

OpenAI Anthropic Google Mistral

`responses.parse` envoie le format, et rend un objet `Facture` déjà lu, dans `output_parsed`.

`extraire.py`

```
from dotenv import load_dotenv
load_dotenv()

from openai import OpenAI

from facture import TEXTE, Facture

client = OpenAI()
response = client.responses.parse(
    model="gpt-6-luna",
    input=[
        {"role": "system", "content": "Extrais les informations de la facture."},
        {"role": "user", "content": TEXTE},
    ],
    text_format=Facture,
)
facture = response.output_parsed
print(facture)
```

La sortie`uv run extraire.py`

**Pas encore exécuté par Meridal.** Ce qu'on attend : une Facture avec le fournisseur, le numéro 2026-0412, la date 2026-09-15, deux lignes et les quatre montants de la facture.

`messages.parse` envoie le format, et rend l'objet lu dans `parsed_output`.

`extraire.py`

```
from dotenv import load_dotenv
load_dotenv()

import anthropic

from facture import TEXTE, Facture

client = anthropic.Anthropic()
response = client.messages.parse(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Extrais les informations de cette facture :\n\n" + TEXTE}],
    output_format=Facture,
)
facture = response.parsed_output
print(facture)
```

La sortie`uv run extraire.py`

**Pas encore exécuté par Meridal.** Ce qu'on attend : une Facture avec le fournisseur, le numéro 2026-0412, la date 2026-09-15, deux lignes et les quatre montants de la facture.

Le format part sous forme de schéma JSON ; la réponse est du JSON, que Pydantic relit et vérifie (`model_validate_json`).

`extraire.py`

```
from dotenv import load_dotenv
load_dotenv()

from google import genai

from facture import TEXTE, Facture

client = genai.Client()
interaction = client.interactions.create(
    model="gemini-3.8-flash",
    input="Extrais les informations de cette facture :\n\n" + TEXTE,
    response_format={
        "type": "text",
        "mime_type": "application/json",
        "schema": Facture.model_json_schema(),
    },
)
facture = Facture.model_validate_json(interaction.output_text)
print(facture)
```

La sortie`uv run extraire.py`

**Pas encore exécuté par Meridal.** Ce qu'on attend : une Facture avec le fournisseur, le numéro 2026-0412, la date 2026-09-15, deux lignes et les quatre montants de la facture.

`chat.parse` envoie le format, et rend l'objet lu dans `choices[0].message.parsed`.

`extraire.py`

```
from dotenv import load_dotenv
load_dotenv()

import os
from mistralai.client import Mistral

from facture import TEXTE, Facture

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])
reponse = client.chat.parse(
    model="mistral-large-latest",
    messages=[
        {"role": "system", "content": "Extrais les informations de la facture."},
        {"role": "user", "content": TEXTE},
    ],
    response_format=Facture,
    max_tokens=1024,
    temperature=0,
)
facture = reponse.choices[0].message.parsed
print(facture)
```

La sortie`uv run extraire.py`

**Pas encore exécuté par Meridal.** Ce qu'on attend : une Facture avec le fournisseur, le numéro 2026-0412, la date 2026-09-15, deux lignes et les quatre montants de la facture.

### Étape 3. Contrôler

Trois règles qu'une facture juste respecte, appliquées à deux lectures de la même facture : la bonne, et une où le total TTC a été lu 1 580 au lieu de 1 500. Les deux lectures sont fabriquées ; le contrôle, lui, a tourné.

`controle.py`

```
from facture import Facture, Ligne, controler

# deux lectures de la même facture : la bonne, et une où un 0 a été lu comme un 8
lignes = [Ligne(libelle="Conseil, 10 heures", montant_ht=1000.0),
          Ligne(libelle="Frais de déplacement", montant_ht=250.0)]
juste = Facture(fournisseur="Cabinet Exemple", numero="2026-0412", date="2026-09-15", lignes=lignes,
                total_ht=1250.0, taux_tva=20.0, total_tva=250.0, total_ttc=1500.0)
fausse = juste.model_copy(update={"total_ttc": 1580.0})

for nom, f in (("Lecture juste", juste), ("Lecture fausse", fausse)):
    print(nom)
    for regle, passe, calcule, lu in controler(f):
        print(f"  {'passe ' if passe else 'ÉCHOUE'} {regle} : {calcule:.2f} attendu, {lu:.2f} lu")
```

La sortie`uv run controle.py`

```
Lecture juste
  passe  Hors taxe = somme des lignes : 1250.00 attendu, 1250.00 lu
  passe  TVA = hors taxe × taux : 250.00 attendu, 250.00 lu
  passe  Total = hors taxe + TVA : 1500.00 attendu, 1500.00 lu
Lecture fausse
  passe  Hors taxe = somme des lignes : 1250.00 attendu, 1250.00 lu
  passe  TVA = hors taxe × taux : 250.00 attendu, 250.00 lu
  ÉCHOUE Total = hors taxe + TVA : 1500.00 attendu, 1580.00 lu
```

Essayé le 7 octobre 2026 avec Python 3.12.7 et Pydantic 2.6.0.

Les deux lectures ont la bonne forme : tous les champs, au bon type. Seule la règle arrête la seconde. Une facture qui échoue se met de côté avec la règle en échec, pour qu'une personne la regarde.

## Critères de réussite

- `extraire.py` affiche une Facture, sans erreur de lecture
- La date sort au format AAAA-MM-JJ : 2026-09-15
- Les trois règles passent sur la facture lue si l'une échoue, relisez le texte : le modèle s'est trompé, ou la facture est fausse
- Aucun montant n'est calculé par le modèle : il recopie ce qui est écrit

### Garanties de chaque éditeur

| Éditeur | Ce qu'il promet | Ce qu'il dit de vérifier |
|---|---|---|
| OpenAI | La réponse respecte le schéma ; le simple « mode JSON » ne garantit qu'un JSON valide | Le contenu peut contenir des erreurs ; un refus ou une réponse coupée peut sortir du format |
| Anthropic | Des réponses conformes au schéma, par un décodage contraint | Un refus ou une coupure à `max_tokens` peut sortir du format ; pas de contraintes numériques (minimum, maximum) dans le schéma envoyé |
| Google | Des réponses qui suivent un schéma JSON | Le JSON est valide, mais les valeurs restent à vérifier |
| Mistral | Le schéma fourni est imposé ; plus fiable que le mode JSON | Pas de garantie écrite comme chez OpenAI et Anthropic ; continuer à ajuster la demande |

## Indications

**OpenAI refuse le schéma**

**Cause**Chez OpenAI, chaque champ doit être obligatoire, la racine doit être un objet, et chaque objet doit interdire les champs en plus ; certains mots du schéma ne sont pas acceptés.

**À faire**Passez par `responses.parse` avec la classe Pydantic, qui écrit le schéma comme il faut ; un champ facultatif s'écrit « type ou null ».

**La première demande est lente**

**Cause**Un nouveau schéma se prépare à la première demande ; chez Anthropic, il reste prêt 24 heures après son dernier usage.

**À faire**Rien : les demandes suivantes vont plus vite.

**Chez Anthropic, les citations ne marchent plus**

**Cause**Le format imposé ne se combine pas avec les citations.

**À faire**Choisir : un objet à lire, ou une réponse qui cite ses sources.

**La réponse est coupée**

**Cause**Le plafond de tokens est trop bas pour l'objet entier.

**À faire**Relevez `max_tokens`, et traitez une réponse incomplète comme une erreur.

## Correction commentée

**Ouvrir la correction**

**La forme**Les quatre éditeurs reçoivent le même format, tiré de la même classe : seuls le nom de la méthode et l'endroit où lire l'objet changent.

**Le fond**La date demande une conversion (« 15 septembre 2026 » en 2026-09-15) : c'est le rôle de la description du champ. Les montants, eux, se recopient tels qu'ils sont écrits ; le programme les vérifie ensuite, et ne demande jamais au modèle de les calculer.

**Pourquoi les règles**Le format garantit la forme, pas la justesse : une lecture fausse a exactement la même forme qu'une bonne. Dans la chaîne du dossier de vente (réalisation R3), un chiffre douteux est mis de côté, jamais comblé.

## Extensions

- **Des montants exacts** : un nombre à virgule flottante arrondit ; pour la comptabilité, recopier le montant en texte et le convertir en `Decimal` dans le programme.
- **Un lot de factures** : la même chaîne sur un dossier entier, avec la liste des factures mises de côté ; c'est [le projet d'extraction de factures](https://meridal.fr/apprendre/projet-extraction-de-factures/).
- **Le document lui-même** : lire un PDF ou une image plutôt qu'un texte recopié, avec les limites que dit [l'extraction des chiffres d'un document](https://meridal.fr/apprendre/extraction-des-chiffres/).

## Lectures suivantes

↑ Plus général

Rien avant : c'est un point de départ.

← Avant [Premier appel à un modèle S'exercer · Niveau 3 · 15 minutes](https://meridal.fr/apprendre/premier-appel/)

Vous êtes ici

Réponse dans un format imposé

→ Ensuite [Un RAG minimal en Python S'exercer · Niveau 3 · 25 minutes](https://meridal.fr/apprendre/rag-minimal-python/)

Construction · étape 5 sur 12

↓ Plus concret [L'appel d'outils S'exercer · Niveau 3 · 20 minutes](https://meridal.fr/apprendre/appel-d-outils/) [Un modèle commun pour le dossier de vente, et des chiffres repris sans ressaisie Réalisation](https://meridal.fr/realisations/document-remis-aux-acquereurs/)

[Tout le dossier « Construction et programmation »](https://meridal.fr/apprendre/dossier/construire/)

Les pages qui renvoient ici

- [Choix d'une API et d'une bibliothèque](https://meridal.fr/apprendre/choix-d-une-api-et-d-une-bibliotheque/)
- [Collecte de données publiques](https://meridal.fr/apprendre/collecte-de-donnees-publiques/)
- [Fiche mémo : construire](https://meridal.fr/apprendre/fiche-memo-construire/)
- [Jeux de tests d'une application d'IA](https://meridal.fr/apprendre/jeux-de-tests/)
- [L'appel d'outils](https://meridal.fr/apprendre/appel-d-outils/)
- [Observabilité : traces, coût et délai](https://meridal.fr/apprendre/observabilite/)

**Limites et sources**

L'appel de l'étape 2 suit la documentation des éditeurs du 7 octobre 2026 et n'a pas encore été exécuté par Meridal. Les étapes 1 et 3 ont tourné avec Python 3.12.7 et Pydantic 2.6.0 ; la version publiée de Pydantic est la 2.13.5.

La facture et les deux lectures du contrôle sont fabriquées.

1. [OpenAI, Structured Outputs](https://developers.openai.com/api/docs/guides/structured-outputs) ; [Anthropic, Structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs) ; [Google, Structured output](https://ai.google.dev/gemini-api/docs/structured-output) ; [Mistral, Custom structured outputs](https://docs.mistral.ai/studio/conversations/structured-output/custom).
2. Le relevé complet : `apprendre/recherches/2026-10-07-dev-appels-modeles.json`.

Écrit par Meridal, avec l'aide de l'IA ; chaque fait vérifié à sa source et relu avant publication. Texte et schémas sous licence [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.fr) : reprenez-les en citant Meridal. Le code sous licence MIT.

Service correspondant

Préparer les documents qui reviennent

1 650 € à 5 100 €, puis sur devis

[Voir le service](https://meridal.fr/offre/#documents)

## Un premier échange de 30 minutes

En visio et sans frais : vous décrivez votre situation, nous vous indiquons ce qui est réalisable, avec quels outils et pour quel ordre de prix. Vous pouvez aussi nous écrire ; chaque message reçoit une réponse écrite.

[Réserver 30 minutes](https://meridal.fr/contact/#reserver) [Écrire un message](https://meridal.fr/contact/)
