Tema 3 · Streaming SSE

3. Streaming de Respuestas en Tiempo Real con SSE (Server-Sent Events)

Construye la experiencia de usuario fluida tipo ChatGPT o Claude: transmisión continua de chunks con Server-Sent Events en PHP y JavaScript.

¿Por qué el Streaming es Obligatorio en Aplicaciones con IA?

La inferencia de un modelo de lenguaje requiere tiempo: generar un párrafo de 300 palabras puede tardar entre 4 y 12 segundos. Si esperas a que la respuesta esté 100% completada antes de enviar el JSON al navegador, el usuario experimentará una sensación de congelamiento y lentitud frustrante.

Con Streaming en Tiempo Real, el servidor web envía cada palabra o fragmento (chunk) al navegador tan pronto como el modelo lo genera. La métrica Time To First Token (TTFT) se reduce de varios segundos a menos de 300 milisegundos, ofreciendo el efecto máquina de escribir que los usuarios esperan.

Server-Sent Events (SSE) vs WebSockets

Aunque WebSockets permite comunicación bidireccional continua, para asistentes de IA y consumo de LLMs la tecnología estándar de la industria es Server-Sent Events (SSE):

Característica Server-Sent Events (SSE) ⭐ Recomendado WebSockets
Dirección del Flujo Unidireccional (Servidor ➔ Cliente tras petición HTTP estándar). Bidireccional completa (Cliente ⇄ Servidor).
Protocolo & Proxies HTTP/1.1 o HTTP/2 estándar (compatible con Nginx, Cloudflare, firewalls). Requiere handshake de mejora de protocolo (Upgrade: websocket).
Formato del Stream Texto plano con prefijo data: {JSON}\n\n y evento de cierre data: [DONE]. Tramas binarias o de texto arbitrarias.
Complejidad Backend Mínima: no requiere servidores persistentes en Node/Go; funciona en PHP tradicional. Alta: requiere daemon WebSocket en ejecución continua y socket rooms.
⚠️ Importante para Nginx y Apache: Para que el streaming no se acumule en memoria intermedia (buffer), debes enviar la cabecera X-Accel-Buffering: no y llamar a ob_flush() y flush() tras cada paquete.

⚡ Consumo de Stream SSE en el Frontend con JavaScript Moderno

Código JavaScript con Fetch API, ReadableStream y TextDecoder que lee los fragmentos continuos y los renderiza en tiempo real.

sse-streaming-client.js
Solo lectura
/**
 * sse-streaming-client.js - Consumo en vivo con Fetch y ReadableStream
 */
async function streamAssistantReply(userPrompt, outputElement) {
  outputElement.textContent = "";

  try {
    const response = await fetch("/api/chat-stream.php", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ message: userPrompt })
    });

    if (!response.ok) {
      throw new Error(`HTTP Error ${response.status}: Fallo al conectar con el asistente`);
    }

    // Obtenemos el lector de flujos binarios del cuerpo HTTP
    const reader = response.body.getReader();
    const decoder = new TextDecoder("utf-8");
    let buffer = "";

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      // Decodificamos el bloque binario a texto legible
      buffer += decoder.decode(value, { stream: true });

      // Dividimos las líneas según la especificación SSE (\n\n)
      const lines = buffer.split("\n\n");
      buffer = lines.pop(); // Guardamos el fragmento incompleto

      for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed.startsWith("data: ")) continue;

        const payload = trimmed.replace("data: ", "");
        if (payload === "[DONE]") {
          console.log("Stream completado exitosamente.");
          return;
        }

        try {
          const parsed = JSON.parse(payload);
          const chunkText = parsed.choices?.[0]?.delta?.content || "";
          outputElement.textContent += chunkText;
        } catch (e) {
          console.warn("Línea SSE no interpretable:", payload);
        }
      }
    }
  } catch (err) {
    outputElement.textContent = `Error: ${err.message}`;
  }
}
🧠
Mini-Quiz de Retención
Comprueba tu comprensión antes de pasar a la siguiente lección
1 Pregunta
Pregunta 1
¿Cuál es la principal ventaja de utilizar Server-Sent Events (SSE) frente a peticiones AJAX tradicionales al integrar un LLM en la web?
🦉
¿Tienes dudas con este tema?
Sócrates AI puede darte analogías y explicarte este código paso a paso.

🚀 ¿Quieres dominar IA Aplicada a Desarrollo Web: APIs, RAG & Agentes y crear proyectos reales?

Aprende arquitectura limpia, desarrollo web moderno, buenas prácticas y despliegue en producción con nuestro Bootcamp Fullstack Developer con mentorías en vivo.

🏛️ Autoría y Propiedad Intelectual: Obra educativa original de David Amador para Kilopolis Academia (Solucionáticos), estructurada sobre las especificaciones y estándares abiertos del W3C, el estándar vivo de WHATWG y la documentación oficial de código abierto. © Todos los derechos reservados.