Chat en streaming y tool calling con Claude usando el Vercel AI SDK 7 (Next.js + TypeScript) — Cesar Ayala
← Todos los artículos

Chat en streaming y tool calling con Claude usando el Vercel AI SDK 7 (Next.js + TypeScript)

Instala ai + @ai-sdk/anthropic + zod; luego, en un Route Handler de Next.js, llama streamText({ model: anthropic('claude-sonnet-5'), messages }) y transmitelo a un cliente useChat. Agrega tools con un inputSchema de Zod más execute para que el SDK corra el bucle multi-paso; acotalo con stopWhen. El AI SDK 7 (2026-06-25) es solo-ESM.

¿Cómo armo un chat en streaming con tool calling usando el Vercel AI SDK y Claude?

En un Route Handler de Next.js llama a streamText({ model: anthropic('claude-sonnet-5'), messages }) y devuelve result.toUIMessageStreamResponse(); en el cliente, el hook useChat lo pinta token por token. Para darle herramientas, defines cada tool con un inputSchema de Zod más un execute, y el SDK corre el bucle multi-paso por ti; lo acotas con stopWhen. El AI SDK 7 (publicado el 2026-06-25) es solo-ESM: si tu proyecto usa require(), arréglalo antes de empezar.

Este es el equivalente en TypeScript de los posts de Claude en producción que ya publiqué en Python. Aquí no hay pseudocódigo: es el patrón que sí llega a producción.

¿Qué es el Vercel AI SDK y por qué la versión 7 para Claude?

El Vercel AI SDK es el kit de herramientas de facto en TypeScript para construir apps con LLMs: chat, streaming, tool calling y salidas estructuradas, sobre muchos proveedores con una sola API. Casi toda la plomería de streaming y tool calling que tendrías que escribir a mano ya está resuelta y probada en producción, así que es el camino más rápido a un chat con Claude andando en un producto Node o Next.js.

La versión mayor vigente es el AI SDK 7, publicada el 2026-06-25 según el changelog de Vercel. Está enfocada en agentes y trae un cambio que rompe a mucha gente: es solo-ESM. Necesitas sintaxis import (o archivos .mjs); el require() de CommonJS ya no funciona. En la práctica eso significa poner "type": "module" en tu package.json. Si copias un snippet de un tutorial viejo con require('ai'), revienta con un error de módulos que parece de configuración pero en realidad es este cambio de versión.

Menciono la versión de forma explícita a propósito: las APIs se movieron bastante entre versiones mayores. Fija mentalmente “AI SDK 7” y verifica cada nombre de método contra ai-sdk.dev al momento de escribir tu código. No presentes un patrón de v3 o v4 como si fuera el actual.

Versión mayorAI SDK 7 (2026-06-25)
MódulosSolo-ESM — nada de require()
EnfoqueAgentes y tool calling multi-paso
Paquetesai + @ai-sdk/anthropic + zod

Instalación: ai, @ai-sdk/anthropic, zod y el provider

Instalas el paquete central ai, el adaptador del proveedor y la librería de schemas en un solo comando:

npm install ai @ai-sdk/anthropic zod

El proveedor de Claude es @ai-sdk/anthropic. Lo importas y llamas al helper con un model id:

import { anthropic } from '@ai-sdk/anthropic';

const model = anthropic('claude-sonnet-5');

Ese anthropic() lee la variable de entorno ANTHROPIC_API_KEY de forma automática. En Next.js la pones en tu .env.local:

ANTHROPIC_API_KEY=sk-ant-...

Si necesitas configuración a la medida (un baseURL distinto, pasar la apiKey a mano, headers extra o un fetch propio), usa el factory createAnthropic en lugar del helper directo:

import { createAnthropic } from '@ai-sdk/anthropic';

const anthropic = createAnthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
  // baseURL, headers, fetch... si los necesitas
});

El detalle del model id: no copies los ids de ejemplo de la doc

Aquí es donde tropieza mucha gente. Le pasas al helper un id de Claude vigente como string:

anthropic('claude-sonnet-5');   // balanceado
anthropic('claude-opus-4-8');   // el más capaz

