tutoriales·15 min de lectura

Vercel AI SDK: Construye Agentes de IA con TypeScript desde Cero

Aprende a usar Vercel AI SDK para crear agentes de IA con TypeScript y Next.js. Streaming, herramientas, generacion de texto y patrones avanzados con ejemplos listos para produccion.

Vercel AI SDK: Construye Agentes de IA con TypeScript desde Cero

Vercel AI SDK es la libreria de TypeScript que unifica la forma de trabajar con modelos de IA en aplicaciones web. Con mas de 20 millones de descargas mensuales en npm, se convirtio en el estandar para construir desde chatbots hasta agentes autonomos que ejecutan herramientas, toman decisiones y responden en streaming.

Esta guia cubre todo el camino: desde instalar el paquete hasta tener un agente funcional con tool calling, streaming y una interfaz React conectada. Todo el codigo funciona con Next.js App Router y TypeScript.

Que hace AI SDK y por que te importa

AI SDK resuelve un problema concreto: cada proveedor de IA (OpenAI, Anthropic, Google) tiene su propia API, su propio formato de mensajes y su propia forma de manejar streaming. AI SDK abstrae todo eso en una interfaz unificada.

Cambiar de GPT-4o a Claude 4 o Gemini es cuestion de cambiar una linea. El resto de tu codigo no se toca. Eso incluye streaming, tool calling, generacion de objetos estructurados y manejo de conversaciones.

Las funciones principales son:

  • generateText -- genera una respuesta completa y la devuelve cuando termina
  • streamText -- envia la respuesta token por token en tiempo real
  • generateObject -- genera datos estructurados con un schema de Zod
  • tool -- define herramientas que el modelo puede ejecutar

Si quieres entender bien el sistema de tipos que usa AI SDK por debajo, revisa la guia de tipos genericos en TypeScript.

Instalacion y configuracion

Necesitas dos paquetes: el core de AI SDK y al menos un proveedor. En este tutorial usamos OpenAI, pero el codigo es identico para cualquier proveedor.

Terminal
$ npm install ai @ai-sdk/openai zod

Si prefieres usar Anthropic o Google en lugar de OpenAI:

Terminal
$ npm install @ai-sdk/anthropic @ai-sdk/google

Variables de entorno

Crea tu archivo .env.local con la API key del proveedor que vayas a usar:

bash
# Para OpenAI
OPENAI_API_KEY=tu-api-key-aqui
 
# Para Anthropic (opcional)
ANTHROPIC_API_KEY=tu-api-key-aqui
 
# Para Google (opcional)
GOOGLE_GENERATIVE_AI_API_KEY=tu-api-key-aqui
⚠️
Nunca subas tus API keys al repositorio. Agrega .env.local a tu .gitignore. Si necesitas repasar como funcionan las variables de entorno en Next.js, tengo una guia dedicada al tema.

AI SDK detecta automaticamente las variables de entorno por convencion. Si tu variable se llama OPENAI_API_KEY, no necesitas configurar nada mas. El proveedor la lee directamente de process.env.

generateText: generacion de texto basica

generateText es la forma mas directa de obtener una respuesta del modelo. Llama a la API, espera la respuesta completa y te la devuelve.

typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
 
async function generarResumen(texto: string) {
  const { text } = await generateText({
    model: openai("gpt-4o"),
    prompt: `Resume el siguiente texto en 3 puntos clave:\n\n${texto}`,
  });
 
  return text;
}

Tambien puedes usar el formato de mensajes para conversaciones con contexto:

typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
 
async function consultarModelo() {
  const { text, usage } = await generateText({
    model: openai("gpt-4o"),
    system: "Eres un asistente tecnico especializado en TypeScript y Next.js.",
    messages: [
      {
        role: "user",
        content: "Cual es la diferencia entre Server Components y Client Components?",
      },
    ],
  });
 
  console.log(text);
  // uso de tokens para monitorear costos
  console.log(`Tokens usados: ${usage.totalTokens}`);
}

