Schéma validé, mais montant de la facture divisé par 10 : comment vérifier le structured output des LLM
Le modèle a renvoyé un JSON parfaitement valide : toutes les clés sont là, les types sont corrects, la citation de la facture est authentique. Sauf que le montant est de 12 500 centimes au lieu de 125 000, soit 125 euros au lieu de 1 250. Ni le parseur ni JSON Schema ne détectent cela. L'auteur détaille trois couches de protection : un contrat Pydantic strict avec recoupement avec le texte source, des retries limités et le constrained decoding.
Traité par IA depuis Machine Learning Mastery ; édité par Hamidun News
Voici une facture de quatre lignes : numéro 2026-0417, payeur Northwind Traders, « Total due: EUR 1,250.00 », à régler sous 30 jours. Vous demandez au modèle d'extraire le numéro, la devise, le montant en centimes et la ligne d'où il a tiré ce montant. La réponse arrive ainsi : montant 12 500, devise EUR, citation « Total due: EUR 1,250.00 ».
Le JSON est valide, toutes les clés sont présentes, les types sont corrects, EUR figure dans la liste autorisée, et la citation existe bien dans la facture. Mais 12 500 centimes font 125 euros, alors que la facture indique 1 250. Une erreur d'un facteur dix qu'aucun parseur ne remarquera : 12500 est un entier tout à fait correct. On ne peut l'attraper qu'en comparant avec le texte source, et cette comparaison, c'est à vous de l'écrire.
Machine Learning Mastery a publié une analyse qui montre comment construire une protection en trois couches. Le structured output est devenu depuis longtemps une fonctionnalité standard : l'API Responses d'OpenAI accepte un schéma JSON ou un modèle Pydantic, et vLLM fait de même sur un serveur local via xgrammar. La plupart des tutoriels s'arrêtent au moment où « le JSON a été parsé ». L'auteur estime que c'est précisément là que tout commence.
Le contrat et le recoupement avec la source
La première couche est un contrat dont l'application est propriétaire, et non le prompt. Dans l'exemple, il s'agit du modèle Invoice à quatre champs. Deux paramètres de configuration font l'essentiel du travail. Le mode strict=True désactive les conversions implicites de Pydantic : la chaîne "125000" dans un champ entier produit une erreur int_type au lieu de devenir silencieusement un nombre. Même chose pour le booléen True, qui deviendrait sinon 1. Le paramètre extra="forbid" transforme une clé superflue en erreur, et non en déchet ignoré.
Autre détail : trois champs acceptent null, mais n'ont pas de valeur par défaut. Le modèle peut dire honnêtement « ce n'est pas dans la facture », mais il ne peut pas se taire, et il ne doit pas inventer un nombre simplement pour éviter d'écrire null. L'argent est stocké en centimes sous forme d'int, afin que 1 250,00 ne devienne pas 1249,9999.
Ensuite, la fonction accept() effectue quatre étapes. D'abord le parsing et la vérification de la forme, seule étape pouvant renvoyer shape_error. Puis la vérification que la citation figure mot pour mot dans la source. Ensuite, le recoupement du numéro de facture et de la devise avec le texte. Enfin, le montant est comparé aux chiffres de la citation : « 1,250.00 » sans séparateurs donne 125000, le modèle a envoyé 12500, l'objet est rejeté. Il y a quatre issues : accepted, needs_review, rejected et shape_error. Si le montant n'est pas indiqué dans la facture, c'est needs_review : c'est un humain qui décide, et non un zéro inventé.
Quand relancer la requête, et quand non
La deuxième couche, ce sont les retries avec une politique stricte. Seul shape_error mérite d'être relancé : on peut demander au modèle de corriger la forme en lui transmettant la liste des erreurs. Si la réponse est rejected, relancer est inutile, car le contenu de la facture ne changera pas. Un refus du fournisseur ou une erreur de configuration arrêtent aussi la boucle immédiatement. Maximum trois tentatives : la première et au plus deux régénérations.
Le retour d'information est limité à cinq motifs, pour que le prompt ne se transforme pas en sténogramme de tous les échecs. Le schéma et le texte source ne changent pas d'une tentative à l'autre. Selon les thèses de l'article, il faut en outre garder sous contrôle les retries de transport cachés du SDK lui-même, faute de quoi le nombre réel de requêtes dépassera celui prévu dans votre budget.
Constrained decoding : utile, mais pas une panacée
La troisième couche est le constrained decoding. Le générateur ne peut physiquement produire que des tokens qui ne font pas sortir la réponse du schéma, si bien que toute une classe d'erreurs de forme n'atteint même pas la validation. Mais le schéma n'exprime pas la règle « les centimes doivent correspondre à la citation ». Un décodeur qui respecte parfaitement le schéma produira quand même ces fameux 12 500. Un objet valide est un laissez-passer vers la vérification suivante, pas un résultat.
Il y a aussi des limites. L'exemple est pédagogique : deux devises, une vérification simple des chiffres. Pour des formats réels, il faut du code qui comprenne vos séparateurs décimaux. Si les factures arrivent sous forme de scans, vérifiez d'abord le texte reconnu : un chiffre erroné en entrée passera toutes les vérifications tant que l'objet est cohérent en interne.
La conclusion pratique est simple. Ne faites pas confiance à la validation « verte » du schéma : conservez dans la réponse une citation-preuve et vérifiez le nombre par rapport à elle. N'utilisez les retries que là où une répétition peut aider, et fixez une limite stricte. Activez le constrained decoding pour économiser sur les erreurs de forme, mais pas à la place de vos propres vérifications. Chaque chemin d'échec mérite d'être couvert par des tests avec un client mock, comme le fait l'auteur.
- 12 nov. 2026Jensen Huang quitte son poste de PDG de Nvidia : l’entreprise perd 1 400 milliards de dollars en deux semaines
- 12 nov. 2026Le syndicat d'Oakland obtient une reconversion plutôt que des licenciements dus à l'IA : 94,1 % pour la grève
- 12 nov. 2026Huit pays, OpenAI et Anthropic promettent un « interrupteur d'arrêt » pour les modèles d'IA, mais sans échéances ni sanctions
- 12 nov. 2026OpenAI et ModRetro lancent une console à 200 $ pour des jeux créés avec Codex
Questions clés sur cet article
Que sait-on sur Le contrat et le recoupement avec la source ?
La première couche est un contrat dont l'application est propriétaire, et non le prompt. Dans l'exemple, il s'agit du modèle Invoice à quatre champs. Deux paramètres de configuration font l'essentiel du travail. Le mode strict=True désactive les conversions implicites de Pydantic : la chaîne "125000" dans un champ entier produit une erreur int_type au lieu de devenir silencieusement un nombre. Même chose pour le booléen True, qui deviendrait sinon 1.
Quand relancer la requête, et quand non?
La deuxième couche, ce sont les retries avec une politique stricte. Seul shape_error mérite d'être relancé : on peut demander au modèle de corriger la forme en lui transmettant la liste des erreurs. Si la réponse est rejected, relancer est inutile, car le contenu de la facture ne changera pas. Un refus du fournisseur ou une erreur de configuration arrêtent aussi la boucle immédiatement. Maximum trois tentatives : la première et au plus deux régénérations.
Que sait-on sur Constrained decoding : utile, mais pas une panacée ?
La troisième couche est le constrained decoding. Le générateur ne peut physiquement produire que des tokens qui ne font pas sortir la réponse du schéma, si bien que toute une classe d'erreurs de forme n'atteint même pas la validation. Mais le schéma n'exprime pas la règle « les centimes doivent correspondre à la citation ». Un décodeur qui respecte parfaitement le schéma produira quand même ces fameux 12 500. Un objet valide est un laissez-passer vers la vérification suivante, pas un résultat.
Test Frontier LLMs & Autonomous Agents in One Workspace
Run Claude Opus 5.5, GPT-6, Gemini 3.1, and DeepSeek side-by-side in Nomad AI (nomadnet.ai) — unified model routing, deep research, and multimodal workflows under one subscription.
L'essentiel de l'IA — une fois par semaine
Sept actus qui ont vraiment compté, choisies à la main. Sans bruit ni communiqués.
C'est fait ! Vérifiez votre boîte mail pour la confirmation.