Structured Output : JSON garanti
Mis à jour le 29 juillet 2026
Structured Output : JSON garanti
Le Structured Output est l’une des fonctionnalités les plus puissantes de la Responses API. Au lieu d’espérer que le modèle retourne du JSON valide, vous le garantissez en fournissant un schéma : le modèle est alors contraint structurellement de le respecter. C’est ce qui transforme un modèle de langage en composant fiable d’une chaîne de traitement automatisée.
Le problème que cela résout
Demander du JSON dans le prompt fonctionne la plupart du temps, et c’est précisément ce qui rend l’approche dangereuse. Un jour sur cent, le modèle préface sa réponse d’une phrase d’introduction, entoure le tout d’un bloc de code Markdown ou renomme un champ — et votre json.loads() lève une exception en production, sur une requête que vous ne pourrez pas reproduire.
from openai import OpenAI
client = OpenAI()
# Sans structure — le modèle peut retourner n'importe quel format
response = client.responses.create(
model="gpt-5.6-terra",
input="Donnez-moi les infos sur Paris en JSON."
)
print(response.output_text)
# Résultat imprévisible :
# Parfois du JSON valide, parfois du texte avec du JSON,
# parfois un format inattendu...
Le paramètre text change la nature de la garantie. Vous y déclarez un format json_schema accompagné du schéma attendu, et la sortie s’y conforme : la désérialisation devient une opération sûre.
response = client.responses.create(
model="gpt-5.6-terra",
input="Donnez-moi les informations sur Paris.",
text={
"format": {
"type": "json_schema",
"name": "ville_info",
"strict": True,
"schema": {
"type": "object",
"properties": {
"nom": {"type": "string"},
"pays": {"type": "string"},
"population": {"type": "integer"},
"langue_officielle": {"type": "string"},
"monuments": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["nom", "pays", "population", "langue_officielle", "monuments"],
"additionalProperties": False
}
}
}
)
import json
data = json.loads(response.output_text)
print(f"Ville : {data['nom']}")
print(f"Population : {data['population']:,}")
print(f"Monuments : {', '.join(data['monuments'])}")
# Résultat GARANTI en JSON valide :
# Ville : Paris
# Population : 2,161,000
# Monuments : Tour Eiffel, Louvre, Notre-Dame, Arc de Triomphe, Sacre-Coeur
Pydantic plutôt que du JSON Schema à la main
Écrire le schéma à la main devient vite pénible et sujet aux fautes de frappe. Pydantic vous permet de le décrire sous forme de classe Python, d’en générer le JSON Schema avec model_json_schema(), puis de valider et typer la réponse avec model_validate_json(). Vous obtenez au bout de la chaîne un objet Python complet, avec l’autocomplétion de votre éditeur, plutôt qu’un dictionnaire anonyme.
from pydantic import BaseModel
from typing import Optional
import json
class Produit(BaseModel):
nom: str
prix: float
categorie: str
en_stock: bool
description: Optional[str] = None
response = client.responses.create(
model="gpt-5.6-terra",
input="Décrivez un ordinateur portable gaming haut de gamme.",
text={
"format": {
"type": "json_schema",
"name": "produit",
"strict": True,
"schema": Produit.model_json_schema()
}
}
)
# Parser et valider avec Pydantic
produit = Produit.model_validate_json(response.output_text)
print(f"Produit : {produit.nom}")
print(f"Prix : {produit.prix} EUR")
print(f"En stock : {'Oui' if produit.en_stock else 'Non'}")
# Résultat :
# Produit : ASUS ROG Strix G18
# Prix : 2499.99 EUR
# En stock : Oui
Rien ne vous limite aux structures plates. En composant les classes, vous décrivez des objets imbriqués et des listes d’objets, ce dont vous aurez besoin dès la première fiche client un peu réaliste : une entreprise contient des contacts, chaque contact possède une adresse.
from pydantic import BaseModel
class Adresse(BaseModel):
rue: str
ville: str
code_postal: str
pays: str
class Contact(BaseModel):
nom: str
prenom: str
email: str
telephone: str
adresse: Adresse
class Entreprise(BaseModel):
nom: str
secteur: str
employes: int
contacts: list[Contact]
response = client.responses.create(
model="gpt-5.6-terra",
input="Générez une fiche entreprise fictive dans le secteur tech "
"avec 2 contacts.",
text={
"format": {
"type": "json_schema",
"name": "entreprise",
"strict": True,
"schema": Entreprise.model_json_schema()
}
}
)
entreprise = Entreprise.model_validate_json(response.output_text)
print(f"Entreprise : {entreprise.nom} ({entreprise.secteur})")
for c in entreprise.contacts:
print(f" - {c.prenom} {c.nom} : {c.email}")
Deux usages qui reviennent partout
Le premier est l’extraction : transformer un texte rédigé en enregistrement exploitable. L’annonce d’une conférence, écrite en français courant, devient une ligne de base de données avec un titre, une date, un lieu et un nombre de participants — sans expression régulière, et sans casser dès que la formulation change.
class Evenement(BaseModel):
titre: str
date: str
lieu: str
participants: int
texte = """
La conférence PyCon France 2026 se tiendra les 15 et 16 novembre
au Palais des Congrès de Lyon. Plus de 800 développeurs Python
sont attendus pour cette édition.
"""
response = client.responses.create(
model="gpt-5.6-terra",
input=f"Extrayez les informations de cet événement :\n{texte}",
text={
"format": {
"type": "json_schema",
"name": "evenement",
"strict": True,
"schema": Evenement.model_json_schema()
}
}
)
evt = Evenement.model_validate_json(response.output_text)
print(f"{evt.titre} - {evt.date} a {evt.lieu} ({evt.participants} participants)")
# Résultat : PyCon France 2026 - 15-16 novembre 2026 a Lyon (800 participants)
Le second est la classification. En déclarant une énumération, vous restreignez le modèle à un vocabulaire fermé : il ne pourra pas répondre « plutôt positif » ni « positive » là où votre code attend positif. C’est la différence entre une valeur exploitable directement et une valeur à normaliser après coup.
from enum import Enum
class Sentiment(str, Enum):
positif = "positif"
negatif = "negatif"
neutre = "neutre"
class Analyse(BaseModel):
sentiment: Sentiment
confiance: float
mots_cles: list[str]
response = client.responses.create(
model="gpt-5.6-terra",
input="Analysez ce commentaire : 'Super service, livraison rapide !'",
text={
"format": {
"type": "json_schema",
"name": "analyse_sentiment",
"strict": True,
"schema": Analyse.model_json_schema()
}
}
)
analyse = Analyse.model_validate_json(response.output_text)
print(f"Sentiment : {analyse.sentiment.value} ({analyse.confiance:.0%})")
# Résultat : Sentiment : positif (95%)
Ce que le mode strict exige
La garantie a une contrepartie : quand strict: True, le schéma doit respecter un sous-ensemble précis de JSON Schema. Ces contraintes sont la première source d’erreurs 400 chez les débutants, autant les connaître avant de les rencontrer :
- Toutes les propriétés doivent être dans
required additionalPropertiesdoit êtreFalse- Les types supportés :
string,number,integer,boolean,array,object,null - Pas de
oneOf,anyOfavec des types mixtes - Utilisez
Optional[str]qui se traduit par{"anyOf": [{"type": "string"}, {"type": "null"}]}
La règle sur required surprend souvent : un champ facultatif ne s’exprime pas en le retirant de la liste, mais en autorisant la valeur null, exactement ce que produit Optional[str] côté Pydantic.
Points clés à retenir
- Le Structured Output garantit que la réponse respecte votre schéma JSON
- Utilisez
text={"format": {"type": "json_schema", ...}}dans la Responses API - Pydantic est l’outil idéal pour définir et valider les schémas
- Activez
strict: Truepour la conformité garantie du schéma - Idéal pour l’extraction de données, la classification et la génération structurée