El objeto usage te da promptTokens, completionTokens y totalTokens. Esto es clave para controlar costos en produccion.

ℹ️
Cambiar de proveedor es literal una linea. Reemplaza openai("gpt-4o") por anthropic("claude-sonnet-4-20250514") o google("gemini-2.5-flash") y el resto del codigo sigue igual.

streamText: respuestas en tiempo real

Para interfaces de chat, esperar la respuesta completa da una experiencia mala. streamText envia la respuesta token por token, exactamente como lo ves en ChatGPT o Claude.

typescript
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
 
async function streamRespuesta() {
  const resultado = streamText({
    model: openai("gpt-4o"),
    prompt: "Explica que es un closure en JavaScript en terminos simples.",
  });
 
  // Iterar sobre el stream de texto
  for await (const textPart of resultado.textStream) {
    process.stdout.write(textPart);
  }
}

La diferencia clave: generateText devuelve una Promise que resuelve cuando termina. streamText devuelve un objeto con streams que puedes consumir inmediatamente.

Cuando usar cada uno

FuncionCaso de uso
generateTextProcesamiento en background, emails, resumenes, tareas sin UI
streamTextChat en tiempo real, interfaces donde el usuario ve la respuesta

Tool calling: dale herramientas al modelo

Aqui es donde AI SDK se pone interesante. Con tool calling, defines funciones que el modelo puede decidir ejecutar. El modelo no ejecuta codigo directamente, sino que indica que herramienta quiere usar y con que parametros. AI SDK ejecuta la funcion por ti y le devuelve el resultado al modelo.

typescript
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
 
const resultado = await generateText({
  model: openai("gpt-4o"),
  prompt: "Cual es el clima en Buenos Aires hoy?",
  tools: {
    obtenerClima: tool({
      description: "Obtiene el clima actual de una ciudad",
      parameters: z.object({
        ciudad: z.string().describe("Nombre de la ciudad"),
        unidad: z
          .enum(["celsius", "fahrenheit"])
          .optional()
          .describe("Unidad de temperatura"),
      }),
      execute: async ({ ciudad, unidad = "celsius" }) => {
        // Aqui va tu llamada real a una API de clima
        const respuesta = await fetch(
          `https://api.weather.example/current?city=${ciudad}&unit=${unidad}`
        );
        return respuesta.json();
      },
    }),
  },
});

El schema de Zod no es opcional. AI SDK lo usa para generar la descripcion de los parametros que el modelo necesita para decidir como llamar a la herramienta. Cada .describe() en Zod le da contexto al modelo sobre que espera ese campo.

Herramientas multiples

Puedes definir todas las herramientas que necesites. El modelo decide cual usar segun el contexto:

typescript
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
 
const resultado = await generateText({
  model: openai("gpt-4o"),
  prompt: "Busca informacion sobre Next.js y guardala en la base de datos.",
  tools: {
    buscarWeb: tool({
      description: "Busca informacion en la web",
      parameters: z.object({
        query: z.string().describe("Termino de busqueda"),
      }),
      execute: async ({ query }) => {
        // Logica de busqueda
        return { resultados: [`Resultado para: ${query}`] };
      },
    }),
    guardarEnDB: tool({
      description: "Guarda informacion en la base de datos",
      parameters: z.object({
        titulo: z.string(),
        contenido: z.string(),
        tags: z.array(z.string()).optional(),
      }),
      execute: async ({ titulo, contenido, tags }) => {
        // Logica de insercion en DB
        return { exito: true, id: "abc-123" };
      },
    }),
  },
});
💡
Las descripciones de las herramientas y los parametros son tan importantes como el prompt. El modelo las lee para decidir que herramienta usar y como llenar cada campo. Dedica tiempo a escribirlas bien.

Construir un agente: tool calling en bucle

Un agente es un modelo que ejecuta herramientas en multiples pasos. En lugar de una sola llamada, el modelo razona, ejecuta una herramienta, analiza el resultado, y decide si necesita ejecutar otra herramienta o si ya tiene la respuesta final.

