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 } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'You are a helpful assistant.',
messages, // OpenAI message format
maxTokens: 1024,
temperature: 0.7,
});
return result.toDataStreamResponse(); // streams to client
}
UI: useChat — the React hook
useChat connects your React component to the streaming API route above. It manages messages, loading state, and input handling.
// app/page.tsx
'use client';
import { useChat } from 'ai/react';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({
api: '/api/chat',
});
return (
<div>
<div>
{messages.map(m => (
<div key={m.id}>
<strong>{m.role}:</strong> {m.content}
</div>
))}
{isLoading && <div>Thinking...</div>}
</div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} placeholder='Ask anything...' />
<button type='submit' disabled={isLoading}>Send</button>
</form>
</div>
);
}
useChat automatically appends each exchange to the messages array and re-renders as tokens arrive. You do not need to manage loading state or streaming manually.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 { useObject } from 'ai/react';
// const { object, submit, isLoading } = useObject({ api: '/api/review', schema: ReviewSchema });
// object.rating arrives as soon as that field is generated
Provider 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-6-20251101');
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
All AI SDK server functions run on the Edge Runtime in Next.js. Add the export to any 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,
maxTokens: 10,
});
console.log(text); // 'billing'
console.log(usage.totalTokens); // track costs
Quick 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 |