Données structurées
Sur cette page
Quand un programme doit lire la réponse de Claude, un problème revient sans cesse : Claude entoure volontiers les données d’explications, de titres et de Markdown. Très bien dans une conversation, rédhibitoire pour un parseur. Cette leçon montre comment obtenir un JSON propre et garanti, avec les sorties structurées de l’API Converse.
Le problème
Imaginez une application web qui génère des règles AWS EventBridge. L’utilisateur décrit ce qu’il veut surveiller, clique sur « Générer », et attend un JSON qu’il peut copier tel quel dans sa console. Par défaut, Claude répond plutôt ainsi :
# Règle EventBridge
```json
{
"source": ["aws.ec2"],
"detail-type": ["EC2 Instance State-change Notification"],
"detail": {"state": ["running"]}
}
```
Cette règle capture les changements d'état des instances EC2.
Un titre, un bloc de code, une phrase de conclusion : parfait pour de la documentation, mais la donnée utile est noyée dans son emballage, et json.loads échoue sur l’ensemble.
L’ancienne recette, et pourquoi elle ne marche plus
Le cours officiel combine les deux techniques de la leçon 9 : il pré-remplit le message assistant avec l’ouverture d’un bloc de code JSON, puis coupe la génération avec une séquence d’arrêt sur les trois accents graves qui le ferment. Claude saute ainsi l’en-tête et s’arrête avant la conclusion. Avec Haiku 5.5, cette recette échoue dès la première étape, puisque le pré-remplissage est refusé. Elle a un successeur plus solide.
Les sorties structurées
Les sorties structurées contraignent la réponse de Claude à un schéma JSON que vous fournissez. Avec l’API Converse, le schéma passe dans outputConfig.textFormat, sous forme de chaîne JSON. Notre fonction chat gagne un paramètre pour le transmettre :
import json
def chat(messages, system=None, stop_sequences=None, output_schema=None):
params = {"modelId": model_id, "messages": messages}
if system:
params["system"] = [{"text": system}]
if stop_sequences:
params["inferenceConfig"] = {"stopSequences": stop_sequences}
if output_schema:
params["outputConfig"] = {
"textFormat": {
"type": "json_schema",
"structure": {
"jsonSchema": {
"schema": json.dumps(output_schema),
"name": "reponse",
}
},
}
}
response = client.converse(**params)
content = response["output"]["message"]["content"]
return "".join(block["text"] for block in content if "text" in block)
Pour la règle EventBridge, le schéma décrit les trois champs attendus :
SCHEMA_REGLE = {
"type": "object",
"properties": {
"source": {"type": "array", "items": {"type": "string"}},
"detail-type": {"type": "array", "items": {"type": "string"}},
"detail": {
"type": "object",
"properties": {"state": {"type": "array", "items": {"type": "string"}}},
"required": ["state"],
"additionalProperties": False,
},
},
"required": ["source", "detail-type", "detail"],
"additionalProperties": False,
}
messages = []
add_user_message(messages, "Génère une règle EventBridge qui détecte les arrêts d'instances EC2.")
text = chat(messages, output_schema=SCHEMA_REGLE)
Bedrock compile le schéma en grammaire, puis restreint la génération aux tokens que cette grammaire autorise : Claude ne peut produire que du JSON conforme, sans préambule ni conclusion.
Les règles du schéma
D’après la documentation AWS au 5 novembre 2026 :
- Chaque objet du schéma doit déclarer
"additionalProperties": False. Sinon, Bedrock répond par uneValidationException. - Les bornes numériques (
minimum,maximum), les longueurs de chaîne (minLength,maxLength) et les schémas récursifs ne sont pas pris en charge. Décrivez ces contraintes dans le texte de la demande, et vérifiez-les dans votre code. - Un schéma compte au plus 12 propriétés facultatives, c’est-à-dire absentes de
required, toutes profondeurs confondues. - Les sorties structurées sont incompatibles avec les citations, que la section 6 présente.
Le premier appel est lent
La première requête qui utilise un nouveau schéma compile la grammaire, ce qui peut prendre jusqu’à environ 90 secondes. Les requêtes suivantes réutilisent la grammaire, gardée en cache 24 heures, et répondent à la vitesse habituelle. Or le client boto3 abandonne la lecture d’une réponse au bout de 60 secondes par défaut. AWS recommande donc un délai d’au moins 120 secondes :
import boto3
from botocore.config import Config
client = boto3.client(
"bedrock-runtime",
region_name="us-east-1",
config=Config(read_timeout=120),
)
Traiter le résultat
Il ne reste qu’à convertir le texte en dictionnaire Python :
regle = json.loads(text)
print(regle["detail"]["state"])
Deux réserves. Le format est garanti, pas le fond : une règle parfaitement conforme peut viser le mauvais état ou le mauvais service, gardez vos contrôles métier. Et si la réponse atteint la limite de tokens, la génération s’arrête avec stopReason à max_tokens et le JSON est incomplet, même avec un schéma. En production, vérifiez stopReason avant json.loads.
Au-delà du JSON
Les sorties structurées produisent du JSON. Pour du code Python, du CSV ou un fichier de configuration, faites porter le contenu par un champ texte du schéma, par exemple {"code": "..."}, puis extrayez-le.
À vous de jouer
Votre extracteur. Écrivez un schéma pour extraire d’un courriel le nom de l’expéditeur, la date d’un rendez-vous et une liste d’actions, puis testez-le sur trois courriels différents. Mesurez la durée du premier appel et celle du deuxième.
Une réponse jamais incomplète. Modifiez chat pour qu’elle lève une exception quand stopReason vaut max_tokens. Provoquez le cas avec inferenceConfig={"maxTokens": 50} et un schéma qui demande un long texte.
Sources officielles : Structured data (Claude Academy) · Get validated JSON results from models (documentation AWS) · Structured outputs (documentation Claude)
Points clés à retenir
- Par défaut, Claude emballe les données de texte et de Markdown, ce qui casse un parseur.
- Le couple pré-remplissage et séquence d’arrêt du cours officiel ne fonctionne plus avec Haiku 5.5.
- Les sorties structurées garantissent un JSON conforme au schéma : sur Converse,
outputConfig.textFormat, avec le schéma en chaîne JSON. "additionalProperties": Falsesur chaque objet ; pas de bornes numériques ni de longueurs de chaîne.- Le premier appel avec un nouveau schéma peut prendre 90 secondes : réglez
read_timeoutà 120. - Le format est garanti, pas le fond ; vérifiez
stopReasonavantjson.loads.