AI SDK maneja esto con el parametro maxSteps en streamText o generateText:

typescript
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
 
const resultado = await generateText({
  model: openai("gpt-4o"),
  system: `Eres un asistente de investigacion. Tienes acceso a herramientas
para buscar informacion y hacer calculos. Usa las herramientas que necesites
para responder la pregunta del usuario de forma completa.`,
  prompt: "Cuantos habitantes tiene Argentina y cual es su PIB per capita?",
  tools: {
    buscarDatos: tool({
      description: "Busca datos estadisticos de un pais",
      parameters: z.object({
        pais: z.string().describe("Nombre del pais"),
        tipo: z
          .enum(["poblacion", "pib", "superficie", "general"])
          .describe("Tipo de dato a buscar"),
      }),
      execute: async ({ pais, tipo }) => {
        // Simulacion -- en produccion conectas a una API real
        const datos: Record<string, Record<string, string>> = {
          argentina: {
            poblacion: "46.6 millones (2025)",
            pib: "USD 621 mil millones (2025)",
            superficie: "2.78 millones de km2",
          },
        };
        const paisKey = pais.toLowerCase();
        if (datos[paisKey]) {
          return tipo === "general"
            ? datos[paisKey]
            : { [tipo]: datos[paisKey][tipo] };
        }
        return { error: "Pais no encontrado" };
      },
    }),
    calcular: tool({
      description: "Realiza un calculo matematico",
      parameters: z.object({
        expresion: z.string().describe("Expresion matematica a evaluar"),
      }),
      execute: async ({ expresion }) => {
        // En produccion usa una libreria de math segura, no eval
        const resultado = Function(`"use strict"; return (${expresion})`)();
        return { resultado: String(resultado) };
      },
    }),
  },
  // Permite hasta 5 pasos de razonamiento
  maxSteps: 5,
});
 
// Ver los pasos que tomo el agente
console.log(`Pasos ejecutados: ${resultado.steps.length}`);
console.log(`Respuesta final: ${resultado.text}`);

Con maxSteps: 5, el agente puede hacer hasta 5 ciclos de razonamiento. En cada paso, el modelo decide si llama a una herramienta o si genera la respuesta final. AI SDK ejecuta las herramientas y alimenta los resultados de vuelta al modelo automaticamente.

El array resultado.steps te da visibilidad completa de lo que hizo el agente en cada paso: que herramienta llamo, con que parametros, y que resultado obtuvo.

⚠️
Define siempre un maxSteps razonable. Sin limite, un modelo podria entrar en un bucle infinito llamando herramientas sin parar. Para la mayoria de los casos, entre 3 y 10 pasos es suficiente.

Route handler: API de streaming en Next.js

Ahora conectamos todo con Next.js. El route handler recibe los mensajes del frontend y devuelve un stream que el cliente consume en tiempo real.

typescript
// app/api/chat/route.ts
import { streamText, tool, UIMessage, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
 
// Permitir respuestas largas en Vercel
export const maxDuration = 30;
 
export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();
 
  const resultado = streamText({
    model: openai("gpt-4o"),
    system: `Eres un asistente tecnico especializado en desarrollo web.
Responde siempre en espanol. Puedes usar herramientas para obtener
informacion cuando la necesites.`,
    messages: convertToModelMessages(messages),
    tools: {
      buscarDocumentacion: tool({
        description: "Busca en la documentacion oficial de una tecnologia",
        parameters: z.object({
          tecnologia: z.string().describe("Nombre de la tecnologia"),
          tema: z.string().describe("Tema especifico a buscar"),
        }),
        execute: async ({ tecnologia, tema }) => {
          // Conecta a tu fuente de datos real
          return {
            resultado: `Documentacion de ${tecnologia} sobre ${tema}`,
            url: `https://docs.example.com/${tecnologia}/${tema}`,
          };
        },
      }),
    },
    maxSteps: 3,
  });
 
  return resultado.toUIMessageStreamResponse();
}

