TechLead
Lección 7 de 18
6 min de lectura
LangChain

LangChain con Next.js

Construye apps de IA full-stack usando LangChain con Next.js App Router

LangChain con Next.js

Next.js es el framework ideal para aplicaciones LangChain. El App Router ofrece capacidades del lado del servidor para ejecutar LangChain de forma segura, mientras que React Server Components y las rutas API habilitan patrones de streaming potentes.

🚀 Beneficios de Next.js + LangChain

  • Server Components: Ejecuta LangChain en el servidor de forma segura
  • Route Handlers: Crea endpoints API con streaming
  • Edge Runtime: Despliega en edge para baja latencia
  • Streaming integrado: Soporte nativo para respuestas en streaming

Configuración del proyecto

Crea un proyecto Next.js App Router e instala LangChain junto con el Vercel AI SDK.

# Create Next.js app
npx create-next-app@latest my-ai-app --typescript --tailwind --app
cd my-ai-app

# Install LangChain and AI SDK
npm install langchain @langchain/openai @langchain/core
npm install ai  # Vercel AI SDK

Variables de entorno

Guarda las claves del proveedor en .env.local para que solo estén disponibles en el servidor.

# .env.local
OPENAI_API_KEY=sk-your-openai-key
# Optional: for other providers
ANTHROPIC_API_KEY=sk-ant-your-key

Ruta API básica

Un Route Handler simple que acepta un mensaje, llama al modelo y retorna JSON.

// app/api/chat/route.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";
import { NextResponse } from "next/server";

const model = new ChatOpenAI({
  modelName: "gpt-4",
  temperature: 0.7,
});

export async function POST(req: Request) {
  try {
    const { message } = await req.json();

    const response = await model.invoke([
      new HumanMessage(message),
    ]);

    return NextResponse.json({
      content: response.content,
    });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to generate response" },
      { status: 500 }
    );
  }
}

Ruta API con streaming

Habilita streaming de tokens en tiempo real para mejor UX:

Transmite chunks del modelo y envíalos como respuesta legible al cliente.

// app/api/chat/stream/route.ts
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

export const runtime = 'edge';  // Optional: use edge runtime

export async function POST(req: Request) {
  const { messages } = await req.json();

  const model = new ChatOpenAI({
    modelName: "gpt-4",
    streaming: true,
  });

  // Convert to LangChain messages
  const langchainMessages = messages.map((m: any) =>
    m.role === 'user'
      ? new HumanMessage(m.content)
      : new SystemMessage(m.content)
  );

  // Create streaming response
  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      const streamResponse = await model.stream(langchainMessages);

      for await (const chunk of streamResponse) {
        const text = chunk.content;
        controller.enqueue(encoder.encode(text));
      }

      controller.close();
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
      'Transfer-Encoding': 'chunked',
    },
  });
}

Usar Vercel AI SDK (Recomendado)

El Vercel AI SDK simplifica el streaming de forma significativa:

Usa el adaptador de LangChain para convertir streams del modelo en una respuesta compatible con el SDK.

// app/api/chat/route.ts
import { ChatOpenAI } from "@langchain/openai";
import { LangChainAdapter, Message } from "ai";

export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages }: { messages: Message[] } = await req.json();

  const model = new ChatOpenAI({
    modelName: "gpt-4",
    streaming: true,
  });

  const stream = await model.stream(
    messages.map(m => ({
      role: m.role,
      content: m.content,
    }))
  );

  return LangChainAdapter.toDataStreamResponse(stream);
}

Componente de cliente

El cliente usa useChat para gestionar la entrada y renderizar respuestas en streaming.

// app/components/Chat.tsx
'use client';

import { useChat } from 'ai/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();

  return (
    <div className="flex flex-col h-screen max-w-2xl mx-auto p-4">
      <div className="flex-1 overflow-y-auto space-y-4">
        {messages.map(m => (
          <div
            key={m.id}
            className={`p-4 rounded-lg ${
              m.role === 'user' ? 'bg-blue-100 ml-auto' : 'bg-gray-100'
            } max-w-[80%]`}
          >
            <p className="font-semibold text-sm mb-1">
              {m.role === 'user' ? 'You' : 'AI'}
            </p>
            <p className="whitespace-pre-wrap">{m.content}</p>
          </div>
        ))}
      </div>

      <form onSubmit={handleSubmit} className="mt-4">
        <div className="flex gap-2">
          <input
            value={input}
            onChange={handleInputChange}
            placeholder="Ask something..."
            className="flex-1 p-3 border rounded-lg"
          />
          <button
            type="submit"
            disabled={isLoading}
            className="px-6 py-3 bg-blue-600 text-white rounded-lg disabled:opacity-50"
          >
            Send
          </button>
        </div>
      </form>
    </div>
  );
}

