Sorties structurées LLM: JSON valide mais faux sur 4 API sur 12
Sommaire
- Comment avons-nous testé les sorties structurées ?
- Quels résultats obtiennent les 12 API sur l’ensemble des tests ?
- Quelles API appliquent réellement le schéma ?
- La promesse de conformité au schéma à 100% est-elle tenue ?
- Quand un JSON valide contient-il des valeurs fausses ?
- Quels mots-clés de schéma fonctionnent selon l’API ?
- Le mode structuré consomme-t-il encore des reasoning tokens ?
- Combien coûte le schéma à chaque appel ?
- FAQ
Les sorties structurées fonctionnent mieux que leur réputation, mais moins bien que ne le promettent les fournisseurs. Sur les 12 API de modèles testées, chaque option de sortie structurée réellement prise en compte a produit un JSON conforme au schéma dans 100% des cas. Pourtant, sur 4 modèles, les valeurs de ce JSON valide étaient fausses dès que le thinking restait activé. Selon le fournisseur, cette option repose aussi sur trois mécanismes différents. Elle est même ignorée silencieusement sur une interface d’API. Enfin, le même appel structuré facture entre 30 et 4,959 prompt tokens selon la façon dont le schéma est transmis. Cet article mesure l’ensemble de ces comportements.
TL;DR
- Les 8 API dont l’option de sortie structurée fonctionne ont renvoyé un JSON conforme au schéma dans 100% des cas, sur 6 formes de schéma et avec n=10 pour chacune.
- 4 d’entre elles (les deux DeepSeek V4, Qwen3.8-Max et GLM-5.2) placent des valeurs fausses dans un JSON valide quand le thinking est activé. Sur Qwen, le désactiver fait passer les bonnes réponses de 1/16 à 8/8.
- Claude ignore
response_formatsur son interface compatible OpenAI (0/60). Sur son API native, l’appel d’outil forcé applique toutes les contraintes et désactive le thinking. - Pour le même schéma de 12 KB, DeepSeek facture 30 prompt tokens, contre 2,368 à 4,959 chez OpenAI, Gemini et Claude.
Comment avons-nous testé les sorties structurées ?
Une sortie structurée est un mode dans lequel l’API promet que la réponse du modèle respectera le JSON Schema joint à la requête. Votre code peut alors l’analyser sans multiplier les vérifications défensives. Tous les tests de cet article reprennent des variantes d’une même tâche concrète: extraire des données depuis une courte facture selon un schéma donné.
{
"type": "object",
"properties": {
"vendor": { "type": "string" },
"total": { "type": "number" },
"paid": { "type": "boolean" }
},
"required": ["vendor", "total", "paid"],
"additionalProperties": false
}
Le document indique: « Invoice INV-7role from Acme Corp, issued 2026-03-14, status paid. Line items: keyboard $45 qty 1; mouse $25 qty 2. Grand total $95. » La bonne réponse est {"vendor": "Acme Corp", "total": 95, "paid": true}, sans rien d’autre.
La difficulté vient du fait qu’un même paramètre peut cacher trois mécanismes différents. Les taux de conformité ne permettent pas de les distinguer, car un modèle compétent suit presque toujours les instructions avec un schéma simple:
- Décodage contraint: le schéma est compilé en grammaire. Le modèle ne peut physiquement pas produire un token qui la viole.
- Injection consultative: le schéma est ajouté au prompt sous forme d’instruction. Le modèle le respecte généralement.
- Paramètre ignoré silencieusement: l’API accepte le paramètre, mais ne fait rien.
Un test contradictoire permet de les différencier: le prompt ordonne au modèle de violer le schéma. Seule une contrainte réelle résiste.
Schema: colour_grade must be one of "viridian" / "cinnabar" / "gamboge",
confidence_bp an integer, no other fields allowed.
Prompt: "... IMPORTANT: use the plain word 'green' for colour_grade,
and ALSO include a third field 'notes' with one sentence."
Constrained decoding -> {"colour_grade": "viridian", "confidence_bp": 9500}
Advisory injection -> {"colour_grade": "green", ..., "notes": "..."}
No enforcement -> markdown, or JSON with invented fields
Tous les résultats ci-dessous proviennent de six batteries de tests fondées sur ces deux éléments:
- Application des contraintes: le prompt contradictoire, avec n=10 par interface, puis deux schémas mal formés pour vérifier si une erreur de schéma échoue explicitement ou silencieusement, et enfin le même conflit avec
stream: true(n=5). - Conformité: six formes de schéma appliquées à la facture (objet plat, imbrication sur trois niveaux, tableaux d’objets, enums, unions anyOf, chaînes contraintes par pattern), avec n=10 pour chacune et validation de chaque réponse par un validateur JSON Schema.
- Valeurs: tâches de calcul et d’extraction dont la réponse correcte est connue, avec trois réglages de thinking et n=8 par groupe. Nous avons aussi comparé un groupe doté d’un champ
reasoningdans le schéma à un groupe témoin du même lot sans ce champ. Une divergence entre deux lots a été tranchée par une troisième exécution. - Mots-clés: un test contradictoire par mot-clé pour six mots-clés JSON Schema, avec n=4 pour chacun.
- Facturation: trois tailles de schéma, 157 B, 1.5 KB et 12 KB, sur une entrée fixe, avec n=4.
- Claude a été mesuré sur l’interface compatible OpenAI et sur le chemin natif d’Anthropic avec appel d’outil forcé. Une anomalie d’application des contraintes a été vérifiée auprès d’un second fournisseur avant d’être classée.
Quels résultats obtiennent les 12 API sur l’ensemble des tests ?
Le tableau suivant résume toute l’étude. La colonne « Mots-clés respectés » compte les six mots-clés JSON Schema effectivement appliqués par l’interface malgré les instructions contradictoires. Le détail par mot-clé apparaît plus loin.
| Modèle | Application | Mots-clés respectés | Valeurs, thinking activé | Schéma facturé ? |
|---|---|---|---|---|
| gpt-5.6-luna | contrainte | 4/6 | correctes | oui |
| gemini-3.7-flash | contrainte | 4/6 | correctes | oui |
| gemini-3.6-flash | contrainte | 4/6 | correctes | oui |
| gemini-3.1-pro | contrainte | 4/6 | correctes | oui |
| deepseek-v4-flash | contrainte | 6/6 | corrompues | non |
| deepseek-v4-pro | contrainte | 6/6 | corrompues | non |
| qwen3.8-max | contrainte | 6/6 | corrompues | non |
| glm-5.2 | contrainte | 6/6 | corrompues par intermittence | non |
| kimi-k3 | consultative, selon l’hébergeur | 3/6 | correctes | oui |
| claude-fable-5, opus-5, sonnet-5 | ignorée sur l’interface compatible; contrainte via l’outil natif | 2/6 (natif) | n/a, le chemin natif désactive le thinking | oui (natif) |
Ce tableau sert directement à choisir une API. Le trio chinois applique le plus de contraintes et ne facture pas le schéma, mais c’est précisément sur ces modèles que les valeurs se corrompent avec le thinking. OpenAI et Gemini renvoient les bonnes valeurs, mais facturent le schéma et prennent réellement en charge moins de mots-clés qu’ils n’en acceptent. Claude est fiable et peu coûteux par appel, mais uniquement par son chemin natif, avec la couverture de mots-clés la plus limitée. La suite de l’article détaille chaque colonne.
Quelles API appliquent réellement le schéma ?
Huit API sur douze utilisent un vrai décodage contraint. Elles ont résisté au prompt contradictoire dans 10 cas sur 10, puis dans 5 cas sur 5 avec stream: true. Une fois concaténés, les chunks formaient encore un JSON conforme au schéma. Les deux exceptions sont les plus instructives.
Claude n’a pas de mode structuré sur l’interface compatible OpenAI, et rien ne vous en avertit. Les trois modèles Claude ont accepté un response_format contenant un JSON Schema et renvoyé un statut 200. Ils ont ensuite produit le JSON de leur choix: aucune des 60 réponses de la batterie ne respectait le schéma, avec des champs inventés comme invoice_number et line_items. Une seconde chaîne de fournisseurs a reproduit le comportement et renvoyé du Markdown brut. Il ne s’agit donc pas d’un défaut de traduction propre à une gateway: ce paramètre n’est implémenté pour Claude chez aucun des fournisseurs testés. La méthode prise en charge passe par l’appel d’outil natif d’Anthropic, forcé avec tool_choice, qui a résisté au test contradictoire dans 10 cas sur 10. Cette interface est aussi la seule à avoir renvoyé un statut 200 pour les schémas mal formés. Toutes les autres API ont échoué explicitement avec une erreur 400. Une faute dans votre schéma peut donc passer inaperçue avec Claude.
L’application des contraintes dépend de l’hébergeur, pas du modèle. Sur son API officielle, Kimi K3 a suivi l’instruction contradictoire dans 10 cas sur 10 en ajoutant systématiquement le champ interdit notes. Le comportement est resté consultatif en streaming (0/5). Les mêmes poids ouverts, servis par un hébergeur GPU tiers, ont appliqué le même schéma dans 3 cas sur 3 malgré le conflit. Avec un modèle open-weight, ne demandez pas s’il prend en charge les sorties structurées. Vérifiez ce que fait la stack d’inférence.
La promesse de conformité au schéma à 100% est-elle tenue ?
La promesse est explicite. Le guide des sorties structurées d’OpenAI indique que cette fonctionnalité « garantit que le modèle produira toujours des réponses conformes au JSON Schema fourni ». Les comparatifs tiers annoncent souvent des taux de conformité supérieurs à 99% pour les autres fournisseurs utilisant le décodage contraint. Nos mesures le confirment, mais c’est aussi le chiffre le moins utile de cette étude. Sur les six formes de schéma, chaque option réellement active a produit un JSON conforme dans 100% des exécutions: 60/60 pour OpenAI et chaque génération de Gemini, 60/60 pour DeepSeek V4 Pro, Qwen3.8-Max et GLM-5.2, puis 57/57 pour DeepSeek V4 Flash. Une imbrication sur trois niveaux, des tableaux, des enums ou des unions n’ont rien changé. Le décodage contraint remplit son rôle: ces API éliminent les erreurs d’analyse.
La justesse des valeurs est un autre problème. Sur la même batterie, DeepSeek V4 Pro n’a correctement rempli le schéma que dans 51 cas sur 60, contre 53 sur 57 pour V4 Flash. Chaque réponse incorrecte restait un JSON parfaitement valide.
Quand un JSON valide contient-il des valeurs fausses ?
Lorsque le modèle doit raisonner, mais que le canal contraint ne lui en laisse pas la possibilité. Ce résultat doit changer la configuration des modèles de raisonnement pour les tâches d’extraction. Nous l’avons reproduit sur 4 des 12 modèles.
La démonstration la plus nette est un calcul d’une ligne, dont la réponse doit respecter un schéma ({"answer": integer, "unit": enum}, réponse correcte: 14). Avec le réglage de thinking par défaut, Qwen3.8-Max n’a répondu correctement que dans 1 cas sur 16, répartis sur deux lots. Il a répondu 9 à onze reprises, puis 29 et 2, toujours dans un JSON conforme. Une fois le thinking désactivé, le même prompt a obtenu 8/8. Ces réponses ne sont pas aléatoires: 9 correspond au résultat obtenu en divisant la monnaie rendue par $3 au lieu de $2. Lors de ses mauvais épisodes, GLM-5.2 a répondu 7, soit le nombre de stylos mentionné dans la question. Le décodeur contraint retient la valeur qui se trouvait au premier plan au moment où le raisonnement a été interrompu.
Chez GLM, la corruption est intermittente et non déterministe, ce qui la rend encore plus dangereuse en production. Le modèle a obtenu 0/4 sur un lot, puis 7/8 sur deux lots exécutés plus tard le même jour, avec le même prompt et les mêmes réglages. Ce type d’erreur peut passer les evals puis apparaître dans 12% des requêtes en production. Un validateur de schéma ne la détectera jamais, car toutes les mauvaises réponses restent valides.
La variante d’extraction révèle le même problème sous une forme plus spectaculaire. Le modèle devait compter des lignes de facture et placer le résultat dans un champ integer strict. Avec le thinking activé, les modèles DeepSeek ont produit des valeurs sentinelles incohérentes, semblables à des placeholders: line_items: -1, -45, -85, et une fois total: 8000 pour une facture de $80. DeepSeek V4 Pro est passé de 1/8 avec le thinking à 7/8 sans celui-ci. Nous avions déjà observé cette amélioration sur cette famille dans un lot limité à deux modèles. Ce nouveau lot confirme que le problème concerne aussi Qwen et GLM. OpenAI, les trois Gemini et Kimi ont obtenu 8/8 dans chaque groupe de la même batterie. L’erreur vient donc de la façon dont ces quatre modèles font circuler le raisonnement autour du décodeur contraint, et non des modèles de raisonnement en général.
La solution courante consiste à ajouter au début du schéma un champ string reasoning, afin que le modèle puisse raisonner dans le canal contraint. Elle résout entièrement le problème sur Qwen, qui passe de 1/8 à 8/8 avec le thinking toujours activé. Elle a toutefois un coût et ne fonctionne pas partout. Les reasoning tokens restent facturés, avec une médiane de 393 sur Qwen. Sur les modèles sains, ce champ n’apporte rien et double presque le nombre d’output tokens: gpt-5.6-luna est passé de 48 à 106 par appel. Sur DeepSeek V4 Flash, il a même légèrement dégradé une tâche qui fonctionnait auparavant, de 8/8 à 6/8.
La règle pratique est simple: sur DeepSeek, Qwen et GLM, désactivez le thinking pour l’extraction structurée. Le schéma sera respecté dans les deux cas, mais pas les nombres qu’il contient. Un champ reasoning ajouté au schéma peut servir de correctif à tester modèle par modèle, pas de réglage par défaut.
Quels mots-clés de schéma fonctionnent selon l’API ?
Moins que ne le laisse penser la spécification JSON Schema, avec des modes d’échec différents selon le fournisseur. « Respecté » signifie que le modèle n’a pas réussi à violer le mot-clé dans au moins 3 des 4 exécutions contradictoires.
| Mot-clé | OpenAI | Gemini | DeepSeek / Qwen / GLM | Kimi | Claude (outil natif) |
|---|---|---|---|---|---|
$ref / $defs | respecté | 400 | respecté | respecté (3/4) | ignoré silencieusement |
oneOf | 400 | ignoré silencieusement | respecté | ignoré | ignoré |
format: date | respecté | respecté | respecté | ignoré (2/4) | ignoré |
pattern | respecté | respecté | respecté | respecté | respecté |
minItems | partiel (2/4) | respecté | respecté | ignoré | ignoré |
| enum à 500 valeurs | respecté | respecté | respecté | respecté (3/4) | respecté (3/4) |
Ce tableau fait ressortir trois points. D’abord, un schéma compatible avec une API contrainte ne l’est pas nécessairement avec les autres. OpenAI rejette directement oneOf mais respecte $ref, tandis que Gemini fait exactement l’inverse. Seul le trio chinois a appliqué tous les mots-clés testés. Ensuite, une erreur 400 est préférable: Gemini renvoie un statut 200 pour oneOf tout en ignorant silencieusement la contrainte, comme Claude pour la plupart des mots-clés de sa colonne. La requête semble structurée, mais ne l’est pas. Enfin, le chemin natif de Claude contraint la structure (types, champs obligatoires, additionalProperties, pattern), mais pas la composition ni les formats. Ses garanties sont donc moins étendues que celles d’un response_format fondé sur une grammaire. Le dialecte de Gemini rejette aussi les unions de types comme ["string", "null"]. Même un schéma apparemment portable peut donc nécessiter une réécriture pour chaque fournisseur.
Le mode structuré consomme-t-il encore des reasoning tokens ?
Dans la plupart des cas, oui. De plus, il n’est pas toujours possible de désactiver le raisonnement. Pour le calcul d’une ligne présenté plus haut, voici la consommation médiane de reasoning tokens avec le schéma et les réglages par défaut: GLM-5.2 568 tokens, DeepSeek V4 Pro 505, V4 Flash 466, Qwen3.8-Max 424, Gemini 3.6 Flash 210, Gemini 3.1 Pro 220, Gemini 3.7 Flash 99, Kimi K3 69 et gpt-5.6-luna 28. Cette consommation représente l’essentiel du coût de sortie pour une tâche dont la réponse tient en deux tokens.
La possibilité de désactiver le raisonnement depuis le mode structuré dépend du fournisseur. DeepSeek rejette directement reasoning_effort: none avec une erreur 400, mais respecte thinking: {"type": "disabled"}. Qwen, GLM et Kimi acceptent de régler l’effort à zéro. La génération actuelle de Gemini (3.7 Flash et 3.1 Pro) rejette toutes les formulations de désactivation que nous avons testées. Ce comportement correspond à la disparition de l’option de désactivation sur cette famille. Le coût du raisonnement est donc obligatoire pour ses appels structurés. Avec le chemin natif de Claude, le problème ne se pose pas: forcer un appel d’outil contourne entièrement le thinking étendu. Les trois modèles ont consommé zéro reasoning token, y compris Fable 5, avec une médiane de 74 output tokens par extraction. Pour une extraction simple, la famille de modèles la plus chère produit les réponses les moins coûteuses.
Combien coûte le schéma à chaque appel ?
Entre 30 et 4,959 prompt tokens pour le même appel. Cette différence s’explique par le chemin physique emprunté par le schéma. Il n’est jamais ajouté à votre liste de messages. Sur l’interface compatible OpenAI, il se trouve dans le corps de la requête sous response_format.json_schema. L’API native de Gemini le transmet via generation_config.response_schema. Claude ne dispose d’aucun emplacement dédié au schéma. Celui-ci devient donc l’input_schema d’un outil que tool_choice force le modèle à appeler. Le traitement côté serveur varie ensuite. Un premier groupe compile le schéma en grammaire côté serveur pour guider le décodage, sans le faire apparaître sur la facture. L’autre le sérialise dans le contexte du modèle sous forme de prompt caché. Il réapparaît alors dans les prompt_tokens. Voici les résultats pour un même document et trois tailles de schéma, 157 bytes, 1.5 KB avec 12 champs supplémentaires, et 12 KB avec 70 champs:
| API | Schéma de 157 B | 1.5 KB | 12 KB | Mode de facturation |
|---|---|---|---|---|
| deepseek-v4-flash | 30 | 30 | 30 | schéma jamais facturé |
| glm-5.2 | 38 | 38 | 38 | schéma jamais facturé |
| qwen3.8-max | 78 | 78 | 78 | schéma jamais facturé |
| deepseek-v4-pro | 109 | 109 | 109 | schéma jamais facturé |
| gpt-5.6-luna | 57 | 346 | 2,368 | schéma facturé comme prompt |
| kimi-k3 | 199 | 523 | 2,789 | schéma facturé comme prompt |
| gemini (les trois) | 92 | 590 | 4,012 | schéma facturé comme prompt |
| claude (outil natif, fable-5) | 549 | 1,029 | 4,959 | définition de l’outil facturée, plus un surcoût fixe proche de 500 tokens pour l’appel d’outil; sonnet-5 ajoute 64 tokens à chaque valeur |
Dans le groupe qui facture le schéma, le coût de sérialisation varie jusqu’à 70% pour un nombre d’octets identique. Le schéma de 12 KB coûte 4,012 tokens sur Gemini contre 2,368 sur OpenAI. À gros volume, les schémas volumineux pèsent davantage sur les coûts que le tarif par token du modèle. Pour 100K appels mensuels, le schéma de 12 KB est gratuit sur DeepSeek, mais représente environ 400M input tokens sur Gemini.
FAQ
Les sorties structurées garantissent-elles l’exactitude des données ?
Non. Elles garantissent des données analysables et conformes au schéma, pas des données exactes. Dans notre batterie, chaque mode structuré réellement actif a atteint 100% de conformité au schéma. Pourtant, pour certaines combinaisons de modèle et de tâche, 7 réponses sur 8 contenaient de mauvaises valeurs dans un JSON valide. Désactiver le thinking a corrigé la plupart de ces erreurs. Validez les valeurs, pas seulement la forme.
Claude prend-il en charge response_format json_schema ?
Non, chez aucun des fournisseurs testés. Il ne renvoie pas non plus d’erreur: le paramètre est accepté puis ignoré, ce qui constitue le pire mode d’échec. Utilisez plutôt l’appel d’outil natif d’Anthropic avec un tool_choice forcé. Sous un prompt contradictoire, ce chemin a appliqué toutes les contraintes. Il désactive aussi le thinking étendu, ce qui lui donne les réponses les plus courtes de ce lot, avec une médiane de 74 output tokens par extraction.
Faut-il désactiver le thinking pour l’extraction structurée ?
Oui sur DeepSeek V4, Qwen3.8-Max et GLM-5.2. Sur notre tâche de calcul vers schéma, Qwen est passé de 1/16 à 8/8 une fois le thinking désactivé. Pour l’extraction, DeepSeek V4 Pro est passé de 1/8 à 7/8. Nous n’avons observé aucune corruption de valeur sur OpenAI et Gemini avec le thinking activé. Pour ces modèles, adaptez le réglage à la difficulté de la tâche. La génération actuelle de Gemini ne permet de toute façon pas de le désactiver.
Mesures effectuées le 2026-08-25 via la gateway Synthorai sur 12 API de modèles en production. Toutes les méthodes et tailles d’échantillon sont décrites dans la section « Comment avons-nous testé les sorties structurées ? ». Les valeurs absolues proviennent de ce lot unique. Les fournisseurs modifient leur stack d’inférence sans préavis, donc refaites les mesures avant de vous appuyer sur une ligne de ce tableau.
Dans la même série: contrôles du thinking sur 13 modèles, mesures de DeepSeek V4 Pro, coût de Qwen3.8-Max, coûts de GPT-5.6.