Puntos clave de este route handler:

  • UIMessage y convertToModelMessages -- convierten los mensajes del formato de UI al formato que espera el modelo. Esto es necesario porque el frontend maneja parts (texto, tool calls, tool results) de forma distinta al modelo.
  • toUIMessageStreamResponse -- convierte el stream a un formato que el hook useChat del frontend consume directamente.
  • maxDuration: 30 -- necesario si desplegaste en Vercel, donde las funciones serverless tienen timeout por defecto de 10 segundos.

Si planeas hacer deploy de esta aplicacion, revisa la guia de deploy en Vercel con Next.js para configurar correctamente el proyecto.

Componente React: chat con useChat

El hook useChat conecta tu componente React con el route handler. Maneja el estado de los mensajes, el envio, el streaming y los errores.

tsx
"use client";
 
import { useChat } from "ai/react";
 
export function Chat() {
  const { messages, input, handleInputChange, handleSubmit, status, error } =
    useChat();
 
  return (
    <div className="mx-auto flex w-full max-w-2xl flex-col gap-4 py-8">
      {/* Lista de mensajes */}
      <div className="flex flex-col gap-3">
        {messages.map((mensaje) => (
          <div
            key={mensaje.id}
            className={`rounded-lg px-4 py-3 ${
              mensaje.role === "user"
                ? "bg-blue-600 text-white self-end"
                : "bg-zinc-800 text-zinc-100 self-start"
            }`}
          >
            {mensaje.parts.map((part, i) => {
              if (part.type === "text") {
                return <p key={i}>{part.text}</p>;
              }
              // Puedes renderizar tool calls y results aqui
              if (part.type === "tool-invocation") {
                return (
                  <div key={i} className="text-sm text-zinc-400 italic">
                    Ejecutando: {part.toolInvocation.toolName}...
                  </div>
                );
              }
              return null;
            })}
          </div>
        ))}
      </div>
 
      {/* Indicador de estado */}
      {status === "streaming" && (
        <p className="text-sm text-zinc-500">Escribiendo...</p>
      )}
 
      {/* Error */}
      {error && (
        <p className="text-sm text-red-400">
          Error: {error.message}
        </p>
      )}
 
      {/* Formulario de entrada */}
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Escribe tu mensaje..."
          className="flex-1 rounded-lg border border-zinc-700 bg-zinc-900 px-4 py-2 text-zinc-100"
        />
        <button
          type="submit"
          disabled={status === "streaming"}
          className="rounded-lg bg-blue-600 px-4 py-2 text-white disabled:opacity-50"
        >
          Enviar
        </button>
      </form>
    </div>
  );
}

El flujo completo funciona asi: el usuario escribe un mensaje, handleSubmit lo envia al route handler (/api/chat por defecto), el handler procesa con streamText y devuelve un stream, y useChat actualiza messages en tiempo real mientras llegan los tokens.

ℹ️
El componente necesita la directiva "use client" porque useChat usa estado de React. Si necesitas repasar cuando usar Server Components vs Client Components, revisa la guia de Server Components.

Estructura del proyecto

Asi queda la estructura de archivos para una app de chat con AI SDK:

Estructura de archivos

app/ api/ chat/ route.ts page.tsx components/ Chat.tsx .env.local package.json

Mejores practicas para produccion

Manejo de errores

Nunca dejes que un error del proveedor de IA rompa tu aplicacion. Envuelve las llamadas en try/catch y devuelve respuestas utiles:

