Sorties structurées : du JSON garanti
Sur cette page
Objectifs
À l’issue de cette leçon, vous serez capable de :
- Obtenir une réponse JSON conforme à un schéma avec les sorties JSON (
output_config.format) - Reconnaître l’ancienne méthode de l’outil « prétexte », et savoir pourquoi elle ne fonctionne plus sur les modèles récents
- Connaître les limites des sorties structurées
Le besoin
Quand vous demandez du JSON à un modèle en langage naturel, vous obtenez souvent du JSON valide, parfois précédé d’un « Voici le résultat : », parfois enveloppé dans un bloc de code, parfois avec une clé renommée. Votre parseur casse une fois sur vingt, ce qui est intenable dans une chaîne de traitement automatisée.
L’ancienne méthode : un outil prétexte, forcé
Pendant longtemps, l’astuce consistait à détourner le tool use : définir un outil « fictif » dont le schéma correspond au JSON voulu (par exemple print_sentiment_scores), forcer son appel avec tool_choice: {"type": "tool", ...}, lire les données dans input, et ne jamais exécuter aucune fonction. Vous la croiserez dans du code existant.
Elle repose sur l’appel forcé, que Claude Opus 5.5, Sonnet 5.5, Fable 5.1 et Mythos 5.1 refusent (erreur 400, leçon 5). Pour obtenir une réponse dans une forme JSON fixe, la documentation officielle renvoie désormais aux sorties structurées.
La méthode actuelle : les sorties JSON
Les sorties structurées contraignent la réponse de Claude à un schéma, par décodage contraint : la réponse est toujours du JSON valide, avec les champs obligatoires et les types attendus. Elles regroupent deux fonctions complémentaires :
- les sorties JSON (
output_config.format) : la réponse de Claude elle-même suit votre schéma ; - le strict tool use (
"strict": true) : les entrées des appels d’outils suivent leur schéma (leçon 8).
Les sorties structurées sont disponibles sur tous les modèles, en version stable : aucun en-tête bêta n’est nécessaire.
Exemple : analyse de sentiment, avec un schéma JSON
Le schéma part dans output_config.format, avec type: "json_schema". Chaque objet du schéma porte additionalProperties: false, et la plage de valeurs est indiquée dans les descriptions : les contraintes numériques comme minimum ou maximum ne font pas partie des schémas pris en charge.
import json
from anthropic import Anthropic
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": "Analyse le sentiment de ce texte : « Ce produit est absolument horrible, je suis très déçu. »"
}],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"positive_score": {"type": "number", "description": "Score de sentiment positif, de 0 à 1"},
"negative_score": {"type": "number", "description": "Score de sentiment négatif, de 0 à 1"},
"neutral_score": {"type": "number", "description": "Score de sentiment neutre, de 0 à 1"}
},
"required": ["positive_score", "negative_score", "neutral_score"],
"additionalProperties": False
}
}
},
)
# Le JSON arrive dans le bloc de texte de la réponse
texte = next(b.text for b in response.content if b.type == "text")
scores = json.loads(texte)
Exemple : extraction d’entités, avec un modèle Pydantic
En Python, la méthode recommandée par la documentation est client.messages.parse() avec un modèle Pydantic passé en output_format : le SDK transforme le schéma, l’envoie dans output_config.format, valide la réponse et renvoie l’objet dans parsed_output.
from pydantic import BaseModel
class Entites(BaseModel):
personnes: list[str]
organisations: list[str]
lieux: list[str]
texte = "Marie Curie a reçu le prix Nobel à Stockholm, décerné par l'Académie royale des sciences de Suède."
response = client.messages.parse(
model="claude-sonnet-5-5",
max_tokens=4096,
messages=[{"role": "user", "content": f"Extrais les personnes, organisations et lieux de ce texte : {texte}"}],
output_format=Entites,
)
entites = response.parsed_output # une instance de Entites
print(entites.lieux)
Les trois listes sont toujours présentes, éventuellement vides : plus de test d’existence de clé dans le code appelant.
Ce qu’il faut savoir
- Première requête plus lente. La première fois qu’un schéma est utilisé, sa grammaire est compilée ; elle reste ensuite en cache 24 heures après sa dernière utilisation.
- Quelques jetons de plus. Claude reçoit automatiquement un prompt système supplémentaire qui décrit le format attendu. Changer
output_config.formatinvalide le cache de prompt de la conversation. - Vérifiez
stop_reason. En cas de refus ("refusal") ou de réponse coupée par la limite de jetons ("max_tokens"), la sortie peut ne pas respecter le schéma ; dans le second cas, relancez avec unmax_tokensplus élevé. - La casse des valeurs
enumn’est pas garantie. Comparez-les sans tenir compte des majuscules, et évitez des valeurs qui ne diffèrent que par la casse. - Incompatibilités. Les sorties JSON ne se combinent ni avec les citations (erreur 400) ni avec le pré-remplissage de la réponse.
Sorties JSON ou strict tool use ?
Les deux répondent à des besoins différents, et se combinent dans une même requête :
| Vous voulez… | Utilisez |
|---|---|
| Que la réponse de Claude suive un format JSON (extraction, classement, rapport) | Les sorties JSON : output_config.format |
| Que les entrées des appels d’outils respectent leur schéma | Le strict tool use : "strict": true (leçon 8) |
À vous de jouer
Un traducteur multilingue. Écrivez une fonction translate qui, pour une phrase donnée, renvoie sa traduction en anglais, espagnol, français, japonais et arabe, dans un objet aux cinq champs obligatoires.
Voir la solution
from pydantic import BaseModel
class Traduction(BaseModel):
english: str
spanish: str
french: str
japanese: str
arabic: str
def translate(phrase):
response = client.messages.parse(
model="claude-sonnet-5-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"Traduis cette phrase en anglais, espagnol, français, japonais et arabe : « {phrase} »"
}],
output_format=Traduction,
)
return response.parsed_output
print(translate("Combien ça coûte ?"))
Sources officielles : Structured outputs (documentation Claude :
output_config.format,messages.parse, limites, incompatibilités) · Define tools — Forcing tool use
Points clés à retenir
- L’outil « prétexte » forcé ne fonctionne plus sur les modèles qui refusent l’appel forcé
- Les sorties JSON (
output_config.format) garantissent une réponse conforme au schéma, sur tous les modèles - En Python,
client.messages.parse()avec un modèle Pydantic renvoie directement l’objet dansparsed_output - Vérifiez
stop_reason, comparez lesenumsans tenir compte de la casse, et pas de citations avec les sorties JSON
Crédits et licence — Leçon réécrite par Corsen à partir du notebook « 03_structured_outputs » du tutoriel Tool use d’Anthropic (dépôt GitHub anthropics/courses, publié sous licence CC BY-NC 4.0), et mise à jour d’après la documentation officielle citée ci-dessus.