La documentación del AI SDK trae ids de ejemplo que envejecen mal, del estilo claude-sonnet-4-20250514. No los copies. Un id desactualizado te da un error del proveedor que no tiene nada que ver con tu código. Verifica el id actual contra la doc del proveedor Anthropic del AI SDK antes de pegarlo. Esta es la clase de detalle que separa un tutorial que corre de uno que ya no.

claude-sonnet-5 es el punto dulce para un chat: rápido, barato y más que capaz para tool calling. Reserva claude-opus-4-8 para lo que de verdad lo amerite.

Un chat en streaming en Next.js: el Route Handler y useChat

El patrón tiene dos piezas: un Route Handler en el servidor que abre el stream, y un componente cliente con useChat que lo consume.

El Route Handler llama a streamText y devuelve la respuesta de stream:

// app/api/chat/route.ts
import { anthropic } from '@ai-sdk/anthropic';
import { streamText } from 'ai';

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

  const result = streamText({
    model: anthropic('claude-sonnet-5'),
    messages,
  });

  return result.toUIMessageStreamResponse();
}

toUIMessageStreamResponse() es lo que el useChat del cliente espera; el viejo toDataStreamResponse de versiones anteriores del SDK ya no existe en el AI SDK 7. Si copias un handler de un post viejo, ese es el primer método a corregir: verifica el nombre vigente contra ai-sdk.dev. Mismo cuidado con la ruta de import de useChat (viene de @ai-sdk/react).

Del lado del cliente, en el AI SDK 7 el hook devuelve messages y sendMessage — ya no maneja el input por ti, así que el input lo guardas en tu propio useState, y renderizas cada mensaje desde su arreglo parts en vez de un solo string content:

'use client';
import { useState } from 'react';
import { useChat } from '@ai-sdk/react';

export default function Chat() {
  const { messages, sendMessage } = useChat();
  const [input, setInput] = useState('');

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        if (!input.trim()) return;
        sendMessage({ text: input });
        setInput('');
      }}
    >
      {messages.map((m) => (
        <p key={m.id}>
          <b>{m.role}:</b>{' '}
          {m.parts.map((part, i) =>
            part.type === 'text' ? <span key={i}>{part.text}</span> : null,
          )}
        </p>
      ))}
      <input value={input} onChange={(e) => setInput(e.target.value)} />
    </form>
  );
}

Eso es un chat en streaming completo. El hook envía el POST a tu Route Handler, recibe los tokens conforme llegan y re-renderiza. No escribes ni una línea de manejo de streams a mano.

useChat hace POST a /api/chat
streamText llama a Claude
toUIMessageStreamResponse abre el stream
useChat pinta los tokens en vivo

Tool calling: description + inputSchema de Zod + execute

Aquí es donde el AI SDK brilla de verdad. Le pasas un objeto tools a streamText o a generateText. Cada herramienta lleva una description, un inputSchema de Zod y una función execute:

import { anthropic } from '@ai-sdk/anthropic';
import { streamText, tool, isStepCount } from 'ai';
import { z } from 'zod';

const result = streamText({
  model: anthropic('claude-sonnet-5'),
  messages,
  tools: {
    getWeather: tool({
      description: 'Obtiene el clima actual de una ciudad',
      inputSchema: z.object({
        city: z.string().describe('El nombre de la ciudad'),
      }),
      execute: async ({ city }) => {
        const data = await fetchWeather(city);
        return { tempC: data.tempC, condition: data.condition };
      },
    }),
  },
  stopWhen: isStepCount(5),
});

El inputSchema de Zod hace doble trabajo: le dice a Claude la forma exacta de los argumentos, y valida lo que Claude devuelve antes de que llegue a tu execute. Sin parseo frágil, sin JSON.parse con try/catch a mano.

Lo mejor: el SDK corre el bucle multi-paso por ti. Cuando Claude decide llamar getWeather, el SDK ejecuta tu función, le devuelve el resultado al modelo y lo deja continuar hasta redactar la respuesta final. Tú no orquestas ese ida y vuelta. Si vienes de armar ese bucle a mano contra la Messages API cruda de Claude, esto te ahorra el pedazo más tedioso.

Acotar y controlar el bucle con stopWhen

Un bucle de herramientas sin límite es una factura sin límite. En el AI SDK 7 lo acotas con stopWhen:

