?
Skillintermédiaire

Latency management : UX fluide malgré les LLMs

Techniques pour masquer la latence : streaming, optimistic UI, progressive disclosure.

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éponse

Technique 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

Sources

productlatenceUX