
La sortie structurée LLM est le mécanisme qui garantit que la réponse d'un modèle de langage est conforme à un schéma prédéfini -- pas seulement du JSON valide, mais du JSON valide selon le schéma avec exactement les champs, les types et les contraintes que vous avez spécifiés. Tous les grands fournisseurs le supportent désormais nativement, et cela a changé la façon dont les applications LLM en production sont construites.
Résumé Rapide : Les Sorties Structurées en Un Coup d'Œil
Si vous êtes pressé, voici le paysage en 2026 :
| Aspect | Détails |
|---|---|
| Ce que c'est | Réponses contraintes par schéma des LLM -- structure garantie, pas un "meilleur effort" |
| Qui le supporte | OpenAI, Anthropic, Gemini, Cohere, xAI (Grok), plus en local via Ollama/vLLM |
| Mécanisme clé | Décodage contraint -- les tokens invalides sont masqués avant l'échantillonnage |
| Mode JSON vs Mode Strict | Mode JSON = syntaxe valide seulement. Mode Strict = conformité complète au schéma |
| Bibliothèque Python | Pydantic (BaseModel + Field) pour la définition du schéma |
| Bibliothèque TypeScript | Zod (z.object + .describe) pour la définition du schéma |
| Meilleure approche de départ | OpenAI avec Pydantic ou Zod via le SDK natif |
| Meilleure bibliothèque de production | Instructor (Python) ou SDK natif (TypeScript) |
| Principal piège | Mettre le champ de raisonnement APRÈS le champ de réponse -- le modèle décide avant de réfléchir |
| Surcharge de latence | 50-200ms au premier appel (compilation du schéma), mis en cache ensuite |
Décortiquons maintenant chaque élément.
Que Sont les Sorties Structurées LLM ?
La sortie structurée est la différence entre espérer qu'un LLM retourne du JSON valide et le garantir. Lorsque vous activez la sortie structurée, le modèle ne peut physiquement pas produire de tokens qui violent votre schéma. Vous définissez un JSON Schema (ou un modèle Pydantic, ou un schéma Zod), vous le passez à l'API, et vous obtenez en retour une réponse qui lui correspond à chaque fois.
Pourquoi est-ce important ? Avant les sorties structurées, les développeurs écrivaient des parseurs regex fragiles, enveloppaient chaque appel LLM dans des blocs try/catch JSON.parse, et devaient quand même faire face à des réponses "presque correctes" -- du JSON valide auquel il manquait un champ ou qui avait le mauvais type. Toute cette catégorie de bugs a disparu.
Il y a trois niveaux de mise en application de la structure, et ils représentent une évolution claire :
- Ingénierie de prompt -- "Veuillez retourner du JSON avec ces champs." Peu fiable. Le modèle pourrait être conforme 80-90% du temps.
- Mode JSON -- Garantit du JSON syntaxiquement valide, mais n'applique pas votre schéma. Vous pourriez obtenir
{"foo": "bar"}alors que vous attendiez{"name": string, "age": number}. - Mode Strict / Décodage contraint -- Garantit une conformité à 100% au schéma. Le modèle ne peut littéralement pas produire de tokens invalides. C'est ce que "sortie structurée" signifie en 2026.
Depuis début 2026, OpenAI, Anthropic et Google Gemini supportent tous nativement les sorties structurées. L'écosystème a convergé.
Verdict : Si vous parsez les réponses LLM avec des regex ou JSON.parse en production, vous faites les choses à la dure. Les sorties structurées natives éliminent toute cette catégorie d'échec.
Mode JSON vs Mode Strict : Qu'est-ce Qui a Réellement Changé ?
Cette distinction déroute beaucoup de développeurs parce que les noms se ressemblent. Ils ne le sont pas.
| Fonctionnalité | Mode JSON | Mode Strict (Sorties Structurées) |
|---|---|---|
| Paramètre API | type: "json_object" | type: "json_schema" avec strict: true |
| Garantit du JSON valide | Oui | Oui |
| Garantit la conformité au schéma | Non | Oui |
| Mécanisme | Biais de token post-hoc | Décodage contraint (FSM) |
| Peut retourner des champs inattendus | Oui | Non |
| Peut omettre des champs requis | Oui | Non |
| Application des types | Aucune | Complète (string, number, array, etc.) |
| Quand l'utiliser | Vous n'avez pas de schéma à l'avance | Tout en production |
La chronologie : OpenAI a introduit le Mode JSON fin 2023. C'était un pas en avant, mais les développeurs ont vite réalisé que "JSON valide" n'était pas suffisant -- ils avaient besoin d'un JSON valide selon le schéma. En août 2024, OpenAI a lancé les Sorties Structurées avec le Mode Strict, qui utilise le décodage contraint pour garantir la conformité au schéma. D'ici 2025-2026, tous les grands fournisseurs avaient adopté la même approche.
Le Mode JSON a encore un cas d'usage étroit : quand vous ne connaissez vraiment pas la forme de la réponse à l'avance et voulez juste un JSON valide pour une exploration non structurée. Mais c'est rare en production.
Verdict : Utilisez le Mode Strict pour tout en production. Le Mode JSON est effectivement déprécié pour les cas d'utilisation liés à un schéma. Si vous avez un schéma (et vous devriez en avoir un), utilisez type: "json_schema" avec strict: true.
Comment Fonctionne Réellement le Décodage Contraint ?
Voici le mécanisme qui rend possible une conformité à 100% au schéma -- pas 99,9%, mais littéralement 100%.
Lorsque vous envoyez un JSON Schema à un fournisseur avec le Mode Strict activé, le schéma est compilé en une machine à états finis (FSM). Cette FSM représente chaque chemin valide à travers votre schéma. À chaque étape de génération de token, le moteur d'inférence vérifie quels tokens maintiendraient la sortie sur un chemin valide et lesquels ne le feraient pas. Les tokens invalides voient leurs logits mis à moins l'infini avant l'échantillonnage, ce qui signifie qu'ils ont une probabilité nulle d'être sélectionnés.
<!-- IMAGE: constrained-decoding diagram showing FSM token masking during structured output generation -->Imaginez ça comme l'autocomplétion sous stéroïdes. Si le modèle vient de produire {"rating": et que votre schéma dit que rating est un entier, les seuls tokens autorisés suivants sont des tokens numériques. Guillemets, lettres, crochets -- tout masqué. Le modèle ne peut pas produire "cinq" même s'il le "veut".
C'est le même mécanisme de base utilisé par XGrammar (le moteur derrière vLLM, SGLang et la plupart des serveurs d'inférence locaux) et Outlines (la bibliothèque Python open-source pour la génération contrainte). Les fournisseurs d'API l'ont simplement intégré dans leur infrastructure d'inférence.
Il y a un compromis à connaître : la première requête avec un nouveau schéma entraîne un temps de compilation supplémentaire (typiquement 50-200ms) pendant la construction de la FSM. Les requêtes suivantes avec le même schéma utilisent une FSM mise en cache et ajoutent un overhead quasi nul. Il y a aussi une considération de qualité subtile -- contraindre le vocabulaire de tokens peut parfois réduire la qualité de sortie pour les champs créatifs ou en forme libre, donc gardez vos schémas focalisés sur des données vraiment structurées.
Verdict : Le décodage contraint est ce qui sépare "fonctionne généralement" de "fonctionne toujours." C'est l'ingénierie qui rend les sorties structurées prêtes pour la production.
Implémentation Multi-Fournisseurs : OpenAI, Anthropic et Gemini
Voici quelque chose qu'aucun des autres guides ne vous montre : la même tâche d'extraction implémentée sur les trois grands fournisseurs. Nous allons extraire un avis produit structuré à partir d'un texte non structuré.
Le schéma Pydantic (partagé entre tous les fournisseurs) :
from pydantic import BaseModel, Field
from typing import Literal
class ProductReview(BaseModel):
reasoning: str = Field(description="Think through the review before scoring")
rating: int = Field(description="Rating from 1-5", ge=1, le=5)
sentiment: Literal["positive", "negative", "neutral"]
pros: list[str] = Field(description="Key positive points")
cons: list[str] = Field(description="Key negative points")
summary: str = Field(description="One-sentence summary")Implémentation OpenAI
from openai import OpenAI
client = OpenAI()
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Extract a structured review from the text."},
{"role": "user", "content": review_text}
],
response_format=ProductReview, # Modèle Pydantic directement
)
review = response.choices[0].message.parsed # Objet ProductReview typéL'implémentation d'OpenAI est la plus mature. La méthode parse() accepte un modèle Pydantic directement et retourne un objet typé. Une contrainte : le Mode Strict d'OpenAI supporte un sous-ensemble de JSON Schema -- pas de $ref, anyOf limité, et tous les champs doivent être requis avec additionalProperties: false.
Implémentation Anthropic
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": f"Extract a structured review:\n\n{review_text}"}
],
output_config={
"format": {
"type": "json_schema",
"json_schema": ProductReview.model_json_schema()
}
}
)
import json
review_data = json.loads(response.content[0].text)
review = ProductReview(**review_data)La sortie structurée native d'Anthropic utilise output_config.format avec un JSON Schema. Elle a atteint la disponibilité générale début 2026. Anthropic supporte aussi l'ancien pattern consistant à définir un faux outil et à extraire via tool_use -- ça fonctionne encore, mais la sortie structurée native est plus propre pour l'extraction pure.
Implémentation Gemini
from google import genai
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=f"Extract a structured review:\n\n{review_text}",
config={
"response_mime_type": "application/json",
"response_schema": ProductReview, # Modèle Pydantic directement
}
)
import json
review = ProductReview(**json.loads(response.text))Gemini supporte les modèles Pydantic directement dans le SDK Python via response_schema. Une fonctionnalité unique : Gemini respecte propertyOrdering dans le schéma, vous permettant de contrôler l'ordre de sortie des champs (utile pour le pattern raisonnement-en-premier).
Comparaison des Fournisseurs
| Fonctionnalité | OpenAI | Anthropic | Gemini |
|---|---|---|---|
| Paramètre API | response_format | output_config.format | response_schema |
| Entrée de schéma | Pydantic ou JSON Schema | JSON Schema | Pydantic ou JSON Schema |
| Mode strict | strict: true | Implicite avec json_schema | Implicite |
| Streaming | Oui (JSON partiel) | Oui | Oui |
| Gestion des refus | champ message.refusal | Réponse d'erreur | Réponse d'erreur |
| Alternative tool-use | Oui | Oui (méthode originale) | Oui |
| Cache de compilation de schéma | Oui (côté serveur) | Oui | Oui |
| Ordre des propriétés | Pas de support natif | Non | Oui (propertyOrdering) |
Verdict : OpenAI a la DX la plus aboutie avec sa méthode parse(). Anthropic offre les modèles sous-jacents les plus capables. L'ordre des propriétés de Gemini est uniquement utile. Les trois font le travail -- choisissez en fonction de votre relation existante avec le fournisseur.
Patterns Pydantic pour les Développeurs Python
Pydantic est le standard de facto pour définir des schémas de sortie structurée en Python. Voici les patterns qui comptent.
Schéma de Base avec Descriptions
from pydantic import BaseModel, Field
from typing import Literal, Optional
class ExtractedEntity(BaseModel):
reasoning: str = Field(description="Think step by step about the entity")
name: str = Field(description="Full name of the entity")
entity_type: Literal["person", "company", "location"]
confidence: float = Field(description="Confidence score 0.0-1.0", ge=0.0, le=1.0)
context: Optional[str] = Field(description="Surrounding context, if relevant")Ces chaînes description ne sont pas seulement pour la documentation -- elles font partie du JSON Schema envoyé au modèle et influencent directement ce que le modèle génère. Considérez-les comme du prompt engineering à l'intérieur du schéma.
Modèles Imbriqués
class Address(BaseModel):
street: str
city: str
country: str
postal_code: Optional[str] = None
class Company(BaseModel):
reasoning: str = Field(description="Analysis of the company details")
name: str
industry: Literal["tech", "finance", "healthcare", "retail", "other"]
headquarters: Address # Modèle imbriqué
key_products: list[str] = Field(description="Top 3 products or services")Gardez l'imbrication à 2-3 niveaux maximum. Les schémas profondément imbriqués augmentent les taux d'erreur et ralentissent la compilation du schéma.
Le Pattern Raisonnement-en-Premier
C'est le pattern de conception de schéma le plus impactant. Mettez un champ reasoning avant vos champs de réponse :
# Mauvais -- le modèle s'engage dans une réponse avant de réfléchir
class ClassificationBad(BaseModel):
category: Literal["spam", "ham"]
confidence: float
# Bon -- le modèle raisonne d'abord à travers le problème
class ClassificationGood(BaseModel):
reasoning: str = Field(description="Analyze the text before classifying")
category: Literal["spam", "ham"]
confidence: float = Field(ge=0.0, le=1.0)Les LLM génèrent des tokens de gauche à droite. Si category vient en premier, le modèle choisit une catégorie et la rationalise ensuite. Si reasoning vient en premier, le modèle travaille à travers le problème et s'engage ensuite dans une catégorie. C'est la chaîne de pensée intégrée dans le schéma.
Export JSON Schema
# Générez le JSON Schema pour n'importe quel modèle Pydantic
schema = ProductReview.model_json_schema()
# Passez ceci à tout fournisseur qui accepte un JSON Schema brutVerdict : Pydantic + champs descriptifs + ordre raisonnement-en-premier est le trio Python des sorties structurées. Maîtrisez ces trois patterns et vous couvrirez 90% des cas d'utilisation.
Patterns Zod pour les Développeurs TypeScript
Zod est l'équivalent TypeScript de Pydantic -- et il est tout aussi central aux workflows de sortie structurée.
Schéma de Base avec Descriptions
import { z } from "zod";
const ProductReview = z.object({
reasoning: z.string().describe("Think through the review before scoring"),
rating: z.number().int().min(1).max(5),
sentiment: z.enum(["positive", "negative", "neutral"]),
pros: z.array(z.string()).describe("Key positive points"),
cons: z.array(z.string()).describe("Key negative points"),
summary: z.string().describe("One-sentence summary"),
});
// Inférer le type TypeScript automatiquement
type ProductReview = z.infer<typeof ProductReview>;Comme Field(description=...) de Pydantic, .describe() de Zod fait partie du JSON Schema et guide la sortie du modèle.
Intégration avec le SDK Node OpenAI
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
const client = new OpenAI();
const response = await client.beta.chat.completions.parse({
model: "gpt-4o-2024-08-06",
messages: [
{ role: "system", content: "Extract a structured review." },
{ role: "user", content: reviewText },
],
response_format: zodResponseFormat(ProductReview, "product_review"),
});
const review = response.choices[0].message.parsed; // Typé !Intégration avec le SDK Vercel AI
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
const { object: review } = await generateObject({
model: openai("gpt-4o"),
schema: ProductReview,
prompt: `Extract a structured review:\n\n${reviewText}`,
});
// review est entièrement typé comme ProductReviewLe SDK Vercel AI utilise Zod nativement avec generateObject(), ce qui en fait l'intégration TypeScript la plus propre. Il fonctionne avec OpenAI, Anthropic, Gemini et d'autres fournisseurs via une API unifiée.
Conversion JSON Schema
import { zodToJsonSchema } from "zod-to-json-schema";
const jsonSchema = zodToJsonSchema(ProductReview);
// Utiliser avec tout fournisseur qui accepte un JSON Schema brutVerdict : Zod + .describe() + le SDK Vercel AI est le stack TypeScript des sorties structurées. Si vous êtes dans l'écosystème Node/Next.js, c'est le chemin de moindre résistance.
Sortie Structurée vs Appel de Fonction : Quand Utiliser Lequel ?
C'est l'une des sources de confusion les plus courantes. Les deux impliquent des schémas, les deux retournent des données structurées -- mais ils résolvent des problèmes différents.
La sortie structurée dit : "Donnez-moi des données dans cette forme exacte." C'est pour l'extraction, la classification et le formatage. Vous extrayez des informations structurées à partir de texte non structuré.
L'appel de fonction (tool use) dit : "Voici des actions que vous pouvez effectuer -- décidez laquelle exécuter et fournissez les arguments." C'est pour les workflows d'agents où le modèle choisit parmi plusieurs outils et déclenche des actions.
La confusion a du sens historiquement. La "sortie structurée" originale d'Anthropic était littéralement un appel de fonction -- vous définissiez un faux outil appelé extract_review et récupériez les arguments. Ça fonctionne encore, mais la sortie structurée native est plus simple pour l'extraction pure.
| Scénario | Meilleure Approche | Pourquoi |
|---|---|---|
| Extraire des données d'un texte | Sortie structurée | Direct, latence plus faible, schéma unique |
| Classer dans des catégories | Sortie structurée | Une réponse, un schéma |
| Agent décidant quel outil appeler | Appel de fonction | Le modèle choisit parmi plusieurs outils |
| Orchestration multi-étapes | Appel de fonction | Invocations d'outils séquentielles |
| Extraire des données ET décider la prochaine action | Les deux | Sortie structurée pour l'extraction, appel de fonction pour l'orchestration |
La sortie structurée alimente les pipelines d'appel d'outils dans les systèmes d'agents IA. Consultez notre guide sur les agents IA pour les entreprises pour voir comment ils s'intègrent dans les workflows de production.
Verdict : Utilisez la sortie structurée quand vous savez quelle forme les données doivent avoir. Utilisez l'appel de fonction quand le modèle doit choisir une action. En pratique, la plupart des applications utilisent les deux -- la sortie structurée pour l'extraction de données et l'appel de fonction pour l'orchestration des agents.
Patterns de Production : Erreurs, Retries et Streaming
Faire fonctionner les sorties structurées dans une démo est facile. Les maintenir fiables en production nécessite de gérer trois choses : les refus, les échecs de validation et le streaming.
Gestion des Refus
Parfois un modèle refuse de générer votre sortie demandée -- typiquement parce que les filtres de sécurité ont signalé l'entrée. Dans ce cas, les APIs de sortie structurée ne retournent pas votre schéma. Elles retournent un refus.
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
# Vérifiez TOUJOURS le refus avant d'accéder au contenu parsé
if response.choices[0].message.refusal:
print(f"Model refused: {response.choices[0].message.refusal}")
else:
review = response.choices[0].message.parsedSi vous sautez la vérification de refus et essayez d'accéder à .parsed sur un refus, vous obtiendrez None et une erreur aval déroutante. Vérifiez en premier, toujours.
Patterns de Retry avec Retour sur Validation
La conformité au schéma est garantie par le décodage contraint, mais la correction sémantique ne l'est pas. Le modèle pourrait retourner {"rating": 1, "sentiment": "positive"} -- schéma valide, contenu contradictoire. C'est là qu'interviennent la validation + les retries.
import instructor
client = instructor.from_openai(OpenAI())
# Instructor gère les retries automatiquement
review = client.chat.completions.create(
model="gpt-4o",
response_model=ProductReview,
max_retries=3, # Retries avec retour d'erreur de validation
messages=[
{"role": "user", "content": review_text}
],
)Instructor renvoie l'erreur de validation au modèle lors du retry, pour qu'il puisse se corriger. Pour les patterns de retry manuels sans Instructor :
from pydantic import ValidationError
for attempt in range(3):
try:
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=messages,
response_format=ProductReview,
)
review = response.choices[0].message.parsed
# Exécuter une validation sémantique supplémentaire ici
break
except ValidationError as e:
messages.append({"role": "assistant", "content": str(response)})
messages.append({"role": "user", "content": f"Validation error: {e}. Fix it."})Streaming de Sortie Structurée
Pour les grandes réponses structurées -- longs tableaux, nombreux champs, objets imbriqués complexes -- le streaming permet de rendre progressivement des résultats partiels.
import instructor
client = instructor.from_openai(OpenAI())
# Streamer les résultats partiels au fur et à mesure que les champs se remplissent
review_stream = client.chat.completions.create_partial(
model="gpt-4o",
response_model=ProductReview,
messages=[{"role": "user", "content": review_text}],
)
for partial_review in review_stream:
# Les champs se remplissent un par un à mesure que les tokens arrivent en streaming
if partial_review.summary:
print(f"Summary so far: {partial_review.summary}")Un piège : les chunks de streaming individuels ne sont pas schema-valides en eux-mêmes. Le champ reasoning pourrait être rempli alors que rating est encore None. Planifiez votre UI en conséquence -- affichez un état de chargement pour les champs non remplis.
Verdict : Les vérifications de refus sont non négociables. Les retries avec retour de validation capturent les erreurs sémantiques. Le streaming vaut la peine pour toute réponse qui prend plus de quelques secondes.
Comparaison des Bibliothèques de Sortie Structurée
Vous pouvez utiliser les sorties structurées via des APIs natives, mais les bibliothèques ajoutent la validation, les retries, le streaming et le support multi-fournisseurs. Voici le paysage.
Instructor est l'option la plus populaire avec 11K+ étoiles GitHub et 3M+ téléchargements mensuels. Il enveloppe OpenAI, Anthropic, Gemini, Cohere, Ollama et plus avec une interface unifiée basée sur Pydantic. Fonctionnalités clés : retries automatiques avec retour de validation, streaming via create_partial(), et configuration simple (instructor.from_openai(client)). Si vous êtes une équipe Python, commencez ici.
BAML adopte une approche différente : schéma-d'abord via un DSL personnalisé. Vous définissez des schémas dans des fichiers .baml et auto-générez des clients pour Python, TypeScript, Ruby et plus. Son algorithme SAP (schema-aligned parsing) gère gracieusement les sorties de modèles désordonnées. Idéal pour les équipes multi-langages ou quand vous voulez des contrats entre votre couche LLM et la couche application. Compromis : étape de build supplémentaire et nouvelle syntaxe à apprendre.
LangChain offre .with_structured_output(schema) pour des sorties structurées indépendantes du fournisseur. Pratique si vous êtes déjà dans l'écosystème LangChain. Compromis : c'est une dépendance lourde, et l'abstraction peut masquer des fonctionnalités spécifiques au fournisseur dont vous pourriez avoir besoin.
Les APIs natives -- appels directs avec response_format / output_config -- ne nécessitent aucune dépendance au-delà du SDK du fournisseur. Vous obtenez un contrôle total et une visibilité totale. Idéal pour les cas d'utilisation simples ou les équipes qui préfèrent une abstraction minimale.
| Bibliothèque | Langages | Fournisseurs | Retries Auto | Streaming | Étoiles GitHub | Courbe d'apprentissage |
|---|---|---|---|---|---|---|
| Instructor | Python, TS | 15+ | Oui | Oui | 11K+ | Faible |
| BAML | Python, TS, Ruby, Go | Tous (DSL-agnostique) | Oui | Oui | 7K+ | Moyen |
| LangChain | Python, TS | 20+ | Partiel | Oui | 100K+ | Moyen-Élevé |
| APIs natives | Tous | 1 par SDK | Non | Oui | N/A | Faible |
Choisir la bonne bibliothèque de sortie structurée fait partie d'une décision plus large sur le stack IA. Nous analysons le stack complet dans notre guide Best AI Stack pour SaaS.
Consultez nos Meilleures Bibliothèques pour les Sorties Structurées LLM [à venir] pour une comparaison approfondie d'Instructor, BAML, Mirascope et plus.
Verdict : Commencez avec Instructor pour Python, les APIs natives pour TypeScript. Passez à BAML si vous avez besoin de contrats de schéma inter-langages. Évitez LangChain uniquement pour les sorties structurées -- c'est excessif.
Meilleures Pratiques de Conception de Schéma (et Erreurs Courantes)
La conception de votre schéma impacte directement la qualité de la sortie. Voici les patterns qui comptent et les erreurs qui vous coûtent en précision.
Mettre le Raisonnement Avant les Réponses
Nous avons abordé cela dans la section Pydantic, mais ça mérite d'être répété car c'est la décision de conception la plus impactante :
# Avant : le modèle devine la réponse, puis la rationalise
class Bad(BaseModel):
answer: str
reasoning: str
# Après : le modèle réfléchit d'abord, puis s'engage
class Good(BaseModel):
reasoning: str = Field(description="Think step by step")
answer: strLes LLM génèrent de gauche à droite. L'ordre des champs est l'ordre du prompt. Raisonnement en premier signifie que le modèle doit travailler à travers le problème avant de s'engager dans une réponse.
Le Tableau des Anti-Patterns
| Erreur | Problème | Solution |
|---|---|---|
| Champ de raisonnement après la réponse | Le modèle décide avant de réfléchir | Déplacer le raisonnement avant la réponse |
| Profondément imbriqué (4+ niveaux) | Taux d'erreur plus élevé, compilation plus lente | Aplatir à 2-3 niveaux |
| Pas de descriptions de champs | Le modèle devine ce que vous voulez | Ajouter .describe() / Field(description=...) |
| Gestion des nulls manquante | Le modèle hallucine une valeur pour remplir le champ | Utiliser Optional / .nullable() |
| Schémas trop grands (50+ champs) | Timeout de compilation, dégradation de la qualité | Diviser en plusieurs appels |
| Options d'enum vagues | Le modèle choisit la mauvaise catégorie | Utiliser des options spécifiques et non chevauchantes |
Gérer les Nulls Explicitement
Si un champ pourrait ne pas avoir de données dans le texte source, rendez-le optionnel. Forcer un champ requis quand les données n'existent pas conduit à l'hallucination :
class PersonInfo(BaseModel):
name: str # Toujours présent
email: Optional[str] = Field(None, description="Email if mentioned, null otherwise")
phone: Optional[str] = Field(None, description="Phone if mentioned, null otherwise")Garder les Schémas Focalisés
Un schéma par tâche. N'essayez pas d'extraire tout dans un seul schéma massif. Si vous avez besoin de 50+ champs, divisez en plusieurs appels d'extraction. Le Mode Strict d'OpenAI a des limites pratiques sur la complexité du schéma, et même quand ça fonctionne, les très grands schémas dégradent la qualité de la sortie.
Verdict : Raisonnement-en-premier, champs descriptifs, nulls explicites et schémas focalisés. Maîtrisez ces quatre éléments et votre précision en sortie structurée augmente de manière mesurable.
Sortie Structurée avec des LLM Locaux
Vous n'avez pas besoin d'un fournisseur d'API pour les sorties structurées. Les moteurs d'inférence locaux le supportent grâce au décodage contraint basé sur la grammaire -- le même mécanisme fondamental, fonctionnant sur votre propre matériel.
Ollama
Le chemin le plus simple pour les sorties structurées locales. Ollama accepte un JSON Schema via le paramètre format :
import ollama
from pydantic import BaseModel
class Country(BaseModel):
name: str
capital: str
languages: list[str]
response = ollama.chat(
model="llama3.2",
messages=[{"role": "user", "content": "Tell me about Japan."}],
format=Country.model_json_schema(),
)
import json
country = Country(**json.loads(response.message.content))Ollama utilise XGrammar en interne pour le décodage contraint. Même garantie que les fournisseurs d'API : conformité à 100% au schéma.
vLLM et SGLang
Pour l'inférence locale de qualité production, vLLM et SGLang supportent tous deux les sorties structurées via les paramètres guided_json et guided_regex. XGrammar est le backend par défaut, offrant un overhead quasi nul sur la génération JSON -- jusqu'à 3,5x plus rapide que les moteurs de grammaire alternatifs.
Outlines
Outlines est la bibliothèque Python open-source qui a pionné la génération contrainte basée sur la grammaire. Elle fonctionne avec n'importe quel modèle Hugging Face et supporte les contraintes JSON Schema, regex et grammaire hors-contexte complète (CFG/EBNF). Elle est aussi intégrée dans vLLM et SGLang comme option de backend de grammaire.
La différence clé avec les fournisseurs d'API : les sorties structurées locales n'ont aucune limitation de sous-ensemble de schéma. Vous contrôlez entièrement la grammaire. Mais la qualité du modèle varie davantage -- un modèle local à 7 milliards de paramètres ne correspondra pas à GPT-4o ou Claude sur des tâches d'extraction complexes. Le schéma sera toujours valide ; la qualité du contenu dépend du modèle.
Verdict : Ollama pour le développement, vLLM/SGLang avec XGrammar pour la production. Les sorties structurées locales sont suffisamment matures pour la plupart des cas d'utilisation, avec la mise en garde que les modèles plus petits produisent du contenu de moindre qualité dans le schéma.
FAQ
Qu'est-ce que la sortie structurée dans les LLM ?
La sortie structurée est un mécanisme qui garantit que la réponse d'un LLM est conforme à un JSON Schema prédéfini. Contrairement au texte brut ou même au Mode JSON, la sortie structurée utilise le décodage contraint pour s'assurer que chaque champ, type et contrainte dans votre schéma est respecté -- 100% du temps, pas "généralement".
Quelle est la différence entre le Mode JSON et les Sorties Structurées ?
Le Mode JSON garantit du JSON syntaxiquement valide mais n'applique pas votre schéma -- vous pourriez obtenir n'importe quel objet JSON valide. Les Sorties Structurées (Mode Strict) garantissent une conformité complète au schéma via le décodage contraint. Utilisez le Mode Strict pour la production ; le Mode JSON n'est pertinent que si vous n'avez pas de schéma à l'avance.
Quels fournisseurs LLM supportent les sorties structurées nativement ?
OpenAI (depuis août 2024), Google Gemini (2024, étendu 2026), Anthropic (bêta novembre 2025, GA début 2026), Cohere et xAI (Grok) supportent tous les sorties structurées nativement. Du côté local, Ollama, vLLM et SGLang les supportent via le décodage contraint basé sur la grammaire.
Comment le décodage contraint garantit-il la conformité au schéma ?
Le JSON Schema est compilé en une machine à états finis (FSM). À chaque étape de génération de token, seuls les tokens qui maintiennent la sortie sur un chemin valide à travers la FSM sont autorisés -- les tokens invalides voient leurs logits mis à moins l'infini. Cela signifie que les tokens invalides ont une probabilité nulle d'être générés, vous donnant une garantie mathématique, pas statistique.
Devrais-je utiliser la sortie structurée ou l'appel de fonction ?
Utilisez la sortie structurée pour l'extraction et la classification -- quand vous voulez des données dans une forme spécifique. Utilisez l'appel de fonction pour les workflows d'agents -- quand le modèle doit décider quelle action effectuer. De nombreuses applications de production utilisent les deux : la sortie structurée pour l'extraction de données et l'appel de fonction pour l'orchestration.
Puis-je streamer des sorties structurées ?
Oui. OpenAI supporte le streaming avec la méthode parse(), et Instructor fournit create_partial() pour streamer des modèles Pydantic qui se remplissent champ par champ. Gardez à l'esprit que les chunks de streaming individuels ne sont pas individuellement schema-valides -- les champs se remplissent de manière incrémentielle.
Qu'est-ce que la bibliothèque Instructor ?
Instructor est la bibliothèque de sortie structurée la plus populaire (11K+ étoiles GitHub, 3M+ téléchargements mensuels). Elle enveloppe les SDKs de fournisseurs avec la validation basée sur Pydantic, des retries automatiques avec retour de validation et le support du streaming. Elle fonctionne avec OpenAI, Anthropic, Gemini, Cohere, Ollama et 10+ autres fournisseurs.
Les sorties structurées fonctionnent-elles avec des LLM locaux ?
Oui. Ollama supporte les sorties structurées via le paramètre format avec JSON Schema. vLLM et SGLang les supportent via les paramètres guided_json. Les trois utilisent XGrammar ou Outlines pour le décodage contraint. La garantie de conformité au schéma est la même que les fournisseurs d'API ; la qualité du contenu dépend du modèle.
Quelles sont les erreurs courantes de conception de schéma ?
Les principales erreurs : mettre le champ de raisonnement après le champ de réponse (le modèle décide avant de réfléchir), les schémas profondément imbriqués (4+ niveaux augmentent les erreurs), les descriptions de champs manquantes (le modèle devine l'intention), pas de gestion des nulls pour les données optionnelles (force l'hallucination), et les schémas trop grands (50+ champs dégradent la qualité).
La sortie structurée ajoute-t-elle de la latence ?
Il y a un overhead de compilation de schéma sur la première requête -- typiquement 50-200ms pendant la construction de la FSM. Les requêtes suivantes avec le même schéma utilisent une FSM mise en cache et ajoutent une latence quasi nulle. Pour la plupart des applications, c'est négligeable par rapport au temps total d'inférence du modèle.
Puis-je utiliser les sorties structurées avec des images ou des entrées multimodales ?
Oui. La sortie structurée s'applique au format de réponse, pas à l'entrée. Vous pouvez envoyer une image à GPT-4o ou Gemini avec un schéma de sortie structurée et obtenir en retour une analyse conforme au schéma de l'image. C'est puissant pour les workflows d'extraction visuelle -- extraire des données structurées de reçus, de formulaires ou d'images de produits.