ai
The AI SDK is Vercel's provider-agnostic TypeScript toolkit for building LLM applications and agents. One API (generateText, streamText, structured output via zod schemas, tool calls) works across OpenAI, Anthropic, Google, and other providers: either through the Vercel AI Gateway with plain model strings like 'anthropic/claude-opus-4.6', or through per-provider packages such as @ai-sdk/openai. Companion UI packages add useChat-style hooks for React, Svelte, Vue, and Angular, and the ToolLoopAgent class runs multi-step tool loops server side with a typed message protocol down to the browser.
The default for TypeScript LLM apps, especially anything with streaming chat UI in Next.js. You pay for its pace: pin versions and read the migration notes on every major.
Use it if
- You want to swap model providers without rewriting call sites: the model is a string or provider function and the surrounding code stays identical
- You are building chat UI in Next.js, React, Svelte, or Vue and want streaming, message state, and tool rendering handled by useChat instead of hand-rolled SSE
- You need typed structured output: zod schema in, validated object out
- You are building tool-using agents and want the tool loop, streaming, and UI message protocol as one coherent stack
- You call exactly one provider with plain prompts: the official openai or @anthropic-ai/sdk client is a smaller, more direct dependency
- You need provider-specific beta features the day they ship: the unified abstraction usually trails the raw provider APIs
- Your team dislikes fast-moving majors: the SDK is on major version 7 with about 1,750 open issues and PRs, and every major brings a migration guide you actually have to read
- You work outside TypeScript/JavaScript: this stack is TS-only, so Python teams should use provider SDKs or another toolkit
Setup reality
npm install ai requires Node 22+. The quick path routes calls through the Vercel AI Gateway using bare model strings, which needs a gateway credential; direct provider access means installing @ai-sdk/openai, @ai-sdk/anthropic, and so on separately, each reading its own API key env var. UI hooks live in yet another package per framework (@ai-sdk/react and friends). The recurring cost is churn: majors arrive fast, and message formats, tool definitions, and helper names have all been renamed across versions, so pin your version and budget upgrade time.
Patterns
Generate text with a gateway model stringgenerate-text
import { generateText } from 'ai';
const { text } = await generateText({
model: 'openai/gpt-5.4', // Vercel AI Gateway model string
prompt: 'What is an agent?',
});Gateway model strings need no provider package installed, but they do need an AI Gateway credential in the environment.
Stream a completion to stdoutstream-text
import { streamText } from 'ai';
const result = streamText({
model: 'anthropic/claude-opus-4.6',
prompt: 'Explain backpressure in one paragraph.',
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}streamText starts immediately and does not reject on model errors; errors arrive as part of the stream, so try/catch alone will not catch them.
Get validated structured output with zodstructured-output
import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
model: 'openai/gpt-5.4',
output: Output.object({
schema: z.object({
recipe: z.object({
name: z.string(),
steps: z.array(z.string()),
}),
}),
}),
prompt: 'Generate a lasagna recipe.',
});Read the result from output, not text; schema validation failures throw, so wrap strict schemas in try/catch.
Define a typed tooldefine-tool
import { tool } from 'ai';
import { z } from 'zod';
const getWeather = tool({
description: 'Get current weather for a city',
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, tempC: 21 }),
});The schema field is inputSchema; older examples on the web still show parameters, which no longer type-checks.
Run a multi-step agent with ToolLoopAgentagent-tool-loop
import { ToolLoopAgent } from 'ai';
const agent = new ToolLoopAgent({
model: 'openai/gpt-5.4',
system: 'You are an agent with access to a shell environment.',
tools: { shell: myShellTool },
});The agent keeps calling tools until the model stops requesting them; guard expensive tools with your own limits.
Serve an agent from a Next.js routeagent-route-handler
// app/api/chat/route.ts
import { createAgentUIStreamResponse } from 'ai';
import { agent } from '@/agent';
export async function POST(req: Request) {
const { messages } = await req.json();
return createAgentUIStreamResponse({ agent, messages });
}useChat posts to /api/chat by default; if you change the route path, configure the hook to match.
Build a chat UI with useChatchat-ui-react
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const { messages, status, sendMessage } = useChat();
const [input, setInput] = useState('');
return (
<form
onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput('');
}}
>
{messages.map((m) => (
<div key={m.id}>
{m.parts.map((part, i) =>
part.type === 'text' ? <span key={i}>{part.text}</span> : null,
)}
</div>
))}
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={status !== 'ready'}
/>
</form>
);
}Messages are arrays of typed parts, not plain strings; render by switching on part.type or tool parts silently disappear.
Render a tool call in the UI by staterender-tool-invocation
import { UIToolInvocation } from 'ai';
function ToolView({ invocation }: { invocation: UIToolInvocation<any> }) {
switch (invocation.state) {
case 'input-available':
return <div>Running tool...</div>;
case 'output-available':
return <pre>{JSON.stringify(invocation.output, null, 2)}</pre>;
}
return null;
}Tool parts move through states (input-available, then output-available); render every state or users see nothing while a tool runs.
Call a provider package directlydirect-provider
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';
const result = await generateText({
model: anthropic('claude-opus-4-6'),
prompt: 'Hello!',
});Provider packages are separate installs and read their own env vars (ANTHROPIC_API_KEY here); the gateway is bypassed entirely.
Use a system prompt with a message historysystem-prompt-messages
import { generateText } from 'ai';
const { text } = await generateText({
model: 'openai/gpt-5.4',
system: 'You answer in one sentence.',
messages: [{ role: 'user', content: 'What is RAG?' }],
});Pass prompt or messages, never both; the call rejects if the two are combined.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| openai | npm | You only call OpenAI models and want the official client with newest features first |
| @anthropic-ai/sdk | npm | Anthropic-only apps that want the raw Messages API without an abstraction layer |
| langchain | npm | You care more about chains, retrievers, and a large integration catalog than typed UI streaming |