typescript
// app/api/chat/route.ts
import { streamText, UIMessage, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";
 
export async function POST(req: Request) {
  try {
    const { messages }: { messages: UIMessage[] } = await req.json();
 
    const resultado = streamText({
      model: openai("gpt-4o"),
      messages: convertToModelMessages(messages),
    });
 
    return resultado.toUIMessageStreamResponse();
  } catch (error) {
    console.error("Error en la API de chat:", error);
 
    // Verificar si es un error de rate limit
    if (error instanceof Error && error.message.includes("429")) {
      return new Response("Demasiadas solicitudes. Intenta en unos segundos.", {
        status: 429,
      });
    }
 
    return new Response("Error interno del servidor.", {
      status: 500,
    });
  }
}

Control de costos

Cada llamada a un modelo de IA cuesta dinero. Algunas estrategias para mantener los costos bajo control:

  • Elige el modelo correcto: no uses GPT-4o para tareas que GPT-4o-mini resuelve igual de bien. La diferencia de precio es 10x o mas.
  • Limita maxTokens: define un tope de tokens en la respuesta para evitar respuestas excesivamente largas.
  • Monitorea usage: el objeto usage que devuelven generateText y streamText te da los tokens exactos de cada llamada.
  • Cachea respuestas: si multiples usuarios hacen la misma pregunta, no necesitas llamar al modelo cada vez.
typescript
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
 
const resultado = await generateText({
  model: openai("gpt-4o-mini"), // Modelo mas economico
  prompt: "Que es TypeScript?",
  maxTokens: 500, // Limitar longitud de respuesta
});
 
// Monitorear costos
console.log(`Tokens de entrada: ${resultado.usage.promptTokens}`);
console.log(`Tokens de salida: ${resultado.usage.completionTokens}`);
💡
Revisa el pricing de OpenAI y el pricing de Anthropic para comparar costos entre modelos. La diferencia entre usar el modelo correcto y el incorrecto puede ser de cientos de dolares al mes.

Rate limiting

Protege tu endpoint para que un solo usuario no queme tu presupuesto de API:

typescript
// lib/rate-limit.ts
const solicitudes = new Map<string, { count: number; resetTime: number }>();
 
export function verificarRateLimit(
  ip: string,
  limite: number = 20,
  ventanaMs: number = 60_000
): boolean {
  const ahora = Date.now();
  const registro = solicitudes.get(ip);
 
  if (!registro || ahora > registro.resetTime) {
    solicitudes.set(ip, { count: 1, resetTime: ahora + ventanaMs });
    return true;
  }
 
  if (registro.count >= limite) {
    return false;
  }
 
  registro.count++;
  return true;
}
typescript
// app/api/chat/route.ts
import { streamText, UIMessage, convertToModelMessages } from "ai";
import { openai } from "@ai-sdk/openai";
import { verificarRateLimit } from "@/lib/rate-limit";
 
export async function POST(req: Request) {
  // Obtener IP del usuario
  const ip = req.headers.get("x-forwarded-for") ?? "unknown";
 
  if (!verificarRateLimit(ip)) {
    return new Response("Limite de solicitudes alcanzado.", { status: 429 });
  }
 
  const { messages }: { messages: UIMessage[] } = await req.json();
 
  const resultado = streamText({
    model: openai("gpt-4o-mini"),
    messages: convertToModelMessages(messages),
  });
 
  return resultado.toUIMessageStreamResponse();
}

Cambiar de proveedor sin tocar tu codigo

Una de las ventajas mas grandes de AI SDK es la abstraccion de proveedores. Si defines tu modelo en un solo lugar, cambiar de proveedor es un cambio de una linea:

typescript
// lib/ai-model.ts
import { openai } from "@ai-sdk/openai";
// import { anthropic } from "@ai-sdk/anthropic";
// import { google } from "@ai-sdk/google";
 
// Cambia esta linea para cambiar de proveedor
export const modelo = openai("gpt-4o-mini");
 
// Alternativas:
// export const modelo = anthropic("claude-sonnet-4-20250514");
// export const modelo = google("gemini-2.5-flash");
typescript
// app/api/chat/route.ts
import { streamText, UIMessage, convertToModelMessages } from "ai";
import { modelo } from "@/lib/ai-model";
 
export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();
 
  const resultado = streamText({
    model: modelo, // Un solo lugar para cambiar el modelo
    messages: convertToModelMessages(messages),
  });
 
  return resultado.toUIMessageStreamResponse();
}

