¿Por Qué Streaming?
Los LLMs pueden tardar varios segundos en generar una respuesta completa. El streaming envía tokens al usuario conforme se generan, mejorando dramáticamente el rendimiento percibido. Los usuarios ven la respuesta aparecer palabra por palabra en lugar de esperar la respuesta completa.
⚡ Beneficios del Streaming
Sin streaming: El usuario espera 5-10 segundos, luego ve la respuesta completa
Con streaming: El primer token aparece en ~200ms, la respuesta se construye progresivamente
Streaming Básico del Modelo
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
modelName: "gpt-4",
streaming: true,
});
// Transmitir tokens uno por uno
const stream = await model.stream("Explica los hooks de React en 3 oraciones.");
for await (const chunk of stream) {
process.stdout.write(chunk.content as string);
}
Streaming con Cadenas
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromTemplate(
"Escribe un tutorial corto sobre {topic}"
);
const model = new ChatOpenAI({ modelName: "gpt-4" });
const parser = new StringOutputParser();
const chain = prompt.pipe(model).pipe(parser);
const stream = await chain.stream({ topic: "volúmenes de Docker" });
let respuestaCompleta = "";
for await (const chunk of stream) {
process.stdout.write(chunk);
respuestaCompleta += chunk;
}
Server-Sent Events (SSE) con Next.js
El patrón más común para transmitir respuestas de IA en aplicaciones web.
// app/api/chat/route.ts
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
export async function POST(req: Request) {
const { message } = await req.json();
const prompt = ChatPromptTemplate.fromMessages([
["system", "Eres un asistente de programación útil."],
["human", "{input}"],
]);
const model = new ChatOpenAI({ modelName: "gpt-4", streaming: true });
const chain = prompt.pipe(model).pipe(new StringOutputParser());
const stream = await chain.stream({ input: message });
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
controller.enqueue(
encoder.encode(`data: ${JSON.stringify({ text: chunk })}\n\n`)
);
}
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(readable, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
},
});
}
Cliente React para SSE
"use client";
import { useState } from "react";
export default function Chat() {
const [respuesta, setRespuesta] = useState("");
const [transmitiendo, setTransmitiendo] = useState(false);
async function handleSubmit(mensaje: string) {
setRespuesta("");
setTransmitiendo(true);
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ message: mensaje }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
const lines = text.split("\n");
for (const line of lines) {
if (line.startsWith("data: ") && line !== "data: [DONE]") {
const data = JSON.parse(line.slice(6));
setRespuesta((prev) => prev + data.text);
}
}
}
setTransmitiendo(false);
}
return (
<div>
<button onClick={() => handleSubmit("Explica Docker")}>
Preguntar
</button>
<div>{respuesta}{transmitiendo && "▊"}</div>
</div>
);
}
💡 Puntos Clave
- • El streaming mejora dramáticamente el rendimiento percibido (200ms vs 5-10s)
- • Usa
.stream()en cualquier cadena para obtener un iterador async de chunks - • Server-Sent Events (SSE) es el patrón estándar para streaming web
- • Las cadenas RAG pueden transmitir la respuesta mientras proveen documentos fuente
- • Siempre maneja errores y desconexiones en clientes de streaming