Sortie structurée LLM : JSON Schema obligatoire et fiabilité des appels d'outils

Une alerte en production : échec d’appel d’outil Agent, cinq retries consécutifs, tous des erreurs de format de paramètres. Dans les logs, le champ city devait être "北京", le LLM renvoyait {"name": "北京", "id": null}. Le parseur plante, tout le pipeline de traitement s’arrête.
C’était un gros piège l’année dernière.
Depuis, j’ai étudié systématiquement la sortie structurée des LLM — des Structured Outputs d’OpenAI au Tool Use d’Anthropic, du retry automatique d’Instructor au décodage contraint d’Outlines. Au début, je pensais qu’un « meilleur prompt » suffisait. En réalité, ce n’est pas un problème de prompt, c’est un problème d’architecture de fiabilité.
Cet article présente cette « architecture de fiabilité à trois niveaux » : validation des paramètres, retry en cas d’échec, décodage contraint. En fin de parcours, comparaison transversale OpenAI, Claude et Gemini pour choisir la bonne approche. Quelques modèles de code prêts pour la production sont inclus.
1. Pourquoi la sortie structurée est la base des Agents
Commençons par le « dérive de format » — pas un cas isolé, le cauchemar de tout développeur Agent.
Trois formes de dérive de format
Première forme : champs manquants. Vous demandez un objet utilisateur avec name, age, email ; le LLM renvoie {"name": "张三"} — les deux autres champs ont disparu. Pas à chaque fois, mais parfois. En production, « parfois » veut dire « inévitablement ».
Deuxième forme : erreur de type. La doc est claire : user_id est un entier. Le LLM renvoie "user_id": "12345", une chaîne. La validation Pydantic échoue, toute la chaîne d’appels se brise.
Troisième forme : contenu superflu. La plus insidieuse. Vous voulez du JSON ; le modèle ajoute « Here is the response: » au début et « I hope this helps! » à la fin. Le parseur JSON ne sait plus quoi en faire.
Les chiffres officiels d’OpenAI parlent d’eux-mêmes : JSON Mode (JSON valide uniquement) échoue dans 5-10 % des cas ; Structured Outputs (respect forcé du Schema) échoue à moins de 0,1 %. Deux ordres de grandeur d’écart.
Pourquoi c’est si important
Vous pourriez penser : « Ce n’est qu’un échec de parsing, quelques retries de plus. »
Le retry n’est pas gratuit.
Coût des appels API. Un appel GPT-4 peut coûter quelques centimes ; cinq retries, quelques euros. Si votre Agent traite 100 000 requêtes par jour avec en moyenne deux retries par requête — faites le calcul.
Latence cumulée. Deux secondes par appel, trois retries : l’utilisateur attend plus de six secondes. Inacceptable en dialogue temps réel.
Expérience utilisateur. L’utilisateur demande la météo, l’Agent tourne dix secondes, puis « erreur système ». Il ne reviendra pas.
La sortie structurée n’est donc pas un « plus », c’est la base d’un Agent stable. La suite explique comment y parvenir — pas avec « un meilleur prompt », mais avec une architecture fiable.
2. Architecture de fiabilité à trois niveaux
Cette architecture résume beaucoup d’essais et d’erreurs. Ce n’est pas une solution miracle, mais elle fait passer le taux d’erreur de format de 5-10 % à quasi zéro.
L1 : couche de validation des paramètres — première ligne de défense
Rôle simple : définir la structure attendue avec Pydantic, forcer la conversion de types, filtrer par liste blanche.
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List
from datetime import datetime
class ToolCallParams(BaseModel):
"""Modèle de paramètres d'appel d'outil"""
city: str = Field(..., min_length=1, max_length=50, description="Nom de la ville")
date: Optional[datetime] = Field(None, description="Date de la requête")
units: str = Field("metric", pattern="^(metric|imperial)$")
@field_validator("city")
@classmethod
def validate_city(cls, v: str) -> str:
# Validation par liste blanche
allowed_cities = {"北京", "上海", "广州", "深圳", "杭州"}
if v not in allowed_cities:
raise ValueError(f"Ville non prise en charge : {v}, prises en charge : {allowed_cities}")
return v
Pydantic fait trois choses : conversion de type (chaîne "123" → entier 123), détection de champs manquants, validation personnalisée. Couche la plus basique et la plus importante.
L2 : couche de retry en cas d’échec — auto-correction avec retour d’erreur
Quand la validation échoue, ne relancez pas aveuglément : renvoyez l’erreur au LLM pour qu’il corrige. La bibliothèque Instructor encapsule bien cette logique.
import instructor
from openai import OpenAI
from pydantic import ValidationError
client = instructor.patch(OpenAI())
def get_weather_with_retry(user_query: str, max_retries: int = 3):
"""Mécanisme de retry avec retour d'erreur"""
messages = [{"role": "user", "content": user_query}]
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model="gpt-4o",
response_model=ToolCallParams, # Modèle Pydantic
messages=messages,
temperature=0.1 # Température basse pour sortie structurée
)
return response # Validation automatique réussie
except ValidationError as e:
# Renvoyer l'erreur au LLM pour correction
error_msg = f"Échec de validation des paramètres : {str(e)}\nCorrigez et renvoyez le JSON au bon format."
messages.append({"role": "assistant", "content": "Génération des paramètres..."})
messages.append({"role": "user", "content": error_msg})
if attempt == max_retries - 1:
raise Exception(f"Échec après {max_retries} tentatives : {e}")
# Exemple d'utilisation
result = get_weather_with_retry("帮我查一下北京明天的天气")
Principe clé : le LLM ne devine pas au hasard ; il sait ce qui est faux et pourquoi. Avec un retour d’erreur, il corrige. En pratique, le taux de succès au retry passe de 60 % à plus de 95 % avec ce mécanisme.
L3 : couche de décodage contraint — éliminer les erreurs à la source
Les deux premiers niveaux corrigent après coup ; L3 prévient avant.
Principe : à chaque token généré, une machine à états finis (FSM) limite les choix possibles et force une séquence conforme au Schema. Comme un « frein » : le modèle ne peut pas dévier.
Deux options principales :
Outlines (open source, modèles locaux) :
from outlines import models, generate
import json
# Charger le modèle local
model = models.transformers("Qwen/Qwen2.5-7B-Instruct")
# Définir le Schema
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}
# Créer le générateur contraint
generator = generate.json(model, schema)
result = generator("提取用户信息: 张三今年28岁")
# 100 % conforme au Schema, sans retry
guided_json de vLLM (grands modèles déployés) :
from vllm import LLM, SamplingParams
llm = LLM(model="Qwen/Qwen2.5-72B-Instruct")
sampling_params = SamplingParams(
temperature=0.0,
guided_decoding_backend="outlines",
guided_json={ # Passer directement le JSON Schema
"type": "object",
"properties": {
"tool_name": {"type": "string"},
"arguments": {"type": "object"}
}
}
)
L3 a un coût de compilation : la FSM doit être construite à partir du Schema. Si le Schema change souvent, chaque reconstruction ajoute de la latence. Pour la plupart des Agents, le Schema est relativement stable — surcoût acceptable.
Comment choisir entre les trois niveaux
| Scénario | Solution recommandée |
|---|---|
| Appels API OpenAI | L1 + L2 (Pydantic + Instructor) |
| Appels API Claude | L1 + L2 (Claude ne supporte pas Strict Mode) |
| Modèles locaux déployés | L1 + L3 (Outlines / vLLM guided_json) |
| Fiabilité maximale exigée | L1 + L2 + L3 combinés |
3. Comparaison des éditeurs : OpenAI, Claude, Gemini
Différences d’implémentation entre éditeurs. Sans comparaison, on tombe facilement dans le piège — « sortie structurée » ne signifie pas la même chose partout.
OpenAI : Strict Mode, conformité forcée
En août 2024, OpenAI a lancé Structured Outputs — la solution la plus fiable parmi les API commerciales actuelles.
Mécanisme central : le paramètre strict: true. Activé, la sortie est contrainte au JSON Schema défini, conformité garantie à 100 %. Sous le capot : décodage contraint par grammaire, proche du principe d’Outlines.
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "提取用户信息"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "user_info",
"strict": True, # Paramètre clé
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}
}
}
)
# Sortie 100 % conforme au Schema
OpenAI annonce un taux d’échec Strict Mode inférieur à 0,1 %. En pratique, je n’ai pas vu d’erreur de format — avec une limite : pas de Schema récursif ; certaines structures imbriquées demandent des contournements.
Anthropic Claude : Tool Use, sans garantie de conformité
Claude emprunte une autre voie — Tool Use (appel d’outil).
Vous définissez un outil, Claude l’appelle avec des arguments. Piège : le paramètre strict existe mais la documentation officielle précise qu’il est ignoré. Claude ne garantit pas que les arguments respectent votre Schema.
Citation officielle Anthropic (mise à jour avril 2026) :
“The
strictparameter is currently ignored for tool definitions. Claude will make a best effort to provide valid arguments, but does not guarantee schema compliance.”
Traduction : il fera de son mieux, sans garantie. Avec Claude pour les appels d’outils, ajoutez L1 (validation) et L2 (retry).
import anthropic
client = anthropic.Anthropic()
# Définition d'outil Claude
tools = [{
"name": "get_weather",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string"}
},
"required": ["city"]
}
}]
response = client.messages.create(
model="claude-3.5-sonnet",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "北京天气"}]
)
# Important : valider manuellement les paramètres tool_use
for block in response.content:
if block.type == "tool_use":
# Validation Pydantic ici
validated_params = ToolCallParams.model_validate(block.input)
Google Gemini : Controlled Generation
Gemini propose Controlled Generation via le paramètre response_schema.
import google.generativeai as genai
model = genai.GenerativeModel('gemini-1.5-pro')
response = model.generate_content(
"提取用户信息",
generation_config={
"response_mime_type": "application/json",
"response_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "age"]
}
}
)
Fiabilité Gemini entre OpenAI et Claude — contrainte présente, mais sans la force « conformité forcée » d’OpenAI. En test, taux d’échec autour de 1-2 % : mieux que JSON Mode, en deçà de Strict Mode.
Modèles open source : Outlines / vLLM
Les modèles open source (Qwen, Llama, Mistral) ne supportent pas nativement la sortie structurée ; il faut des outils externes — Outlines et guided_json de vLLM.
Point intéressant : modèle open source + Outlines peut être plus fiable que certaines API commerciales — la FSM est une contrainte dure, pas un « meilleur effort sans garantie ».
Tableau de choix rapide
| Besoin | Solution recommandée | Raison |
|---|---|---|
| Appels API purs, stabilité | OpenAI + Structured Outputs | 0,1 % d’échec, le plus fiable |
| Raisonnement complexe + outils | Claude + validation L1/L2 | Fort en raisonnement, validation requise |
| Modèle privé déployé | Qwen/Llama + Outlines | Coût maîtrisé, haute fiabilité |
| Format très strict (finance, santé) | OpenAI Strict ou Outlines | Échec quasi nul |
| Prototype rapide | Instructor + API au choix | Bien encapsulé, retry automatique |
4. Modèles de code prêts pour la production
Quelques modèles validés en production — utilisables tels quels ou adaptés.
Modèle 1 : exemple complet OpenAI Structured Outputs
"""
Exemple complet OpenAI Structured Outputs
Usage : appels d'outils, extraction de données, génération de rapports
"""
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import List, Optional
import json
# 1. Définir le modèle Pydantic
class SearchQuery(BaseModel):
"""Paramètres de requête de recherche"""
keywords: List[str] = Field(
...,
min_length=1,
max_length=5,
description="Liste de mots-clés de recherche"
)
filters: Optional[dict] = Field(
default=None,
description="Filtres optionnels"
)
limit: int = Field(
default=10,
ge=1,
le=100,
description="Nombre de résultats"
)
# 2. Modèle Pydantic → JSON Schema
def model_to_schema(model: type[BaseModel]) -> dict:
"""Convertir un modèle Pydantic en JSON Schema"""
schema = model.model_json_schema()
# Nettoyer les métadonnées Pydantic
schema.pop("title", None)
for prop in schema.get("properties", {}).values():
prop.pop("title", None)
return schema
# 3. Appel de sortie structurée
client = OpenAI()
def extract_search_params(user_input: str) -> SearchQuery:
"""Extraire les paramètres de recherche depuis l'entrée utilisateur"""
schema = model_to_schema(SearchQuery)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "你是一个搜索助手,帮助用户提取搜索参数。"
},
{"role": "user", "content": user_input}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "search_query",
"strict": True,
"schema": schema
}
},
temperature=0.1 # Température basse pour sortie structurée
)
# 4. Analyser et valider une seconde fois
raw_content = response.choices[0].message.content
data = json.loads(raw_content)
return SearchQuery.model_validate(data)
# Exemple d'utilisation
if __name__ == "__main__":
query = extract_search_params(
"我想找一些关于 Python 异步编程的文章,只要最近一个月的,最多 20 条"
)
print(query)
# SearchQuery(keywords=['Python', '异步编程'], filters={'date_range': 'last_month'}, limit=20)
Modèle 2 : retry automatique avec Instructor
"""
Exemple de retry automatique Instructor
Usage : API Claude, OpenAI JSON Mode (non Strict), scénarios tolérants aux erreurs
"""
import instructor
from openai import OpenAI
from pydantic import BaseModel, Field, ValidationError
class AgentAction(BaseModel):
"""Décision d'action de l'Agent"""
action_type: str = Field(
...,
pattern="^(search|execute|respond|clarify)$"
)
parameters: dict = Field(default_factory=dict)
reasoning: str = Field(..., min_length=10)
# patch du client OpenAI
client = instructor.patch(OpenAI())
def get_agent_decision(
context: str,
user_request: str,
max_retries: int = 3
) -> AgentAction:
"""
Obtenir la décision d'action de l'Agent avec retry automatique
Args:
context: Contexte de conversation actuel
user_request: Requête utilisateur
max_retries: Nombre maximal de tentatives
Returns:
AgentAction: Décision validée
"""
messages = [
{"role": "system", "content": "你是一个智能助手,分析用户需求并决定下一步行动。"},
{"role": "user", "content": f"上下文: {context}\n\n用户请求: {user_request}"}
]
try:
response = client.chat.completions.create(
model="gpt-4o",
response_model=AgentAction, # Validation automatique Instructor
messages=messages,
max_retries=max_retries, # Retry intégré
temperature=0.1
)
return response
except ValidationError as e:
# Instructor a déjà retenté max_retries fois
raise Exception(f"Erreur de format non corrigeable, vérifiez la définition du modèle : {e}")
# Exemple d'utilisation
decision = get_agent_decision(
context="用户正在查询天气信息",
user_request="帮我查北京明天的天气,要是晴天就推荐户外活动"
)
print(f"Type d'action : {decision.action_type}")
print(f"Paramètres : {decision.parameters}")
print(f"Raisonnement : {decision.reasoning}")
Modèle 3 : sortie structurée avec modèle local Outlines
"""
Exemple Outlines pour modèle local
Usage : déploiement privé, coût sensible, exigences de confidentialité
"""
from outlines import models, generate
from pydantic import BaseModel
from typing import List
import json
# Définir la structure de données
class ProductInfo(BaseModel):
"""Informations produit"""
name: str
price: float
category: str
tags: List[str]
# Charger le modèle (premier chargement : quelques secondes)
model = models.transformers("Qwen/Qwen2.5-7B-Instruct")
# Créer le générateur structuré
# Note : le schema est compilé en FSM au premier appel (~1-2 s)
schema_str = json.dumps(ProductInfo.model_json_schema())
generator = generate.json(model, schema_str)
def extract_product_info(description: str) -> ProductInfo:
"""
Extraire les informations structurées depuis une description produit
Args:
description: Texte de description produit
Returns:
ProductInfo: Informations produit structurées
"""
prompt = f"从以下商品描述中提取关键信息,以 JSON 格式返回:\n{description}"
# Résultat 100 % conforme au Schema
result = generator(prompt)
# Conversion en modèle Pydantic (double validation)
return ProductInfo.model_validate(result)
# Exemple d'utilisation
description = """
这款蓝牙耳机采用最新的降噪技术,价格 299 元,
属于数码配件类,适合运动、通勤等场景使用。
"""
product = extract_product_info(description)
print(product)
# ProductInfo(name='蓝牙耳机', price=299.0, category='数码配件', tags=['运动', '通勤'])
Modèle 4 : flux complet d’appel d’outil
"""
Flux complet de validation des paramètres d'appel d'outil
Inclut : définition Schema → appel LLM → validation → retry → exécution outil
"""
from openai import OpenAI
from pydantic import BaseModel, Field, field_validator, ValidationError
from typing import Callable, Dict, Any
import json
# 1. Modèle de paramètres d'outil
class WeatherQueryParams(BaseModel):
"""Paramètres de l'outil météo"""
city: str = Field(..., min_length=1, max_length=50)
date_offset: int = Field(default=0, ge=-7, le=7, description="Décalage de date, 0 = aujourd'hui")
@field_validator("city")
@classmethod
def validate_city(cls, v: str) -> str:
allowed = {"北京", "上海", "广州", "深圳", "杭州", "成都", "武汉"}
if v not in allowed:
raise ValueError(f"Ville non prise en charge, options : {allowed}")
return v
# 2. Gestionnaire d'appels d'outils
class ToolCallManager:
"""Gère le flux complet d'appel d'outil"""
def __init__(self):
self.client = OpenAI()
self.tools: Dict[str, Callable] = {}
def register_tool(self, name: str, func: Callable, param_model: type[BaseModel]):
"""Enregistrer un outil"""
self.tools[name] = {
"function": func,
"param_model": param_model
}
def execute_with_retry(
self,
tool_name: str,
user_request: str,
max_retries: int = 3
) -> Any:
"""Exécuter un appel d'outil avec retry"""
tool_config = self.tools[tool_name]
param_model = tool_config["param_model"]
schema = param_model.model_json_schema()
messages = [
{"role": "system", "content": f"Extraire les paramètres d'appel de l'outil '{tool_name}'"},
{"role": "user", "content": user_request}
]
for attempt in range(max_retries):
try:
# Appeler le LLM pour obtenir les paramètres
response = self.client.chat.completions.create(
model="gpt-4o",
messages=messages,
response_format={
"type": "json_schema",
"json_schema": {
"name": tool_name,
"strict": True,
"schema": schema
}
},
temperature=0.1
)
# Valider les paramètres
params = param_model.model_validate_json(
response.choices[0].message.content
)
# Exécuter l'outil
return tool_config["function"](params)
except ValidationError as e:
# Retour d'erreur pour correction
messages.append({
"role": "user",
"content": f"Échec de validation des paramètres : {e}\nCorrigez le format."
})
continue
raise Exception(f"Échec d'appel d'outil après {max_retries} tentatives de validation")
# 3. Exemple d'utilisation
def get_weather(params: WeatherQueryParams) -> str:
"""Simulation de requête météo"""
# Logique d'appel API réelle ici
return f"{params.city} 未来 {params.date_offset} 天天气晴朗"
manager = ToolCallManager()
manager.register_tool("get_weather", get_weather, WeatherQueryParams)
result = manager.execute_with_retry(
"get_weather",
"帮我查一下北京明天的天气"
)
print(result) # 北京未来 1 天天气晴朗
Ces modèles couvrent les scénarios les plus courants. Combinez et adaptez selon vos besoins.
5. Bonnes pratiques en production
Le code est prêt, mais la production impose d’autres détails. Quelques pièges rencontrés et leurs solutions.
Temperature : ne montez pas trop
En sortie structurée, réglez Temperature entre 0,0 et 0,2 — plage recommandée par OpenAI, la plus stable en pratique.
Problème d’une température élevée ? Le LLM « diverge », la sortie devient aléatoire. L’aléatoire est l’ennemi de la structure — vous voulez de la déterminisme, pas de la créativité. À 0,7, j’avais 15 % d’erreurs de format ; à 0,1, le problème a quasi disparu.
Stratégie de retry : tout ne mérite pas un retry
Avant de retenter, classifiez l’erreur :
| Type d’erreur | Retry ? | Raison |
|---|---|---|
| Erreur de format (champ manquant, mauvais type) | Oui + retour d’erreur | Le LLM peut corriger |
| Erreur de service API (429, 500) | Oui + backoff | Problème temporaire serveur |
| Échec validation métier (ville hors liste) | Non, erreur directe | Confirmation utilisateur |
| Échec exécution outil (résultat vide) | Non, fallback | Problème de l’outil |
J’ai vu des retries illimités sur une ville hors liste blanche : dix tentatives, timeout. Classifiez les erreurs pour traiter efficacement.
Comparaison du surcoût de performance
| Solution | Latence ajoutée | Coût ajouté | Fiabilité |
|---|---|---|---|
| Contrainte par prompt (sans paramètre spécial) | +0 ms | +0 % | 5-10 % d’échec |
| JSON Mode (OpenAI uniquement) | +50 ms | +0 % | 2-5 % d’échec |
| Structured Outputs (Strict) | +100 ms | +0 % | <0,1 % d’échec |
| Retry Instructor | +200-500 ms/tentative | coût × retries | proche de 0 % |
| Outlines FSM | +1-2 s (première compilation) | +0 % | 100 % conforme |
Arbitrage : stabilité maximale → Structured Outputs ou Outlines ; prototype rapide → Instructor ; budget serré → JSON Mode + validation manuelle.
Métriques à surveiller : trois incontournables
Après mise en ligne, surveillez :
- Taux d’échec de format : part des requêtes en échec de validation. Au-delà de 1 %, investiguer.
- Nombre moyen de retries : normalement entre 0,5 et 1,5. Au-delà de 2, problème de modèle ou de Schema.
- Latence moyenne : +50-200 ms vs sortie classique, à garder acceptable.
Avec Prometheus + Grafana, je consulte un rapport hebdomadaire. Un jour, les retries sont passés de 0,8 à 2,5 — le Schema avait changé sans mise à jour du code. Le monitoring a évité pire.
Conclusion
Un seul message central : en 2026, la sortie structurée n’est plus un problème insoluble — avec la bonne méthode.
L’architecture à trois niveaux (validation + retry + décodage contraint) couvre le passage du « ça tourne » au « ça tourne de façon stable ». Pour les éditeurs : OpenAI Strict Mode est le plus stable, Claude exige une validation manuelle, modèles open source + Outlines offrent une fiabilité remarquable.
Les modèles de code sont au chapitre 4 — adaptez-les. Si vous débutez en développement Agent, commencez par Instructor : bonne encapsulation, retry et retour d’erreur intégrés ; passez à Outlines pour une conformité à 100 % une fois à l’aise.
Des questions ? Commentez ou contactez-moi. Contenu un peu dense — j’espère qu’il vous évitera quelques pièges.
Implémenter le flux complet OpenAI Structured Outputs
Étapes complètes de la définition du modèle Pydantic à l'appel de sortie structurée
⏱️ Estimated time: 15 min
- 1
Step 1: Définir le modèle de données Pydantic
Créez une classe de modèle Pydantic avec Field pour les contraintes :
• `Field(..., min_length=1, max_length=50)` pour la longueur des chaînes
• `Field(default=10, ge=1, le=100)` pour les plages numériques
• `@field_validator` pour la logique de validation personnalisée (ex. filtre par liste blanche)
• `Optional[T]` pour les champs optionnels - 2
Step 2: Convertir le modèle Pydantic en JSON Schema
Utilisez la méthode `model.model_json_schema()` :
```python
schema = SearchQuery.model_json_schema()
schema.pop("title", None) # Nettoyer les métadonnées Pydantic
```
Vérifiez que le Schema respecte les exigences OpenAI Structured Outputs. - 3
Step 3: Appeler l'API OpenAI avec Strict Mode activé
Définissez le paramètre `response_format` dans la requête API :
• `type: "json_schema"` — type de sortie structurée
• `strict: True` — mode de conformité forcée
• `json_schema.name` — nom du Schema (personnalisé)
• `json_schema.schema` — JSON Schema converti à l'étape précédente - 4
Step 4: Analyser la réponse et valider une seconde fois
Même si Strict Mode garantit une conformité à 100 %, une double validation reste recommandée :
• `json.loads()` pour analyser la chaîne de réponse
• `model.model_validate(data)` pour la validation Pydantic
• Capturer `ValidationError` et gérer les cas limites - 5
Step 5: Configurer le paramètre Temperature
Utilisez une température basse pour la sortie structurée :
```python
temperature=0.1 # Recommandé : 0.0-0.2
```
Évitez une température élevée qui augmente l'aléatoire et déstabilise le format.
FAQ
Que faire si le LLM renvoie un JSON mal formaté ?
• Niveau L1 — validation des paramètres : modèle Pydantic, conversion de types et validation des champs
• Niveau L2 — retry en cas d'échec : bibliothèque Instructor qui renvoie l'erreur au LLM pour auto-correction
• Niveau L3 — décodage contraint : Outlines ou guided_json de vLLM pour garantir la conformité à la source
Quelle est la différence entre la sortie structurée OpenAI et Claude ?
Comment choisir la bonne solution de sortie structurée ?
• **Appels API OpenAI** : Structured Outputs + Strict Mode (le plus stable)
• **Appels API Claude** : validation Pydantic + retry Instructor (validation manuelle requise)
• **Déploiement de modèles locaux** : Outlines ou vLLM guided_json (coût maîtrisé, haute fiabilité)
• **Prototype rapide** : bibliothèque Instructor (encapsulation prête à l'emploi)
• **Secteurs exigeants (finance, santé)** : OpenAI Strict ou Outlines (échec quasi nul)
Comment régler le paramètre Temperature ?
Quel surcoût de performance pour la sortie structurée ?
• **Contrainte par prompt** : +0 ms de latence, 5-10 % d'échecs
• **JSON Mode** : +50 ms, 2-5 % d'échecs
• **Structured Outputs** : +100 ms, <0,1 % d'échecs
• **Retry Instructor** : +200-500 ms par retry, taux d'échec proche de 0 %
• **Outlines FSM** : +1-2 s à la première compilation, 100 % de conformité
Arbitrez selon la fiabilité et le budget.
Quelles erreurs faut-il retenter ? Lesquelles non ?
**À retenter** :
• Erreur de format des paramètres (champ manquant, mauvais type) — le LLM peut corriger
• Erreur de service API (429, 500) — problème temporaire côté serveur
**À ne pas retenter** :
• Échec de validation métier (ville hors liste blanche) — confirmation utilisateur requise
• Échec d'exécution de l'outil (résultat vide) — problème de l'outil, passer en fallback
Les retries illimités provoquent des timeouts ; seule une classification fine des erreurs est efficace.
15 min de lecture · Publié le: 6 mai 2026 · Mis à jour le: 27 juil. 2026
Guide d'ingénierie AI Agent
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
LangGraph vs AutoGen : comparaison du suivi d'état — Checkpoint, reprise après timeout et choix de framework
Comparaison approfondie LangGraph vs AutoGen sur le suivi d'état : 12 dimensions (Checkpoint, reprise après timeout, support distribué), cas réels, arbre de décision et code exécutable pour choisir le bon framework.
Partie 11 sur 16
Suivant
Benchmarks d'évaluation d'agents : d'AgentBench à DeepEval, guide pratique des tests de performance
Benchmarks d'évaluation d'agents et frameworks de test de performance : comparaison de cinq références majeures (AgentBench, WebArena, τ-Bench…), méthode d'évaluation par composants avec DeepEval, et exemples de code complets.
Partie 13 sur 16



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire