Sommaire
En 2023 on priait pour que le LLM sorte du JSON propre. En 2026, on le force. Voici les 3 techniques qui marchent.
1. Schema-guided generation (la meilleure)
OpenAI et Anthropic supportent maintenant nativement le structured output : vous fournissez un JSON Schema, et le modèle est contraint au niveau du décodage à produire une sortie qui match. Pas un parsing ensuite, une contrainte pendant la génération.
response = await openai.chat.completions.create({\n model: 'gpt-4o',\n messages,\n response_format: {\n type: 'json_schema',\n json_schema: {\n name: 'invoice_extraction',\n schema: {\n type: 'object',\n properties: {\n invoice_number: { type: 'string' },\n total: { type: 'number' },\n items: { type: 'array', items: { type: 'string' } }\n },\n required: ['invoice_number', 'total']\n },\n strict: true\n }\n }\n});Avec strict: true, c'est 100 % garanti. Plus de try/catch sur JSON.parse.
2. Tool use comme surrogate
Claude supporte les structured outputs via le tool use : vous définissez un "tool" dont le schéma d'input est votre sortie désirée, et vous forcez son appel. Le modèle vous retourne l'objet typé parfaitement.
tools: [{\n name: 'return_analysis',\n input_schema: {\n type: 'object',\n properties: {\n sentiment: { type: 'string', enum: ['positive', 'neutral', 'negative'] },\n confidence: { type: 'number' }\n }\n }\n}],\ntool_choice: { type: 'tool', name: 'return_analysis' }3. Instructor / constrained decoding (open source)
Instructor (Python) ou Ollama en local permettent du constrained decoding contre un schéma Pydantic/Zod. Valider en post, si invalide, retry avec l'erreur en prompt. Fonctionne avec tout modèle, utile pour les modèles open source.
Les pièges
- Énumérations mal pensées : trop de valeurs possibles = le modèle dérive. Gardez < 20 valeurs par enum.
- Schémas trop imbriqués : au-delà de 3 niveaux, la qualité chute même en mode strict.
- Descriptions manquantes : chaque champ devrait avoir une description claire, le modèle l'utilise pour décider quoi mettre.
Mon pattern en 2026
// 1. Définir le schéma Zod côté TS\nconst InvoiceSchema = z.object({\n number: z.string(),\n total: z.number().positive(),\n items: z.array(z.string())\n});\n\n// 2. Convertir en JSON Schema pour l'API\nconst schema = zodToJsonSchema(InvoiceSchema);\n\n// 3. Appel en mode structured\nconst raw = await callLLM(schema, prompt);\n\n// 4. Validation Zod en defense en profondeur\nconst invoice = InvoiceSchema.parse(raw);C'est l'assurance d'une excellente fiabilité en production. Besoin d'intégrer ça chez vous ?