Esto te permite hacer A/B testing entre modelos, tener un modelo diferente en desarrollo y produccion, o migrar de OpenAI a Anthropic sin refactorizar toda tu aplicacion.

Resumen rapido

Lo que cubrimos en esta guia:

  1. Instalacion de AI SDK con proveedores y Zod
  2. generateText para respuestas completas en una sola llamada
  3. streamText para respuestas en tiempo real token por token
  4. Tool calling para que el modelo ejecute funciones definidas por ti
  5. Agentes con maxSteps para razonamiento en multiples pasos
  6. Route handler en Next.js para exponer streaming como API
  7. Componente React con useChat para la interfaz de chat
  8. Mejores practicas de error handling, rate limiting y control de costos

La documentacion oficial de AI SDK es excelente y cubre casos que no entraron aqui, como generateObject para datos estructurados, integracion con MCP (Model Context Protocol), y patrones avanzados de multi-agente.

Preguntas frecuentes

Que es Vercel AI SDK y para que sirve?

Vercel AI SDK es una libreria open-source de TypeScript que proporciona una interfaz unificada para construir aplicaciones con IA. Soporta multiples proveedores (OpenAI, Anthropic, Google) y maneja streaming, herramientas y agentes de forma estandarizada.

Necesito pagar para usar Vercel AI SDK?

No. Vercel AI SDK es completamente gratuito y open-source. Lo que pagas son las llamadas a los proveedores de IA (OpenAI, Anthropic, etc.) segun su pricing individual. Puedes probar con los tiers gratuitos de cada proveedor mientras aprendes.

Puedo usar AI SDK sin Vercel como hosting?

Si. AI SDK funciona con cualquier hosting que soporte Node.js: Railway, Render, AWS, tu propio servidor. No esta atado a Vercel, aunque se integra muy bien con su plataforma por el soporte nativo de edge functions y streaming.

Cual es la diferencia entre generateText y streamText?

generateText espera la respuesta completa antes de devolverla. Es ideal para procesamiento en background, generacion de emails, resumenes, o cualquier tarea donde no necesitas mostrar la respuesta en tiempo real. streamText envia la respuesta token por token, ideal para interfaces de chat donde el usuario ve la respuesta escribiendose progresivamente.

AI SDK soporta multiples proveedores de IA?

Si. AI SDK soporta OpenAI, Anthropic (Claude), Google (Gemini), Mistral, Cohere y mas. Puedes cambiar de proveedor cambiando una linea de codigo sin modificar el resto de tu aplicacion. Los schemas de herramientas, el manejo de streaming y la estructura de mensajes son identicos entre proveedores.

#ai-sdk#typescript#nextjs#ia#agentes#vercel

Preguntas frecuentes

Que es Vercel AI SDK y para que sirve?

Vercel AI SDK es una libreria open-source de TypeScript que proporciona una interfaz unificada para construir aplicaciones con IA. Soporta multiples proveedores (OpenAI, Anthropic, Google) y maneja streaming, herramientas y agentes de forma estandarizada.

Necesito pagar para usar Vercel AI SDK?

No, Vercel AI SDK es completamente gratuito y open-source. Lo que pagas son las llamadas a los proveedores de IA (OpenAI, Anthropic, etc.) segun su pricing individual.

Puedo usar AI SDK sin Vercel como hosting?

Si, AI SDK funciona con cualquier hosting que soporte Node.js. No esta atado a Vercel, aunque se integra muy bien con su plataforma.

Cual es la diferencia entre generateText y streamText?

generateText espera la respuesta completa antes de devolverla, ideal para procesamiento en background. streamText envia la respuesta token por token en tiempo real, ideal para interfaces de chat donde el usuario ve la respuesta escribiendose.

AI SDK soporta multiples proveedores de IA?

Si, AI SDK soporta OpenAI, Anthropic (Claude), Google (Gemini), Mistral, Cohere y mas. Puedes cambiar de proveedor cambiando una linea de codigo sin modificar el resto de tu aplicacion.