Vercel AI SDK Hands-On Tutorial: Integrate AI Chat in Next.js in 10 Minutes
Streaming Output, Tool Calling, Multi-Model Switching – Full Feature Breakdown of Vercel AI SDK
Vercel AI SDK Hands-On Tutorial: Next.js AI Chat Integration
Why Vercel AI SDK?
Problems with directly calling the OpenAI API:
Vercel AI SDK solves all of these: a unified interface supports 30+ models, built-in React hooks manage conversation state, and streaming output works out of the box.
Installation
bash
npm install ai @ai-sdk/openai
or Claude: npm install ai @ai-sdk/anthropic
or Google: npm install ai @ai-sdk/google
Minimal Streaming Chat (10 Lines of Code)
API Route
typescript
// src/app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
});
return result.toDataStreamResponse();
}
Frontend Component
typescript
// src/components/chat.tsx
'use client';
import { useChat } from 'ai/react';export function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
{messages.map((m) => (
{m.role === 'user' ? 'You' : 'AI'}:
{m.content}
))}
);
}
Tool Calling
typescript
import { streamText, tool } from 'ai';
import { z } from 'zod';const result = streamText({
model: openai('gpt-4o'),
messages,
tools: {
getWeather: tool({
description: 'Get the current weather for a specified city',
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => {
const weather = await fetchWeatherAPI(city);
return { city, temperature: weather.temp };
},
}),
queryOrders: tool({
description: 'Query user order list',
parameters: z.object({
userId: z.string(),
status: z.enum(['pending', 'completed']).optional(),
}),
execute: async ({ userId, status }) => {
return { orders: await db.orders.findMany({ where: { userId, status } }) };
},
}),
},
maxSteps: 5,
});
Multi-Model Switching
typescript
import { openai } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google';function selectModel(taskType: string) {
const models: Record = {
code: anthropic('claude-3-5-sonnet-20241022'),
analysis: openai('gpt-4o'),
fast: openai('gpt-4o-mini'),
multimodal: google('gemini-2.5-pro'),
};
return models[taskType] ?? openai('gpt-4o-mini');
}
Structured JSON Output
typescript
import { generateObject } from 'ai';
import { z } from 'zod';const { object } = await generateObject({
model: openai('gpt-4o'),
schema: z.object({
name: z.string(),
category: z.enum(['electronics', 'clothing', 'food']),
price: z.number().positive(),
tags: z.array(z.string()).max(5),
}),
prompt: 'Generate product data based on the following information: ' + productInfo,
});
// object has full TypeScript type inference
Chat History Persistence
typescript
const result = streamText({
model: openai('gpt-4o'),
messages: [...historicalMessages, ...newMessages],
onFinish: async ({ text }) => {
await db.messages.create({
data: { conversationId, role: 'assistant', content: text },
});
},
});
Production: Rate Limiting + Authentication
typescript
import { auth } from '@clerk/nextjs/server';export async function POST(req: Request) {
const { userId } = await auth();
if (!userId) return new Response('Unauthorized', { status: 401 });
const { success } = await ratelimit.limit(userId);
if (!success) return new Response('Too many requests', { status: 429 });
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o'),
messages,
maxTokens: 1000, // Control single response length
});
return result.toDataStreamResponse();
}
Further Reading
Also available in 中文.