TechLead
Lección 12 de 18
5 min de lectura
LangChain

Streaming en LangChain

Transmite respuestas de LLM en tiempo real para mejor UX. Aprende streaming de tokens, cadenas y eventos enviados por el servidor

¿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

Continuar aprendiendo