Acciones de servidor

Usa Server Actions para operaciones simples sin streaming:

Las acciones son ideales para tareas como resumir o traducir donde no se requiere streaming.

// app/actions.ts
'use server';

import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage } from "@langchain/core/messages";

const model = new ChatOpenAI({ modelName: "gpt-4" });

export async function generateSummary(text: string) {
  const response = await model.invoke([
    new HumanMessage(`Summarize this text: ${text}`),
  ]);

  return response.content as string;
}

export async function translateText(text: string, language: string) {
  const response = await model.invoke([
    new HumanMessage(`Translate to ${language}: ${text}`),
  ]);

  return response.content as string;
}

Usar acciones de servidor

Llama acciones desde formularios del cliente y gestiona el estado de la UI alrededor del resultado async.

// app/page.tsx
'use client';

import { useState } from 'react';
import { generateSummary } from './actions';

export default function Page() {
  const [summary, setSummary] = useState('');
  const [loading, setLoading] = useState(false);

  async function handleSummarize(formData: FormData) {
    setLoading(true);
    const text = formData.get('text') as string;
    const result = await generateSummary(text);
    setSummary(result);
    setLoading(false);
  }

  return (
    <form action={handleSummarize}>
      <textarea name="text" className="w-full p-2 border" />
      <button type="submit" disabled={loading}>
        {loading ? 'Summarizing...' : 'Summarize'}
      </button>
      {summary && <p>{summary}</p>}
    </form>
  );
}

RAG con Next.js

Combina un retriever con un prompt y un modelo para responder preguntas desde tu store de documentos.

// app/api/rag/route.ts
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { MemoryVectorStore } from "langchain/vectorstores/memory";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnableSequence, RunnablePassthrough } from "@langchain/core/runnables";

// Initialize once (consider caching in production)
let vectorStore: MemoryVectorStore | null = null;

async function getVectorStore() {
  if (!vectorStore) {
    // Load your documents here
    vectorStore = await MemoryVectorStore.fromTexts(
      ["Your documents...", "More content..."],
      [{}, {}],
      new OpenAIEmbeddings()
    );
  }
  return vectorStore;
}

export async function POST(req: Request) {
  const { question } = await req.json();

  const store = await getVectorStore();
  const retriever = store.asRetriever({ k: 4 });

  const model = new ChatOpenAI({ modelName: "gpt-4" });

  const prompt = ChatPromptTemplate.fromTemplate(`
    Answer based on context:
    {context}

    Question: {question}
  `);

  const chain = RunnableSequence.from([
    {
      context: retriever.pipe(docs => docs.map(d => d.pageContent).join("\n")),
      question: new RunnablePassthrough(),
    },
    prompt,
    model,
    new StringOutputParser(),
  ]);

  const answer = await chain.invoke(question);

  return Response.json({ answer });
}

Estructura del proyecto

Organiza rutas API, utilidades compartidas de LangChain y componentes UI en el App Router.

my-ai-app/
├── app/
│   ├── api/
│   │   ├── chat/
│   │   │   └── route.ts      # Chat API
│   │   └── rag/
│   │       └── route.ts      # RAG API
│   ├── components/
│   │   └── Chat.tsx          # Chat UI
│   ├── lib/
│   │   └── langchain.ts      # LangChain setup
│   ├── actions.ts            # Server Actions
│   ├── page.tsx              # Home page
│   └── layout.tsx
├── .env.local                # API keys
└── package.json

💡 Puntos clave

  • • Usa Route Handlers (app/api) para endpoints de LangChain
  • • Vercel AI SDK simplifica el streaming de forma notable
  • • Server Actions funcionan muy bien para operaciones sin streaming
  • • El runtime edge ofrece menor latencia para usuarios globales
  • • Mantén claves API en .env.local, nunca las expongas al cliente

📚 Más recursos

Continuar aprendiendo