import { streamText, isStepCount } from 'ai';

const result = streamText({
  model: anthropic('claude-sonnet-5'),
  messages,
  tools: { getWeather /* ... */ },
  stopWhen: isStepCount(5),
});

El helper exacto también cambió entre versiones (en algunas fue un maxSteps de nivel superior). Verifica el nombre vigente contra ai-sdk.dev antes de escribirlo. La idea es la misma: pones un techo de pasos para que un modelo indeciso no encadene veinte llamadas a herramientas y se queme el presupuesto. Empieza con un límite bajo, como cinco pasos, y súbelo solo si tu caso real lo pide.

  1. Claude pide una toolEl SDK valida los args contra el inputSchema de Zod
  2. Corre executeTu función devuelve datos reales al modelo
  3. Claude continúaEncadena más tools o redacta la respuesta
  4. stopWhen cortaEl techo de pasos evita bucles infinitos y sobrecostos

Manejo de errores y abort para producción

Un tutorial se detiene en el happy path. Producción no. Dos cosas que sí necesitas.

Primero, cancelación. useChat expone un stop para abortar un stream en curso cuando el usuario cambia de idea o cierra la vista:

const { messages, stop, status } = useChat();
// <button onClick={stop} disabled={status !== 'streaming'}>Detener</button>

Segundo, errores del proveedor y de tus herramientas. Un execute puede fallar (una API caída, un timeout), y el modelo puede devolver un error. Envuelve tu Route Handler y deja que el error viaje de forma controlada al cliente:

export async function POST(req: Request) {
  try {
    const { messages } = await req.json();
    const result = streamText({
      model: anthropic('claude-sonnet-5'),
      messages,
      onError: ({ error }) => console.error('stream error', error),
    });
    return result.toUIMessageStreamResponse();
  } catch (err) {
    return new Response('Error del servidor', { status: 500 });
  }
}

Registra el onError desde el día uno. Cuando un execute truene en producción, vas a querer el stack, no un chat que se quedó mudo sin explicación.

AI SDK contra la Messages API cruda: ¿cuándo usar cada una?

Las dos son válidas. La decisión es de altura, no de gusto.

Vercel AI SDK 7

  • Chat en streaming con useChat casi sin código
  • Corre el bucle multi-paso de tools por ti
  • inputSchema de Zod valida y tipa los args
  • Cambia de proveedor con una línea
  • ESM-only: fija la versión y verifica los métodos

Messages API cruda de Claude

  • Control total del payload y los headers
  • Acceso inmediato a features nuevas de Anthropic
  • Tú orquestas el bucle de tools a mano
  • Sin capa de abstracción que verificar
  • Ideal para backends que no son TypeScript/React

Regla práctica: si estás en Next.js o React y quieres chat, streaming y tool calling andando rápido, el AI SDK te ahorra días. Si necesitas control fino del request, una feature recién salida de Anthropic, o estás fuera del ecosistema TypeScript, ve directo a la Messages API de Claude. No es una guerra religiosa; muchos equipos usan las dos según el servicio.

La ventaja competitiva y los siguientes pasos

El nicho que este post ocupa: Claude específico, en producción, con la versión 7. La mayoría de los tutoriales del AI SDK son genéricos o se quedaron en una versión vieja, y casi ninguno está en español pensado para quien lo pone en producción.

El hermano natural de streamText es generateObject: en vez de texto libre, te devuelve un objeto garantizado contra un schema de Zod, con reintento y reparación automática si el modelo se sale de forma. Esta es la contraparte en TypeScript del modo de salidas estructuradas de Anthropic. Cúbrelo en la guía de generateObject + Zod y compáralo con las salidas estructuradas nativas de Claude.

De aquí, dos rutas. Si vas a darle a tu chat memoria sobre tus propios documentos, arma un sistema RAG con Claude. Si vas a que estas herramientas actúen solas y encadenen decisiones, ya estás construyendo un agente: empieza por qué es un agente de IA y sigue con el resto del hub de agentes de IA.

Para verificar cada método antes de escribirlo, tus tres fuentes son la documentación del AI SDK, el proveedor de Anthropic y el changelog del AI SDK 7. Fija la versión, verifica los nombres, y ponlo en producción.