Le défi de la latence LLM
Les LLMs ont des temps de réponse de 1 à 30+ secondes selon la complexité. Pour l'utilisateur, tout délai au-delà de 2 secondes sans feedback est perçu comme un bug. Voici les techniques pour maintenir une UX fluide.
Budget latence typique
- Time to First Token (TTFT) : 200ms - 2s
- Génération complète : 2s - 30s
- Pipeline RAG complet : 3s - 15s
- Agent multi-étapes : 10s - 60s
Technique 1 : Streaming obligatoire
Toujours streamer la réponse, jamais attendre la fin :
// API Route (Next.js)
export async function POST(req: Request) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [...],
stream: true
});
for await (const chunk of response) {
const text = chunk.choices[0]?.delta?.content || '';
controller.enqueue(encoder.encode(text));
}
controller.close();
}
});
return new Response(stream);
}Technique 2 : Optimistic UI
Montrer immédiatement un résultat provisoire :
- Afficher un squelette de réponse (skeleton)
- Pour les tâches connues, montrer un résultat pré-calculé puis le remplacer
- Pour les actions (envoi d'email, création), confirmer immédiatement puis traiter en arrière-plan
Technique 3 : Progressive disclosure pendant l'attente
Utiliser le temps d'attente de manière productive :
Étape 1 (0-1s) : "Analyse de votre question..."
Étape 2 (1-3s) : "Recherche dans 3 documents pertinents..."
Étape 3 (3-5s) : "Synthèse des informations..."
Étape 4 (5s+) : Début du streaming de la réponseTechnique 4 : Pre-computation et cache
# Cache sémantique : requêtes similaires = même réponse
from langchain.cache import InMemoryCache
import langchain
langchain.llm_cache = InMemoryCache()
# Pre-computation des réponses fréquentes
FREQUENT_QUERIES = load_frequent_queries()
for query in FREQUENT_QUERIES:
cache.set(query, generate_response(query))Technique 5 : Parallélisation
import asyncio
async def parallel_rag(question: str):
# Lancer embedding + BM25 en parallèle
embedding_task = asyncio.create_task(embed_query(question))
bm25_task = asyncio.create_task(bm25_search(question))
embedding_results, bm25_results = await asyncio.gather(
embedding_task, bm25_task
)
# Fusionner et reranker
return merge_results(embedding_results, bm25_results)Technique 6 : Modèle routing par latence
- Requêtes simples : Modèle petit et rapide (TTFT < 200ms)
- Requêtes complexes : Grand modèle avec streaming
- Compromis explicite qualité/vitesse selon le contexte
Métriques UX à monitorer
- Perceived latency : Temps avant le premier contenu visible
- Abandon rate : Utilisateurs qui quittent pendant le chargement
- Interaction to response : Temps entre le clic et le feedback
- Seuils : < 1s = instantané, 1-3s = acceptable, > 5s = frustrant