Aller au contenu

S'exercerNiveau 3, Construire20 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

Étape 4 sur 12

Aussi dans : Premiers pas en développement

Sur cette page
  1. L'essentiel
  2. Objectif
  3. Données fournies
  4. Consignes
  5. Critères de réussite
  6. Indications
  7. Correction commentée
  8. Extensions
  9. Lectures suivantes
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.
1

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.

2

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 €
3

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.
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 sortieuv 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

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 sortieuv 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 sortieuv 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 luLecture 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.

4

Critères de réussite

Garanties de chaque éditeur

Garanties de chaque éditeur
ÉditeurCe qu'il prometCe qu'il dit de vérifier
OpenAILa réponse respecte le schéma ; le simple « mode JSON » ne garantit qu'un JSON valideLe contenu peut contenir des erreurs ; un refus ou une réponse coupée peut sortir du format
AnthropicDes réponses conformes au schéma, par un décodage contraintUn refus ou une coupure à max_tokens peut sortir du format ; pas de contraintes numériques (minimum, maximum) dans le schéma envoyé
GoogleDes réponses qui suivent un schéma JSONLe JSON est valide, mais les valeurs restent à vérifier
MistralLe schéma fourni est imposé ; plus fiable que le mode JSONPas de garantie écrite comme chez OpenAI et Anthropic ; continuer à ajuster la demande
5

Indications

OpenAI refuse le schéma

CauseChez 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.

À fairePassez 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

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

À faireRien : les demandes suivantes vont plus vite.

Chez Anthropic, les citations ne marchent plus

CauseLe format imposé ne se combine pas avec les citations.

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

La réponse est coupée

CauseLe plafond de tokens est trop bas pour l'objet entier.

À faireRelevez max_tokens, et traitez une réponse incomplète comme une erreur.

6

Correction commentée

Ouvrir la correction

La formeLes 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 fondLa 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èglesLe 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é.

7

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.
  • 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.
8

Lectures suivantes

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 ; Anthropic, Structured outputs ; Google, Structured output ; Mistral, Custom structured outputs.
  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 : 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

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.