Attendre 8 secondes qu'un LLM finisse avant d'afficher quoi que ce soit, c'est une UX morte. Le streaming token par token change tout. Et côté serveur, pas besoin de WebSocket : Server-Sent Events suffit, en PHP natif.

Le principe

SSE est un flux HTTP texte avec un Content-Type: text/event-stream. Le serveur écrit des lignes data: ...\n\n et le navigateur les reçoit via EventSource. Unidirectionnel serveur→client, exactement ce qu'il faut pour du token streaming.

Le code serveur

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no'); // désactive le buffer nginx

while (ob_get_level() > 0) { ob_end_flush(); }

foreach (streamLlm($prompt) as $chunk) {
    echo 'data: ' . json_encode(['t' => $chunk]) . "\n\n";
    flush();
}
echo "event: done\ndata: {}\n\n";
flush();

Côté client

const es = new EventSource('/stream?q=' + encodeURIComponent(q));
es.onmessage = (e) => { output.textContent += JSON.parse(e.data).t; };
es.addEventListener('done', () => es.close());

Les trois pièges

  • Buffering nginx/Apache : le header X-Accel-Buffering: no (nginx) et désactiver gzip sur cette route, sinon tout arrive d'un bloc.
  • Buffer PHP : vider tous les niveaux d'ob_* et flush() après chaque chunk.
  • Timeout FPM : un stream long tient un worker FPM. Limiter la durée, prévoir un max_execution_time adapté, et idéalement isoler ces requêtes d'un pool FPM dédié.

Pas de dépendance, ~15 lignes serveur. Pour un chat IA, c'est le strict nécessaire et ça suffit en production tant que la concurrence reste raisonnable.

FAQ

Pourquoi SSE plutôt que WebSocket pour du streaming LLM ?

Le flux est unidirectionnel serveur→client ; SSE (simple HTTP) suffit sans la complexité d'un serveur WebSocket dédié.

Faut-il une extension PHP particulière ?

Non, uniquement des headers HTTP et flush() — aucune dépendance.

SSE fonctionne-t-il derrière nginx sans configuration ?

Non, il faut désactiver le buffering (X-Accel-Buffering: no) et le gzip sur cette route, sinon tout arrive d'un bloc.