Performance en production
Mis à jour le 29 juillet 2026
Au-delà du cache : la performance globale
Le cache réduit la facture, mais une intégration en production se juge sur davantage de critères : la latence ressentie par l’utilisateur, le débit soutenu, la fiabilité face aux erreurs transitoires et la capacité à diagnostiquer une dégradation. Une application qui coûte peu mais répond en douze secondes ou échoue une fois sur cinquante n’est pas une application performante. Cette leçon couvre les quatre dimensions qui restent une fois le coût maîtrisé.
Le streaming, ou comment gagner du temps sans en gagner
Pour toute interface où un humain attend une réponse, le streaming SSE (Server-Sent Events) change radicalement la perception. Sans lui, l’utilisateur fixe un curseur pendant que le modèle génère 500 tokens ; avec lui, les premiers mots apparaissent au bout de quelques centaines de millisecondes et la lecture commence pendant la génération. Le temps total est le même, le temps ressenti est divisé par plusieurs.
import requests
import json
def appel_streaming(messages, model="grok-4.20"):
reponse = requests.post(
"https://api.x.ai/v1/chat/completions",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
},
json={
"model": model,
"messages": messages,
"stream": True
},
stream=True
)
for ligne in reponse.iter_lines():
if ligne and ligne.startswith(b"data: "):
donnees = ligne[6:]
if donnees == b"[DONE]":
break
chunk = json.loads(donnees)
token = chunk["choices"][0]["delta"].get("content", "")
yield token
Le paramètre stream: True ne modifie ni le nombre de tokens facturés ni leur tarif. Ce que vous améliorez, c’est le TTFT — le time to first token. Notez que la fonction est un générateur : elle rend la main token par token, ce qui vous permet d’écrire directement dans la réponse HTTP de votre serveur sans garder toute la génération en mémoire.
Choisir son modèle pour la latence
Le second levier de latence est le choix du modèle, et il se joue surtout sur l’effort de raisonnement. Un modèle qui raisonne produit d’abord des tokens de réflexion invisibles avant de rédiger sa réponse : la qualité monte, le délai aussi. Aux extrêmes, grok-build-0.1 est le plus rapide du catalogue, ce qui le destine aux usages temps réel comme la classification à la volée ou l’autocomplétion ; grok-4.20-0309 en variante non-reasoning et grok-4.3 offrent un compromis raisonnable entre qualité et vitesse ; grok-4.5 avec un raisonnement poussé occupe l’autre bord, à réserver aux tâches qui l’exigent vraiment.
La conséquence pratique est qu’on ne choisit pas un modèle pour une application, mais pour une étape. Un schéma répandu consiste à faire trancher un premier appel rapide — cette demande est-elle triviale, ambiguë, ou complexe ? — puis à n’envoyer vers un modèle raisonnant que la fraction des cas qui le justifient. Vous divisez à la fois la latence moyenne et le coût moyen, sans dégrader les cas difficiles.
Gérer les erreurs sans aggraver la situation
Le code 429 traité à la leçon 3 n’est pas le seul que vous rencontrerez. La distinction structurante est celle entre les erreurs qui méritent un nouvel essai et celles où réessayer ne fera qu’ajouter du trafic inutile.
| Code | Nature | Conduite |
|---|---|---|
| 429 Too Many Requests | récupérable | backoff exponentiel (leçon 3) |
| 500 Internal Server Error | récupérable | retry avec backoff, l’erreur est côté serveur |
| 502 / 503 Service Unavailable | récupérable | retry après quelques secondes |
| 400 Bad Request | définitive | requête malformée, corrigez le payload |
| 401 Unauthorized | définitive | clé API invalide ou expirée |
| 413 Payload Too Large | définitive | le prompt dépasse la limite de contexte |
Cette règle se code en trois lignes et vaut d’être isolée dans une fonction plutôt que dispersée en if un peu partout :
def classifier_erreur(status_code):
recuperable = {429, 500, 502, 503}
return status_code in recuperable
Timeouts et résilience
Un timeout mal réglé produit deux pathologies opposées : trop court, il abandonne des requêtes qui allaient aboutir ; trop long, il immobilise vos workers pendant que la file s’allonge. Réglez-en trois séparément. La connexion doit s’établir en 10 secondes ; au-delà, le problème est réseau. La lecture en mode non-streaming se cale sur le modèle : 120 secondes pour un modèle raisonnant, 60 secondes pour grok-build-0.1. En streaming, le seuil pertinent n’est pas la durée totale mais l’intervalle entre deux chunks, à fixer autour de 30 secondes.
Un point mérite une vraie prudence : lorsqu’un timeout expire sur une génération longue, ne relancez pas automatiquement. Le modèle a peut-être déjà consommé des tokens de raisonnement que vous paierez une seconde fois, et rien ne garantit que la première requête n’a pas abouti côté serveur. Un mécanisme d’idempotence — une clé de requête que vous conservez pour reconnaître un doublon — ou une reprise explicite vaut mieux qu’un retry aveugle.
Ce qu’il faut instrumenter
Quatre familles de métriques suffisent à diagnostiquer l’essentiel des incidents.
| Métrique | Ce qu’elle révèle | Cible |
|---|---|---|
| TTFT | latence initiale ressentie | à surveiller par modèle |
| Taux de succès | fiabilité de l’intégration | supérieur à 99,5 % |
| Taux de cache | qualité de la structure des prompts | supérieur à 50 % |
| Coût par requête | dérive budgétaire, en moyenne glissante | stable dans le temps |
Ajoutez-y la latence au 95e percentile. La moyenne masque toujours les cas extrêmes, et ce sont eux qui déclenchent les tickets de support : une P95 à huit secondes signifie qu’un utilisateur sur vingt attend huit secondes, ce qu’aucune moyenne à 1,2 seconde ne laissera deviner.
Points clés à retenir
- Le streaming SSE réduit la latence perçue sans affecter le coût
- Choisissez un modèle rapide pour le temps réel et un modèle raisonnant pour la qualité
- Distinguez les erreurs récupérables (429, 500, 502/503) des erreurs définitives (400, 401)
- Configurez des timeouts adaptés au modèle utilisé
- Surveillez le TTFT, le taux de succès, le taux de cache et le coût moyen par requête