streamText, generateObject, useChat, and provider switching in under 30 minutes
What the Vercel AI SDK is
The Vercel AI SDK is a TypeScript library that makes it easy to add streaming LLM features to web applications — especially Next.js apps. It abstracts over multiple LLM providers (OpenAI, Anthropic, Google, Mistral, and more), provides React hooks for building chat UIs, and handles the streaming plumbing that would otherwise require significant boilerplate.
It has two main layers: the AI SDK Core (server-side, framework-agnostic) and the AI SDK UI (client-side React hooks). This guide covers both.
Installation
npm install ai @ai-sdk/openai
# Or for Anthropic:
npm install ai @ai-sdk/anthropic
# Or Google:
npm install ai @ai-sdk/google
Core: streamText — streaming text from the server
streamText is the main server-side function. It streams tokens from any supported LLM and returns a response that can be piped to the client.
// app/api/chat/route.ts
import { streamText, convertToModelMessages, UIMessage } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'You are a helpful assistant.',
messages: convertToModelMessages(messages), // UI -> model messages
maxOutputTokens: 1024,
temperature: 0.7,
});
return result.toUIMessageStreamResponse(); // streams to client
}UI: useChat — the React hook
useChat connects your React component to the streaming API route above. It manages the message list and the streaming for you. In AI SDK v5 the hook no longer owns the input box, so you keep the input value in your own useState and read a status string instead of an isLoading boolean.
// app/page.tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
export default function ChatPage() {
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
const [input, setInput] = useState('');
return (
<div>
<div>
{messages.map(m => (
<div key={m.id}>
<strong>{m.role}:</strong>{' '}
{m.parts.map((part, i) =>
part.type === 'text' ? <span key={i}>{part.text}</span> : null
)}
</div>
))}
{status === 'streaming' && <div>Thinking...</div>}
</div>
<form onSubmit={e => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }}>
<input value={input} onChange={e => setInput(e.target.value)} placeholder='Ask anything...' />
<button type='submit' disabled={status !== 'ready'}>Send</button>
</form>
</div>
);
}useChat appends each exchange to the messages array and re-renders as tokens arrive. It manages the streaming and message history for you; in v5 you supply the input value yourself and read the current status ('ready', 'submitted', 'streaming', or 'error') instead of an isLoading boolean.generateObject — structured data from LLMs
generateObject returns a typed, validated Zod object instead of text. Use it whenever you need structured output from an LLM.
// app/api/extract/route.ts
import { generateObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
const ProductSchema = z.object({
name: z.string(),
price: z.number(),
category: z.enum(['electronics', 'clothing', 'food', 'other']),
inStock: z.boolean(),
tags: z.array(z.string()),
});
export async function POST(req: Request) {
const { description } = await req.json();
const { object } = await generateObject({
model: openai('gpt-4o'),
schema: ProductSchema,
prompt: `Extract product details from: ${description}`,
});
return Response.json(object); // fully typed and validated
}
useObject — streaming structured data to the client
useObject is the client-side counterpart to generateObject with streamObject on the server. It delivers partial objects as they stream, updating the UI progressively.
// Server: app/api/review/route.ts
import { streamObject } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
const ReviewSchema = z.object({
rating: z.number().min(1).max(5),
summary: z.string(),
pros: z.array(z.string()),
cons: z.array(z.string()),
});
export async function POST(req: Request) {
const { text } = await req.json();
const result = streamObject({
model: openai('gpt-4o'),
schema: ReviewSchema,
prompt: `Analyse this product review: ${text}`,
});
return result.toTextStreamResponse();
}
// Client: components/Review.tsx
// 'use client';
// import { experimental_useObject as useObject } from '@ai-sdk/react';
// const { object, submit, isLoading } = useObject({ api: '/api/review', schema: ReviewSchema });
// object.rating arrives as soon as that field is generatedProvider switching
The SDK uses a unified interface across providers. Switching is a one-line change.
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google';
// Same API, different providers
const gpt4o = openai('gpt-4o');
const claude = anthropic('claude-opus-4-8');
const gemini = google('gemini-2.0-flash');
// Swap the model variable — everything else stays the same
const result = streamText({
model: process.env.USE_CLAUDE ? claude : gpt4o,
messages,
});
Edge Runtime compatibility
AI SDK routes can run on the Edge Runtime, but it is opt-in via export const runtime = 'edge' and only works when your chosen provider and other dependencies are edge-compatible. When they are, add the export to the route:
export const runtime = 'edge'; // add to any route.ts
// Edge Runtime limitations to be aware of:
// - No Node.js built-ins (fs, path, etc.)
// - 25MB response limit on Vercel free tier
// - Cold starts are faster but warm memory is limited
generateText — non-streaming server calls
Use generateText when you do not need streaming — batch processing, background jobs, or when the response feeds into another step.
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const { text, usage } = await generateText({
model: anthropic('claude-haiku-4-5-20251001'), // cheapest model for batch work
prompt: 'Classify this support ticket as: billing, technical, or general: ' + ticket,
maxOutputTokens: 10,
});
console.log(text); // 'billing'
console.log(usage.totalTokens); // track costsQuick reference
| Function / Hook | Use case | Streaming |
|---|---|---|
| streamText | Server: stream text to client | Yes |
| generateText | Server: get text (no stream needed) | No |
| streamObject | Server: stream structured JSON | Yes |
| generateObject | Server: get validated object | No |
| useChat | Client: chat UI with history | Yes |
| useCompletion | Client: single-turn completion | Yes |
| useObject | Client: streaming structured data | Yes |