Vercel AI SDK
Provider-agnostic streaming. Tool calls. UI hooks.
If LLM APIs are the engine, the Vercel AI SDK is the car. It is a TypeScript library that wraps every major provider (Anthropic, OpenAI, Google, Mistral, xAI, Groq, plus 30+ others) behind one consistent API. You write your app once, then swap the model string to switch providers. No more rewriting your codebase when a better model ships next Tuesday.
Two functions to rule them all
Ninety percent of what you do with the AI SDK on the server is one of these two functions:
generateTextcalls the model, waits for the full response, returns a string. Use it for short server-side tasks like classification, extraction, or one-off completions.streamTextcalls the model and gives you a streaming result object. Pipe it straight to a Response or to a React hook. Use it for anything user-facing.
import { generateText } from "ai";
export async function POST(req: Request) {
const { question } = await req.json();
const { text } = await generateText({
model: "anthropic/claude-opus-4-5",
prompt: question,
});
return Response.json({ text });
}Notice the model string: "anthropic/claude-opus-4-5". That is the model routing format used by the AI Gateway (more on that in a second). Change anthropic to openai and the rest of your code stays the same.
Streaming the response
For chat UIs, you want streaming. streamText returns a result object with helpers to convert into the response shape your framework expects. In a Next.js Route Handler:
import { streamText, convertToModelMessages } from "ai";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: "anthropic/claude-opus-4-5",
system: "You are a concise senior engineer.",
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}useChat: the React hook that wires it all up
On the client, the useChat hook from @ai-sdk/react handles message state, input bindings, and streaming for you. You pass it the URL of your streaming endpoint and it gives you back everything a chat UI needs.
"use client";
import { useChat } from "@ai-sdk/react";
import { useState } from "react";
export default function ChatPage() {
const { messages, sendMessage, status } = useChat();
const [input, setInput] = useState("");
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong>{" "}
{m.parts.map((p, i) =>
p.type === "text" ? <span key={i}>{p.text}</span> : null
)}
</div>
))}
<form
onSubmit={(e) => {
e.preventDefault();
if (!input.trim()) return;
sendMessage({ text: input });
setInput("");
}}
>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={status !== "ready"}
placeholder="Say something..."
/>
<button disabled={status !== "ready"}>Send</button>
</form>
</div>
);
}parts, not single text strings. That is so the same message can hold text, tool calls, images, and reasoning traces side by side. Loop over parts when rendering.Tool calling: give the model superpowers
A tool is a function you let the model call. You declare its name, description, and input schema (with Zod). The model decides when to invoke it. The SDK runs the function, sends the result back to the model, and the model continues. This is how you let an LLM check the weather, query your database, or send an email.
import { streamText, tool, convertToModelMessages, stepCountIs } from "ai";
import { z } from "zod";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: "anthropic/claude-opus-4-5",
messages: convertToModelMessages(messages),
stopWhen: stepCountIs(5),
tools: {
getWeather: tool({
description: "Get the current weather for a city.",
inputSchema: z.object({
city: z.string().describe("City name, e.g. 'Bangalore'"),
}),
execute: async ({ city }) => {
const res = await fetch("https://wttr.in/" + city + "?format=j1");
const data = await res.json();
return {
tempC: data.current_condition[0].temp_C,
desc: data.current_condition[0].weatherDesc[0].value,
};
},
}),
},
});
return result.toUIMessageStreamResponse();
}With stopWhen: stepCountIs(5) the SDK will run up to 5 rounds of model + tool calls before stopping. That is how the model can chain tools (look up a city, then fetch weather for it, then format a sentence).
The AI Gateway: one endpoint, every model
When you use a model string like "anthropic/claude-opus-4-5" (a provider/modelidentifier) without explicitly importing an SDK provider, the AI SDK routes through Vercel's AI Gateway. The Gateway is a hosted proxy that:
- Holds the API keys for every provider so you do not have to.
- Provides failover. If Anthropic is having a bad day, the request can fall through to OpenAI automatically.
- Aggregates usage and cost across all providers in one dashboard.
- Lets you switch models with a string change. No new env vars, no new install.
@ai-sdk/anthropic, @ai-sdk/openai, etc.) and pass the explicit provider instance. The Gateway is a convenience, not a requirement.Structured output
Need JSON instead of prose? Use generateObject or streamObject. You pass a Zod schema and the SDK guarantees the output matches it (or throws).
import { generateObject } from "ai";
import { z } from "zod";
const { object } = await generateObject({
model: "anthropic/claude-opus-4-5",
schema: z.object({
title: z.string(),
tags: z.array(z.string()).max(5),
sentiment: z.enum(["positive", "neutral", "negative"]),
}),
prompt: "Classify this review: 'Loved every minute.'",
});
console.log(object.sentiment); // "positive"Quick quiz
What is the main reason to use the Vercel AI SDK over a provider SDK directly?
Recap
generateTextfor one-shot,streamTextfor streaming,generateObjectfor typed JSON.useChatfrom@ai-sdk/reacthandles state, input, and streaming on the client.- Tools let the model call your functions. Define them with Zod schemas and
executefunctions. - The AI Gateway proxies provider/model strings so you can change models without code changes.
- Messages are arrays of
parts, not strings, so they can carry mixed content.