Anti-patterns de l'API
Les erreurs qui coûtent cher en production
Après avoir vu les bonnes pratiques, examinons les anti-patterns les plus fréquents dans l’utilisation de l’API Chat Completions. Ces erreurs sont souvent invisibles en développement mais explosent en production : coûts imprévus, réponses incohérentes, latences élevées, ou pire — des données sensibles qui fuient.
Anti-pattern 1 : ignorer finish_reason
L’erreur la plus courante. Si vous ne vérifiez pas finish_reason, vous risquez de traiter des réponses tronquées comme si elles étaient complètes :
# MAUVAIS — pas de vérification
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": "Rédigez un contrat complet."}],
max_tokens=100 # Trop peu pour un contrat !
)
contrat = response.choices[0].message.content # Potentiellement tronqué !
# BON — vérification systématique
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": "Rédigez un contrat complet."}],
max_tokens=4000
)
if response.choices[0].finish_reason == "length":
print("ATTENTION : réponse tronquée, augmentez max_tokens")
else:
contrat = response.choices[0].message.content
Anti-pattern 2 : historique de conversation illimité
Renvoyer toute la conversation sans limite consomme de plus en plus de tokens et finit par dépasser la fenêtre de contexte :
# MAUVAIS — l'historique grandit indéfiniment
conversation = []
while True:
user_input = input("Vous : ")
conversation.append({"role": "user", "content": user_input})
response = client.chat.complete(
model="mistral-large-latest",
messages=conversation # 100 tours = des milliers de tokens !
)
reply = response.choices[0].message.content
conversation.append({"role": "assistant", "content": reply})
# BON — fenêtre glissante avec résumé
MAX_MESSAGES = 20
def trim_conversation(messages: list, max_messages: int = MAX_MESSAGES) -> list:
"""Garde le system prompt + les N derniers messages."""
system = [m for m in messages if m["role"] == "system"]
history = [m for m in messages if m["role"] != "system"]
if len(history) > max_messages:
history = history[-max_messages:]
return system + history
Anti-pattern 3 : ne pas gérer les erreurs réseau
# MAUVAIS — aucune gestion d'erreur
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": "Bonjour"}]
)
# Crash si timeout, 429, 500...
# BON — retry avec backoff
from mistralai.exceptions import MistralAPIException
import time
def safe_complete(client, messages, max_retries=3, **kwargs):
"""Appel API avec retry et backoff exponentiel."""
for attempt in range(max_retries):
try:
return client.chat.complete(messages=messages, **kwargs)
except MistralAPIException as e:
if e.status_code == 429:
wait = 2 ** attempt
print(f"Rate limit atteint, attente {wait}s...")
time.sleep(wait)
elif e.status_code >= 500:
time.sleep(1)
else:
raise # Erreur client (400, 401) — ne pas retenter
raise Exception("Échec après 3 tentatives")
Anti-pattern 4 : le langage subjectif dans les prompts
Les modèles interprètent mal les termes vagues :
# MAUVAIS — subjectif, non mesurable
"Écrivez un résumé court." # Court = 1 phrase ? 1 paragraphe ?
"Donnez beaucoup d'exemples." # Beaucoup = 3 ? 10 ? 50 ?
"Répondez de manière intéressante." # Intéressant selon quels critères ?
# BON — critères objectifs
"Écrivez un résumé de 50 à 80 mots."
"Donnez exactement 5 exemples, un par ligne."
"Commencez chaque réponse par une statistique chiffrée liée au sujet."
Anti-pattern 5 : laisser le modèle compter
Les LLMs sont notoirement mauvais en comptage de mots, caractères et éléments :
# MAUVAIS — le modèle ne sait pas compter précisément
"Écrivez exactement 280 caractères pour un tweet."
# BON — compter côté application
response = client.chat.complete(
model="mistral-large-latest",
messages=[{"role": "user", "content": "Rédigez un tweet sur l'IA en France."}],
max_tokens=100
)
tweet = response.choices[0].message.content
if len(tweet) > 280:
tweet = tweet[:277] + "..."
print(f"Tweet ({len(tweet)} chars) : {tweet}")
Anti-pattern 6 : pas de logging en production
# MAUVAIS — aucune trace
response = client.chat.complete(
model="mistral-large-latest",
messages=messages
)
return response.choices[0].message.content
# BON — logging structuré
import logging
import json
logger = logging.getLogger("mistral_api")
def logged_complete(client, messages, **kwargs):
"""Appel API avec logging structuré."""
response = client.chat.complete(messages=messages, **kwargs)
logger.info(json.dumps({
"request_id": response.id,
"model": response.model,
"prompt_tokens": response.usage.prompt_tokens,
"completion_tokens": response.usage.completion_tokens,
"finish_reason": response.choices[0].finish_reason
}))
return response
Anti-pattern 7 : échelles numériques dans les prompts
# MAUVAIS — les modèles interprètent mal les échelles numériques
"Évaluez la qualité de ce texte de 1 à 5."
# BON — échelle verbale avec définitions
"""Évaluez la qualité de ce texte selon l'échelle suivante :
- EXCELLENT : clair, bien structuré, sans erreur
- BON : globalement correct, améliorations mineures possibles
- MOYEN : compréhensible mais manque de structure ou contient des erreurs
- FAIBLE : difficile à comprendre, erreurs significatives
- TRÈS FAIBLE : incompréhensible ou hors sujet
Répondez uniquement par le label en majuscules."""
Checklist de debugging
Quand une réponse ne correspond pas à vos attentes, vérifiez dans l’ordre :
def debug_response(response):
"""Checklist de debugging pour les réponses inattendues."""
choice = response.choices[0]
# 1. La réponse est-elle complète ?
if choice.finish_reason == "length":
print("PROBLÈME : Réponse tronquée — augmentez max_tokens")
# 2. Le bon modèle a-t-il été utilisé ?
print(f"Modèle : {response.model}")
# 3. Combien de tokens consommés ?
print(f"Tokens entrée : {response.usage.prompt_tokens}")
print(f"Tokens sortie : {response.usage.completion_tokens}")
# 4. La réponse est-elle vide ?
if not choice.message.content or not choice.message.content.strip():
print("PROBLÈME : Réponse vide")
# 5. Ratio entrée/sortie suspect ?
ratio = response.usage.completion_tokens / max(response.usage.prompt_tokens, 1)
if ratio < 0.01:
print(f"ATTENTION : Ratio sortie/entrée très bas ({ratio:.4f})")
Points clés à retenir
- Vérifiez toujours
finish_reasonpour détecter les troncatures - Limitez l’historique de conversation avec une fenêtre glissante
- Implémentez un retry avec backoff exponentiel pour les erreurs réseau
- Utilisez des critères objectifs et mesurables dans vos prompts
- Ne laissez jamais le modèle compter — validez côté application
- Loguez chaque appel API en production pour le debugging et le suivi des coûts