Bonnes pratiques et récapitulatif
Récapitulatif du cours
Vous avez parcouru l’ensemble des mécanismes de sorties structurées proposés par l’API Mistral. Cette dernière leçon synthétise les bonnes pratiques à appliquer au quotidien, les pièges à éviter, et les prochaines étapes pour aller plus loin.
Arbre de décision : quelle approche choisir ?
Posez-vous ces questions dans l’ordre :
-
Avez-vous un schéma de sortie défini ?
- Non → JSON Mode (exploration, prototypage)
- Oui → question suivante
-
Travaillez-vous en Python ou TypeScript ?
- Oui → Custom Structured Outputs (Pydantic / Zod)
- Non → JSON Mode avec validation manuelle
-
Êtes-vous en production ?
- Oui → Custom Structured Outputs (toujours)
- Non → JSON Mode suffit pour le prototypage
-
Le schéma change-t-il fréquemment ?
- Oui → JSON Mode peut être plus pratique au début
- Non → Custom Structured Outputs pour la fiabilité
Les 10 règles d’or
1. Toujours définir temperature=0 en production
# Production : déterministe
response = client.chat.parse(
model="mistral-large-latest",
messages=messages,
response_format=MonSchema,
temperature=0 # Reproductibilité
)
La variabilité de la température est un ennemi de la fiabilité en production.
2. Toujours vérifier finish_reason
if response.choices[0].finish_reason != "stop":
# La réponse est tronquée — augmentez max_tokens
raise ValueError(f"Réponse incomplète : {response.choices[0].finish_reason}")
3. Toujours instruire le modèle dans le prompt (JSON Mode)
# JSON Mode — le prompt DOIT décrire la structure
messages = [
{
"role": "system",
"content": "Retourne un JSON avec les clés : name (string), age (number), city (string)."
},
{"role": "user", "content": texte}
]
4. Utiliser des descriptions de champs (Custom)
class Product(BaseModel):
name: str = Field(description="Nom commercial du produit")
price: float = Field(description="Prix en euros HT, arrondi à 2 décimales")
const ProductSchema = z.object({
name: z.string().describe("Nom commercial du produit"),
price: z.number().describe("Prix en euros HT, arrondi à 2 décimales"),
});
5. Séparer le contexte (system) du contenu (user)
messages = [
{"role": "system", "content": "Tu es un extracteur de données. [Instructions de format]"},
{"role": "user", "content": "[Le texte à traiter — PAS d'instructions de format ici]"}
]
6. Implémenter un mécanisme de retry
Ne faites jamais un seul appel sans filet. Le retry avec feedback est le pattern minimal en production.
7. Valider au-delà du schéma
Le schéma garantit la structure. La validation sémantique (bornes, cohérence, logique métier) est votre responsabilité.
8. Choisir le bon modèle pour le bon usage
- mistral-large-latest : tâches complexes, extraction multi-champs
- mistral-small-latest : classification simple, extraction basique
- ministral-8b-latest : haute fréquence, latence minimale
9. Logger chaque appel en production
Enregistrez : le modèle utilisé, la durée, le finish_reason, le succès ou l’erreur. Sans logs, vous êtes aveugle.
10. Ne pas sur-optimiser le prompt trop tôt
Commencez simple. Mesurez. Itérez. L’optimisation prématurée du prompt est un piège courant — concentrez-vous d’abord sur le schéma et la validation.
Pièges courants à éviter
Le piège du max_tokens trop bas
# Dangereux — le JSON sera tronqué pour les réponses longues
response = client.chat.parse(
model="mistral-large-latest",
messages=messages,
response_format=MonSchema,
max_tokens=128 # Trop bas pour un schéma complexe
)
Réglez max_tokens en fonction de la taille attendue de votre sortie. Une facture avec 20 lignes nécessite plus de tokens qu’une classification simple.
Le piège de la température non-nulle
# Dangereux en production — résultats non reproductibles
response = client.chat.parse(
...,
temperature=0.7 # Variation inutile pour de l'extraction
)
Le piège du modèle unique
N’utilisez pas mistral-large pour tout. Une classification binaire ne nécessite pas le modèle le plus puissant — ministral-8b fera le travail 5x plus vite pour moins cher.
Monitoring en production
Métriques à suivre
- Taux de succès : pourcentage d’appels qui retournent un objet valide au premier essai
- Taux de retry : pourcentage d’appels nécessitant un retry
- Latence P50/P95 : temps de réponse médian et au 95e percentile
- Coût par extraction : tokens consommés par appel
Alertes recommandées
- Taux de succès < 95% → alerte jaune
- Taux de succès < 85% → alerte rouge
- Latence P95 > 10s → investiguer
- Circuit breaker ouvert → alerte immédiate
Prochaines étapes
Maintenant que vous maîtrisez les sorties structurées, voici les directions pour approfondir :
- Function calling : utiliser les sorties structurées pour alimenter des appels de fonctions dans un agent
- Streaming : recevoir la sortie structurée progressivement pour les réponses longues
- Fine-tuning : ajuster un modèle Mistral pour améliorer la qualité de l’extraction sur vos données spécifiques
- Évaluation : mettre en place un framework d’évaluation automatique (golden dataset + métriques)
Exemple final : pipeline complet
Voici un pipeline complet qui combine toutes les bonnes pratiques :
from mistralai import Mistral
from pydantic import BaseModel, Field, field_validator
import logging
import time
logger = logging.getLogger("pipeline")
class CustomerFeedback(BaseModel):
sentiment: str = Field(description="positif, négatif ou neutre")
score: float = Field(description="Score entre -1.0 et 1.0")
category: str = Field(description="produit, service, livraison, prix ou autre")
action_required: bool = Field(description="True si une action est nécessaire")
summary: str = Field(description="Résumé en une phrase")
@field_validator("score")
@classmethod
def score_in_range(cls, v):
if not -1.0 <= v <= 1.0:
raise ValueError(f"Score hors bornes : {v}")
return v
def analyze_feedback(client: Mistral, text: str) -> CustomerFeedback:
start = time.time()
try:
response = client.chat.parse(
model="mistral-small-latest",
messages=[
{"role": "system", "content": "Analyse le feedback client."},
{"role": "user", "content": text}
],
response_format=CustomerFeedback,
temperature=0,
max_tokens=256
)
assert response.choices[0].finish_reason == "stop"
result = response.choices[0].message.parsed
logger.info(f"OK en {time.time()-start:.2f}s — {result.sentiment} ({result.score:+.1f})")
return result
except Exception as e:
logger.error(f"ERREUR en {time.time()-start:.2f}s — {e}")
raise
Points clés à retenir
- Utilisez Custom Structured Outputs en production, JSON Mode pour le prototypage
temperature=0, vérification definish_reason, et retry sont non négociables- Choisissez le modèle en fonction de la complexité de la tâche et du budget
- Monitorez le taux de succès, la latence et le coût
- Commencez simple, mesurez, puis optimisez