Structured Output
Un structured output (sortie structurée) est une réponse de LLM qui respecte un format prédéfini et parsable automatiquement, typiquement un schéma JSON, XML ou un autre format structuré, garanti par une contrainte au niveau de l’API plutôt que par une simple instruction dans le prompt.
- Catégorie
- Format de sortie / Intégration LLM
- Problème résolu
- Garantir que la sortie du modèle est valide et conforme à un schéma, sans parsing fragile ni retry
- Méthodes principales
- JSON Schema natif (OpenAI, Anthropic, Gemini), JSON mode, tool use comme workaround, prefilling
- Fiabilité
- 100 % de conformité au schéma avec les structured outputs natifs d’OpenAI et Anthropic (modèles compatibles)
- Formats liés
- JSON mode, XML output, Markdown output
- Verdict
- Indispensable en production. Utilisez les structured outputs natifs quand ils sont disponibles, le tool use en fallback.
Le problème que les structured outputs résolvent
Quand un LLM génère du texte libre, sa sortie est imprévisible en termes de format. Demandez « Retourne les données en JSON » dans le prompt, et le modèle peut retourner du JSON valide, du JSON entouré de blocs Markdown (```json ... ```), du JSON avec des commentaires, ou un texte narratif qui mentionne les données sans les structurer. Résultat : votre code qui fait json.loads(response) plante de manière intermittente.
Avant les structured outputs natifs, les développeurs jonglaient entre le prompt engineering (« Tu DOIS répondre en JSON strict, rien d’autre »), le parsing avec regex, les retries automatiques quand la sortie était invalide, et les bibliothèques comme Instructor qui ajoutaient une couche de validation Pydantic. Ces approches fonctionnaient « la plupart du temps », mais pas à 100 %, ce qui est inacceptable en production.
Les structured outputs natifs résolvent ce problème en contraignant la génération côté serveur. Le modèle ne peut physiquement pas produire de sortie invalide par rapport au schéma défini. Ce n’est pas du post-traitement : la contrainte est appliquée pendant la génération, token par token.
Les trois approches pour obtenir des sorties structurées
1. Structured outputs natifs (JSON Schema)
L’approche la plus fiable. Le développeur fournit un schéma JSON dans la requête API, et le modèle est contraint de produire une sortie conforme à ce schéma. La validation est faite côté serveur pendant la génération.
Chez OpenAI, les structured outputs sont disponibles via le paramètre response_format avec "type": "json_schema". OpenAI a annoncé 100 % de fiabilité sur ses évaluations de conformité au schéma.
# OpenAI Structured Outputs
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-5.4",
messages=[{"role": "user", "content": "Extrais les infos de cet e-mail : ..."}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "email_extraction",
"schema": {
"type": "object",
"properties": {
"sender": {"type": "string"},
"subject": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"action_required": {"type": "boolean"}
},
"required": ["sender", "subject", "priority", "action_required"],
"additionalProperties": False
}
}
}
)
Chez Anthropic, les structured outputs utilisent le paramètre output_config avec "format": {"type": "json_schema"}. Cette fonctionnalité est disponible sur Claude Opus 4.6 et Sonnet 4.6. Le modèle retourne une chaîne JSON garantie conforme au schéma, directement parsable.
# Anthropic Structured Outputs (API native)
import anthropic, json
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Extrais les infos de cet e-mail : ..."}],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"sender": {"type": "string"},
"subject": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"action_required": {"type": "boolean"}
},
"required": ["sender", "subject", "priority", "action_required"],
"additionalProperties": False
}
}
}
)
data = json.loads(response.content[0].text) # Garanti valide
Chez Google (Gemini), le paramètre response_schema combiné avec response_mime_type: "application/json" force la conformité au schéma dans l’API Generative Language.
2. Tool use comme workaround
Avant l’arrivée des structured outputs natifs, la méthode recommandée chez Anthropic était de définir un « outil » fictif dont le schéma d’entrée correspondait au format de sortie souhaité, puis de forcer le modèle à « appeler » cet outil. Le function calling garantit que les paramètres de l’appel respectent le schéma de l’outil. C’est un détournement élégant mais indirect.
# Workaround tool use (fonctionne sur tous les modèles Claude)
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tool_choice={"type": "tool", "name": "extract_email"},
tools=[{
"name": "extract_email",
"description": "Extrait les infos structurées d'un e-mail",
"input_schema": {
"type": "object",
"properties": {
"sender": {"type": "string"},
"subject": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]},
"action_required": {"type": "boolean"}
},
"required": ["sender", "subject", "priority", "action_required"]
}
}],
messages=[{"role": "user", "content": "Extrais les infos de cet e-mail : ..."}]
)
Cette approche reste utile quand vous travaillez avec des modèles qui ne supportent pas les structured outputs natifs, ou quand vous avez besoin de strict mode ("strict": true) sur les paramètres d’outils.
3. Prompt engineering + prefilling
L’approche la moins fiable mais la plus universelle. Vous demandez du JSON dans le system prompt, et optionnellement vous utilisez le prefilling (commencer l’assistant message par {) pour contraindre le début de la sortie. Cette méthode fonctionne avec tous les modèles mais ne garantit pas 100 % de conformité. Utilisez-la uniquement quand les deux premières options ne sont pas disponibles.
Comparaison par provider
| Feature | OpenAI (GPT-5.4) | Anthropic (Claude Opus 4.6) | Google (Gemini 3.1 Pro) | Mistral |
|---|---|---|---|---|
| Structured outputs natifs | Oui (response_format) | Oui (output_config) | Oui (response_schema) | Non (JSON mode uniquement) |
| JSON mode | Oui | Via structured outputs | Oui | Oui |
| Tool use strict | Oui (strict: true) | Oui (strict: true) | Oui | Oui |
| Conformité schéma | 100 % (annoncé) | 100 % (modèles compatibles) | Élevée | JSON valide, pas schéma strict |
| Pydantic intégré | Oui (SDK Python) | Non natif (via Instructor) | Non natif | Non natif |
Cas d’usage typiques
| Cas d’usage | Schéma type | Pourquoi structured |
|---|---|---|
| Extraction d’entités | {name, email, company, role} | Données injectées dans un CRM, format garanti |
| Classification | {label, confidence, explanation} | Label parsable automatiquement pour des pipelines |
| Analyse de sentiment | {sentiment, score, aspects: [{topic, polarity}]} | Données structurées pour dashboards analytics |
| Extraction de données tabulaires | {rows: [{col1, col2, col3}]} | Import direct en base de données ou spreadsheet |
| Audit de code | {bugs: [{line, severity, description, fix}], security_risks: [...]} | Intégration CI/CD, traitement automatisé |
| Planification d’agent | {steps: [{action, tool, params, expected_result}]} | Orchestration programmatique de workflows agentiques |
Limitations et pièges
Les structured outputs ne sont pas sans contraintes. Premièrement, les schémas supportés ont des limitations. Chez Anthropic et OpenAI, les schémas JSON doivent respecter certaines règles : pas de $ref récursifs infinis, additionalProperties: false recommandé, pas de oneOf/anyOf dans certains cas. Consultez la documentation de chaque provider pour les restrictions exactes.
Deuxièmement, la conformité au schéma ne garantit pas la qualité du contenu. Le modèle peut produire un JSON parfaitement valide avec des valeurs absurdes ou des champs vides. La validation structurelle est assurée, mais la validation sémantique reste votre responsabilité. Un champ "email": "not_an_email" passera la validation JSON Schema mais échouera en pratique.
Troisièmement, les structured outputs peuvent augmenter la latence. La contrainte de génération côté serveur ajoute un surcoût computationnel par rapport à la génération libre. Pour les applications temps réel à très faible latence, testez l’impact sur vos métriques de performance.
Quatrièmement, la compatibilité varie selon les modèles. Chez Anthropic, les structured outputs natifs (via output_config) sont disponibles sur Claude Opus 4.6 et Sonnet 4.6. Les modèles plus anciens nécessitent le workaround tool use. Chez OpenAI, les structured outputs sont disponibles à partir de GPT-4o-2024-08-06.
Bibliothèques et outils tiers
Plusieurs bibliothèques facilitent l’utilisation de sorties structurées, même avec des providers qui ne les supportent pas nativement :
Instructor est la bibliothèque la plus populaire. Construite sur Pydantic, elle permet de définir un schéma de sortie comme un modèle Python, gère la validation, les retries automatiques, et supporte plus de 15 providers (OpenAI, Anthropic, Google, Ollama, DeepSeek). C’est la solution recommandée quand vous avez besoin d’un comportement uniforme à travers plusieurs providers.
LangChain standardise les interactions avec les modèles et propose des méthodes de structured output via .with_structured_output() qui s’adaptent automatiquement au provider utilisé (JSON Schema natif quand disponible, tool use en fallback).
Outlines et Guidance sont des bibliothèques qui appliquent des contraintes de grammaire pendant la génération pour les modèles locaux (via Ollama, vLLM). Elles garantissent la conformité au schéma sans dépendre d’une fonctionnalité API.
Structured outputs vs formats alternatifs
JSON n’est pas le seul format structuré pour les sorties de LLM. Selon le cas d’usage, d’autres formats peuvent être plus adaptés :
| Format | Avantages | Inconvénients | Cas d’usage |
|---|---|---|---|
| JSON (structured outputs) | Standard universel, parsable partout, schéma garanti | Verbeux, coûteux en tokens | API, intégrations, bases de données |
| JSON (mode simple) | JSON valide garanti, moins strict | Pas de conformité schéma | Cas simples sans schéma rigide |
| XML | Bien géré par les LLM, balises sémantiques, extraction facile | Pas de mode natif chez la plupart des providers | Extraction de contenu, documents structurés |
| Markdown | Lisible par l’humain, léger en tokens | Pas de validation stricte, parsing plus fragile | Contenu éditorial, documentation, rapports |
| CSV/TSV | Ultra-compact, import tableur direct | Pas de types, pas de structure imbriquée | Données tabulaires simples |
| YAML | Lisible, compact, supporte les commentaires | Pas de mode natif, parsing sensible à l’indentation | Configuration, données hiérarchiques légères |
En pratique, JSON avec structured outputs est la solution par défaut pour toute intégration programmatique. Les formats alternatifs sont pertinents pour des cas spécifiques : XML quand les LLM doivent extraire du contenu balisé (les modèles Claude gèrent particulièrement bien le XML), Markdown pour les contenus destinés à la lecture humaine, et CSV pour les exports tabulaires simples.
Impact sur la qualité des réponses
Un aspect souvent négligé : contraindre le format de sortie peut influencer la qualité du contenu. Quand le modèle est forcé de structurer sa réponse en JSON avec des champs précis, il ne peut pas « prendre son temps » avec des explications intermédiaires ou du raisonnement en langage naturel. Pour les tâches simples (classification, extraction), c’est un avantage : le modèle va droit au but. Pour les tâches complexes (analyse nuancée, raisonnement multi-étapes), la contrainte de format peut réduire la qualité.
La solution pour les tâches complexes est de combiner extended thinking (raisonnement interne libre) avec structured output (réponse finale contrainte). Chez Anthropic, le modèle peut raisonner librement dans son bloc thinking, puis produire une sortie JSON structurée dans son bloc text. Le raisonnement n’est pas contraint, seule la sortie finale l’est. C’est le meilleur des deux mondes pour les tâches qui nécessitent à la fois du raisonnement et un format exploitable.
Impact sur la consommation de tokens
Les sorties JSON sont plus verbeuses que le texte naturel. Les clés de propriétés, les guillemets, les accolades et les crochets consomment des tokens supplémentaires. Un résultat de classification qui tiendrait en un mot (« important ») devient {"label": "important", "confidence": 0.95}, soit environ 10× plus de tokens. À fort volume, cette surcharge est significative.
Stratégies d’optimisation : utilisez des noms de clés courts (mais lisibles), évitez les champs optionnels inutiles, et pour les pipelines batch où le coût est critique, envisagez de n’utiliser les structured outputs que pour les champs essentiels et de traiter les informations complémentaires via un second appel si nécessaire. Chez Anthropic, les tokens de sortie coûtent $25/M sur Opus 4.6 : chaque token superflu dans un JSON compte à fort volume.
Bonnes pratiques
Utilisez toujours additionalProperties: false dans vos schémas pour empêcher le modèle d’ajouter des champs non prévus. Ajoutez des descriptions aux propriétés de votre schéma : le modèle les utilise pour mieux comprendre ce qu’il doit remplir, et des descriptions précises améliorent la qualité de l’extraction. Validez les valeurs côté client même quand la structure est garantie (un e-mail syntaxiquement valide comme JSON n’est pas forcément un vrai e-mail). Utilisez les types enum pour les champs à valeurs prédéfinies (catégories, labels, statuts) : cela contraint physiquement la sortie aux valeurs autorisées.
Testez sur un échantillon représentatif avant le déploiement : la conformité structurelle est garantie, mais la précision du contenu dépend de la qualité du prompt et du modèle. Versionnez vos schémas comme du code, car un changement de schéma peut casser vos consommateurs aval. Documentez chaque champ avec un exemple attendu dans la description du schéma. Enfin, mesurez le taux de champs vides ou de valeurs par défaut dans les réponses : un taux élevé indique que le modèle ne comprend pas bien ce qui est attendu et que votre prompt ou vos descriptions de schéma doivent être affinés.
Verdict
Les structured outputs sont un changement fondamental dans l’utilisabilité des LLM en production. Ils transforment un modèle de génération de texte imprévisible en une interface programmatique fiable, comparable à un endpoint d’API REST classique avec un contrat de données garanti. L’époque où il fallait parser des réponses textuelles avec des regex fragiles et des retries est révolue pour les applications utilisant les modèles récents. Utilisez les structured outputs natifs (JSON Schema via l’API) quand ils sont disponibles, le tool use comme fallback robuste, et réservez le prompt engineering + prefilling aux cas où les deux premières options ne sont pas accessibles. Et n’oubliez pas : la conformité au schéma ne remplace pas la validation métier, et la verbosité du JSON a un coût en tokens qu’il faut prendre en compte à fort volume.
FAQ
Qu’est-ce qu’un structured output dans un LLM ?
C’est une sortie de modèle qui respecte un format structuré prédéfini (JSON, XML), garanti par une contrainte au niveau de l’API. Contrairement à une simple instruction « réponds en JSON » dans le prompt, les structured outputs natifs appliquent la contrainte pendant la génération, rendant physiquement impossible la production d’une sortie non conforme au schéma défini.
Quelle est la différence entre JSON mode et structured outputs ?
Le JSON mode garantit uniquement que la sortie est du JSON valide (parsable). Les structured outputs garantissent que la sortie est du JSON valide ET conforme à un schéma spécifique (bons champs, bons types, bonnes contraintes). En production, préférez toujours les structured outputs quand ils sont disponibles.
Comment obtenir des sorties structurées avec Claude ?
Trois options, par ordre de fiabilité : (1) Structured outputs natifs via output_config: {format: {type: "json_schema", schema: {...}}} sur Claude Opus 4.6 et Sonnet 4.6. (2) Tool use comme workaround : définissez un outil fictif dont le schéma d’entrée correspond à votre format souhaité. (3) Prompt engineering + prefilling de l’assistant message avec {. L’option 1 est recommandée quand disponible.
Les structured outputs ralentissent-ils la génération ?
Légèrement. La contrainte de schéma côté serveur ajoute un surcoût computationnel par rapport à la génération libre. En pratique, l’impact sur la latence est généralement modeste pour les schémas simples, mais peut devenir perceptible pour les schémas très complexes avec de nombreuses contraintes imbriquées. Testez sur votre cas d’usage spécifique.
Faut-il encore valider la sortie si le schéma est garanti ?
Oui. Les structured outputs garantissent la conformité structurelle (JSON valide, bons champs, bons types), mais pas la qualité sémantique du contenu. Un champ "email" de type string peut contenir "pas un email" et rester valide structurellement. Ajoutez une validation métier côté client pour les champs critiques (formats d’e-mail, plages de dates, valeurs cohérentes).