Mesurer et analyser la latence
Mis à jour le 28 juillet 2026
Pourquoi la latence est votre métrique numéro un
Lorsque vous déployez une application alimentée par un LLM en production, la latence perçue par vos utilisateurs détermine directement l’adoption. Une réponse qui met 8 secondes à apparaître fait fuir 70 % des utilisateurs : ils ferment l’onglet, relancent la requête, ou concluent que l’outil ne marche pas. Aucune amélioration de qualité ne compense cette perte, parce qu’elle se produit avant même que l’utilisateur ait lu la réponse.
La difficulté, quand on arrive en production, est que « c’est lent » n’est pas un diagnostic. Une même durée de huit secondes peut venir d’un prompt système démesuré, d’un réglage de raisonnement trop ambitieux, d’un serveur applicatif mal placé, ou d’un post-traitement qui attend la réponse complète avant d’afficher quoi que ce soit. Chacune de ces causes appelle une correction différente, et corriger la mauvaise ne change rien. Cette leçon vous apprend donc à mesurer, décomposer et diagnostiquer la latence de bout en bout, pour transformer une plainte en un chiffre actionnable.
Les composantes de la latence
La latence totale d’un appel API se décompose en quatre segments qu’il faut savoir séparer. La latence réseau correspond au temps de transit entre votre serveur et l’API OpenAI ; elle est invisible dans vos logs applicatifs mais peut représenter plus de cent millisecondes si votre serveur est loin de l’endpoint. Le Time to First Token (TTFT) mesure le délai avant que le premier token ne soit généré : c’est le segment que l’utilisateur ressent comme « l’attente », puisque tant qu’il n’est pas écoulé, l’écran reste vide. Le Time Per Output Token (TPOT) décrit ensuite la vitesse de génération de chaque token suivant ; multiplié par la longueur de la réponse, il donne la durée de défilement du texte. Enfin la latence applicative couvre tout ce que votre propre serveur fait avant et après l’appel : récupération de documents, assemblage du prompt, validation du JSON de sortie, écriture en base.
Cette décomposition n’est pas académique. Une équipe qui découvre que son TTFT est bon mais que sa latence applicative pèse deux secondes n’ira pas changer de modèle : elle ira regarder sa requête SQL de récupération de contexte.
Mesurer avec Python
Voici comment instrumenter un appel à la Responses API pour capturer chaque segment :
import time
import openai
client = openai.OpenAI()
def mesurer_latence(prompt: str, model: str = "gpt-5.6-terra") -> dict:
"""Mesure la latence décomposée d'un appel API."""
metriques = {}
debut_total = time.perf_counter()
# Appel en streaming pour mesurer le TTFT
stream = client.responses.create(
model=model,
input=prompt,
stream=True,
)
premier_token = True
tokens_sortie = 0
for event in stream:
if event.type == "response.output_text.delta":
if premier_token:
metriques["ttft"] = time.perf_counter() - debut_total
debut_generation = time.perf_counter()
premier_token = False
tokens_sortie += 1
metriques["duree_totale"] = time.perf_counter() - debut_total
metriques["tokens_sortie"] = tokens_sortie
if tokens_sortie > 1:
metriques["tpot"] = (
(metriques["duree_totale"] - metriques["ttft"]) / (tokens_sortie - 1)
)
return metriques
resultat = mesurer_latence("Expliquez le théorème de Bayes en 3 phrases.")
for cle, valeur in resultat.items():
print(f"{cle}: {valeur:.4f}" if isinstance(valeur, float) else f"{cle}: {valeur}")
Le point à retenir de ce code est le passage en stream=True. Sans streaming, vous ne pouvez pas isoler le TTFT : l’API vous rend la main une fois la réponse complète, et vous ne mesurez qu’un total. Le chronomètre démarre avant l’appel, se fige au premier delta de texte, et la durée résiduelle divisée par le nombre de tokens restants donne le TPOT. Passez ce même prompt sur deux modèles ou deux réglages de raisonnement et vous obtenez, en quelques minutes, la comparaison chiffrée qui remplacera les intuitions de votre équipe.
Diagnostiquer les goulots d’étranglement
Un TTFT élevé, au-delà de deux secondes, oriente vers trois causes. La plus fréquente est un prompt système trop long qui n’est pas mis en cache : le modèle doit ingérer plusieurs milliers de tokens avant de commencer à écrire, à chaque appel. Vient ensuite un réglage de raisonnement élevé — high, xhigh ou max sur GPT-5.6 — qui fait délibérément réfléchir le modèle avant qu’il ne produise le moindre mot visible ; ce n’est pas un défaut, c’est le comportement attendu, mais il doit être un choix conscient et non un réglage hérité d’un prototype. Reste enfin la possibilité d’un pic de charge côté OpenAI, que vous ne contrôlez pas : c’est précisément pour la distinguer des deux autres qu’il faut mesurer en continu plutôt qu’une fois.
Un TPOT élevé, au-delà de 50 ms par token, se lit différemment. Un max_output_tokens très élevé pousse le modèle à produire des séquences longues, et la durée totale s’allonge mécaniquement même si chaque token arrive à vitesse normale. L’usage de function calling complexe, avec de nombreux outils déclarés, pèse également sur la génération. Concrètement, une équipe qui a laissé quinze outils dans son schéma alors que la moitié ne sert plus paiera ce surplus sur chaque appel.
Outils de monitoring
Mesurer une fois ne suffit pas : il faut suivre la distribution dans le temps, parce que c’est la queue de distribution qui produit les tickets de support. Pour un suivi continu en production, intégrez ces métriques dans votre stack d’observabilité :
import logging
from dataclasses import dataclass, field
@dataclass
class MetriquesLatence:
"""Agrège les métriques de latence sur une fenêtre glissante."""
mesures: list[dict] = field(default_factory=list)
def ajouter(self, mesure: dict) -> None:
self.mesures.append(mesure)
if len(self.mesures) > 1000:
self.mesures = self.mesures[-1000:]
@property
def ttft_p50(self) -> float:
valeurs = sorted(m["ttft"] for m in self.mesures if "ttft" in m)
if not valeurs:
return 0.0
return valeurs[len(valeurs) // 2]
@property
def ttft_p99(self) -> float:
valeurs = sorted(m["ttft"] for m in self.mesures if "ttft" in m)
if not valeurs:
return 0.0
idx = int(len(valeurs) * 0.99)
return valeurs[min(idx, len(valeurs) - 1)]
def rapport(self) -> str:
return (
f"TTFT p50={self.ttft_p50:.3f}s p99={self.ttft_p99:.3f}s | "
f"Échantillons: {len(self.mesures)}"
)
La fenêtre glissante de mille mesures évite de faire grossir la mémoire indéfiniment tout en gardant un échantillon représentatif du trafic récent. Surtout, cette classe expose deux percentiles et pas une moyenne : un p50 à 700 ms avec un p99 à 6 secondes décrit un service que la plupart des utilisateurs trouvent rapide et qu’une minorité trouve inutilisable. La moyenne, elle, aurait masqué les deux réalités. Branchez rapport() sur votre outil de métriques et fixez un seuil d’alerte sur le p99 avant de toucher au reste du système.
Points clés à retenir
- La latence se décompose en TTFT + génération (TPOT x tokens) + réseau
- Mesurez toujours en streaming pour obtenir le TTFT réel
- Suivez les percentiles p50 et p99, pas seulement la moyenne
- Le choix du modèle et du réglage de raisonnement impacte directement la latence : GPT-5.6 Terra en raisonnement faible répond plus vite que GPT-5.6 Sol en raisonnement élevé