Une sortie structurée n'est pas une vérité : ce qu'un schéma JSON garantit, et ses limites
Correction, octobre 2026 : une précédente version de cet article présentait des chiffres pour une comparaison de six modèles et renvoyait vers un banc d’essai que je ne peux ni fournir ni vérifier. J’ai retiré ces chiffres et les recommandations qui en découlaient. Cette version présente un exemple exécutable et distingue la conformité au schéma de l’exactitude des données.
Quand un agent transmet sa réponse à un programme, un JSON valide est indispensable. Mais sortie structurée recouvre trois questions : les octets peuvent-ils être analysés ? Les champs respectent-ils le contrat ? Les valeurs sont-elles étayées par le texte d’entrée ? Un modèle peut réussir les deux premières étapes et échouer à la troisième.
Commencer par le contrat
Un programme doit extraire d’un court rapport une priorité, un nombre de personnes concernées et un résumé. Voici un exemple avec Pydantic 2 ; installez-le avec python -m pip install 'pydantic>=2,<3' pour exécuter le code :
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class Extraction(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")
priority: Literal["low", "medium", "high"]
affected_users: int = Field(ge=0)
summary: str
source = "The report says no users were affected. Priority is low."
shape_valid_but_wrong = (
'{"priority":"low","affected_users":12,'
'"summary":"No users were affected."}'
)
record = Extraction.model_validate_json(shape_valid_but_wrong)
print(record.affected_users) # 12 : le schéma l'accepte
print(record.affected_users == 0) # False : le rapport dit le contraire
try:
Extraction.model_validate_json(
'{"priority":"low","affected_users":"twelve",'
'"summary":"No users were affected."}'
)
except ValidationError:
print("Type validation rejected the string count")
Le premier objet contient les champs et types attendus. Pourtant, 12 contredit le rapport. Pydantic remplit son rôle : il vérifie le contrat déclaré, pas les preuves. Le second objet est rejeté parce que le nombre est une chaîne de caractères. extra="forbid" refuse aussi les champs inattendus. La documentation du mode strict explique les subtilités de la conversion automatique des types.
Ce code illustre un problème ; ce n’est pas un benchmark de modèles. Aucun modèle de langage n’est exécuté ici.
Le rôle d’une grammaire
Un décodeur contraint limite les prochains tokens pour respecter une grammaire ou un schéma. Outlines décrit ses types de sortie JSON Schema et llama.cpp documente ses grammaires. Ces outils sont utiles quand le programme suivant attend une forme précise. Ils ne vérifient ni la présence d’un nombre dans la source, ni la fidélité d’un résumé, ni la justification d’une priorité.
Demander « réponds en JSON » dans un prompt n’est pas équivalent à contraindre le décodeur. Le prompt formule une demande ; la grammaire limite les tokens possibles. Il faut préciser ce qui a été testé. Validez aussi la réponse complète à la frontière de l’application.
Ce qu’exigerait une vraie comparaison
Pour comparer des modèles, publiez les prompts, les réponses attendues, le schéma, les fichiers de modèles et quantifications exacts, la version du moteur d’inférence, les réglages du décodage, le code de validation et les sorties brutes. Indiquez le nombre d’essais et le traitement des réponses ambiguës. Séparez au moins ces échecs :
- Échec de l’analyse : la sortie n’est pas du JSON valide.
- Échec du contrat : le JSON est lisible, mais les champs, types, plages ou valeurs permises ne conviennent pas.
- Échec de la preuve : le contrat est respecté, mais une valeur est absente de la source ou la contredit.
- Échec opérationnel : expiration du délai, troncature ou réponse incomplète.
Un taux de réussite au premier essai n’a de sens que si le numérateur, le dénominateur et la définition de l’échec sont explicites. Conformité au schéma et extraction factuellement juste sont deux mesures différentes. Quand une grammaire élimine les fautes de syntaxe, l’évaluation doit se concentrer sur la qualité des preuves.
En pratique
Définissez un petit schéma, contraignez la génération si votre moteur le permet, puis analysez et validez à nouveau à l’entrée de l’application. Comparez les champs importants avec la source ou un système de confiance. Rendez les échecs visibles ; réessayez ou escaladez selon leur impact. Ne transformez pas silencieusement « bien formé » en « vrai ».
Sans comparaison reproductible, je ne recommande aucun modèle local particulier pour cet usage. La conclusion solide est plus étroite : la structure relève du contrat de sortie ; la justesse est une affirmation sur le monde. Testez les deux.