Vercel AI SDK
"Vercel AI SDK: unified LLM interface, useChat/useCompletion hooks, streaming, tool calling, multi-provider, RSC integration"
The Vercel AI SDK is a **unified interface** for building AI-powered applications across any LLM provider. It abstracts away provider differences so you can swap between OpenAI, Anthropic, Google, Groq, and others with a single line change. Build with three layers: **AI SDK Core** (server-side generation), **AI SDK UI** (React/Svelte/Vue hooks for streaming UI), and **AI SDK RSC** (React Server Components integration). Use the SDK to avoid vendor lock-in and to build streaming-first UIs with minimal boilerplate. ## Key Points - **Use `streamText` + `toDataStreamResponse()`** for all chat endpoints — streaming is the default expected UX. - **Define tools with Zod schemas** — the SDK validates parameters and provides full TypeScript types. - **Set `maxSteps`** when using tools to allow multi-turn tool execution loops. Without it, the model cannot call tools iteratively. - **Use `generateObject`/`streamObject`** for structured extraction — it is more reliable than parsing JSON from text. - **Use `@ai-sdk/react`'s `useChat`** for chat UIs — it handles streaming, message management, loading states, and errors. - **Create custom provider instances** with `createOpenAI()` to connect to any OpenAI-compatible API (Groq, Together, local models). - **Use `onFinish` callbacks** in `useChat` for analytics, logging, or triggering side effects after a response completes. - **Handle `isLoading` state** in the UI to disable inputs and show indicators during generation. - **Using `generateText` for chat endpoints** — always use `streamText` for user-facing responses. Non-streaming creates unacceptable latency. - **Not setting `maxSteps`** when tools are defined — without it, the model can only call tools once and cannot react to results. - **Building custom streaming parsing** instead of using `useChat` — the hook handles the AI SDK's streaming protocol automatically. - **Mixing raw fetch calls with AI SDK hooks** — `useChat` expects the specific data stream format from `toDataStreamResponse()`.
skilldb get ai-llm-services-skills/Vercel AI SDKFull skill: 351 linesVercel AI SDK Skill
Core Philosophy
The Vercel AI SDK is a unified interface for building AI-powered applications across any LLM provider. It abstracts away provider differences so you can swap between OpenAI, Anthropic, Google, Groq, and others with a single line change. Build with three layers: AI SDK Core (server-side generation), AI SDK UI (React/Svelte/Vue hooks for streaming UI), and AI SDK RSC (React Server Components integration). Use the SDK to avoid vendor lock-in and to build streaming-first UIs with minimal boilerplate.
Setup
Install the core package and a provider:
// Install: npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google
import { generateText, streamText } from "ai";
import { openai } from "@ai-sdk/openai";
// Basic text generation
const { text } = await generateText({
model: openai("gpt-4o"),
prompt: "Explain the observer pattern in one paragraph.",
});
console.log(text);
Swap providers with one line:
import { anthropic } from "@ai-sdk/anthropic";
import { google } from "@ai-sdk/google";
// Same code, different provider
const { text: claudeText } = await generateText({
model: anthropic("claude-sonnet-4-20250514"),
prompt: "Explain the observer pattern in one paragraph.",
});
const { text: geminiText } = await generateText({
model: google("gemini-2.0-flash"),
prompt: "Explain the observer pattern in one paragraph.",
});
Key Techniques
Streaming Text
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
const result = streamText({
model: openai("gpt-4o"),
prompt: "Write a guide to error handling in TypeScript.",
maxTokens: 1024,
});
// Stream to stdout
for await (const textPart of result.textStream) {
process.stdout.write(textPart);
}
// Or get the full result
const fullResult = await result;
console.log("\nTokens:", fullResult.usage);
Streaming in Next.js API Routes
// 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"),
system: "You are a helpful assistant.",
messages,
maxTokens: 1024,
});
return result.toDataStreamResponse();
}
useChat Hook (React)
// components/Chat.tsx
"use client";
import { useChat } from "@ai-sdk/react";
export function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading, error } =
useChat({
api: "/api/chat",
initialMessages: [],
onFinish: (message) => {
console.log("Completed:", message.content);
},
onError: (error) => {
console.error("Chat error:", error);
},
});
return (
<div>
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong> {m.content}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={handleInputChange}
placeholder="Say something..."
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
{error && <div>Error: {error.message}</div>}
</div>
);
}
Tool Calling
import { generateText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const result = await generateText({
model: openai("gpt-4o"),
prompt: "What's the weather in San Francisco?",
tools: {
getWeather: tool({
description: "Get the current weather for a location",
parameters: z.object({
city: z.string().describe("City name"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
execute: async ({ city, unit }) => {
// Your actual weather API call
const weather = await fetchWeather(city, unit);
return weather;
},
}),
},
maxSteps: 5, // Allow up to 5 tool call rounds
});
console.log(result.text); // Final text after tool execution
console.log(result.steps); // Detailed step-by-step execution log
Streaming Tool Calls with useChat
// app/api/chat/route.ts
import { streamText, tool } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages,
tools: {
searchDocs: tool({
description: "Search the documentation",
parameters: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
return await searchDocumentation(query);
},
}),
},
maxSteps: 3,
});
return result.toDataStreamResponse();
}
// Client side — tool invocations are available on messages
// components/Chat.tsx
"use client";
import { useChat } from "@ai-sdk/react";
export function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong> {m.content}
{m.toolInvocations?.map((ti) => (
<div key={ti.toolCallId}>
Tool: {ti.toolName} | State: {ti.state}
{ti.state === "result" && <pre>{JSON.stringify(ti.result)}</pre>}
</div>
))}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
</form>
</div>
);
}
Structured Output (generateObject)
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const { object } = await generateObject({
model: openai("gpt-4o"),
schema: z.object({
title: z.string(),
summary: z.string(),
tags: z.array(z.string()),
sentiment: z.enum(["positive", "negative", "neutral"]),
confidence: z.number().min(0).max(1),
}),
prompt: "Analyze this article: ...",
});
console.log(object.title); // Fully typed
console.log(object.tags); // string[]
Streaming Structured Output
import { streamObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const result = streamObject({
model: openai("gpt-4o"),
schema: z.object({
steps: z.array(
z.object({
title: z.string(),
description: z.string(),
})
),
}),
prompt: "Create a 5-step plan to learn Rust.",
});
for await (const partialObject of result.partialObjectStream) {
console.log(partialObject); // Partial typed object as it streams
}
Multi-Provider with Custom Configuration
import { generateText } from "ai";
import { createOpenAI } from "@ai-sdk/openai";
import { createAnthropic } from "@ai-sdk/anthropic";
// Custom provider instances
const groqProvider = createOpenAI({
apiKey: process.env.GROQ_API_KEY!,
baseURL: "https://api.groq.com/openai/v1",
});
const togetherProvider = createOpenAI({
apiKey: process.env.TOGETHER_API_KEY!,
baseURL: "https://api.together.xyz/v1",
});
// Use Groq for speed
const { text: fastText } = await generateText({
model: groqProvider("llama-3.3-70b-versatile"),
prompt: "Quick classification task...",
});
// Use Together for open-source models
const { text: openText } = await generateText({
model: togetherProvider("meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo"),
prompt: "Complex analysis task...",
});
Embeddings
import { embedMany, embed } from "ai";
import { openai } from "@ai-sdk/openai";
// Single embedding
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: "What is machine learning?",
});
// Batch embeddings
const { embeddings } = await embedMany({
model: openai.embedding("text-embedding-3-small"),
values: ["First document", "Second document", "Third document"],
});
Best Practices
- Use
streamText+toDataStreamResponse()for all chat endpoints — streaming is the default expected UX. - Define tools with Zod schemas — the SDK validates parameters and provides full TypeScript types.
- Set
maxStepswhen using tools to allow multi-turn tool execution loops. Without it, the model cannot call tools iteratively. - Use
generateObject/streamObjectfor structured extraction — it is more reliable than parsing JSON from text. - Use
@ai-sdk/react'suseChatfor chat UIs — it handles streaming, message management, loading states, and errors. - Create custom provider instances with
createOpenAI()to connect to any OpenAI-compatible API (Groq, Together, local models). - Use
onFinishcallbacks inuseChatfor analytics, logging, or triggering side effects after a response completes. - Handle
isLoadingstate in the UI to disable inputs and show indicators during generation.
Anti-Patterns
- Using
generateTextfor chat endpoints — always usestreamTextfor user-facing responses. Non-streaming creates unacceptable latency. - Not setting
maxStepswhen tools are defined — without it, the model can only call tools once and cannot react to results. - Building custom streaming parsing instead of using
useChat— the hook handles the AI SDK's streaming protocol automatically. - Mixing raw fetch calls with AI SDK hooks —
useChatexpects the specific data stream format fromtoDataStreamResponse(). - Not using Zod for tool parameters — raw JSON schemas lose type safety. The SDK's Zod integration gives you validated, typed arguments.
- Creating a new model instance per request — create the provider once at module scope and reuse it.
- Ignoring the
stepsarray in tool results — it contains the full execution trace including all tool calls and intermediate results. - Hardcoding a single provider — the SDK's main value is provider abstraction. Structure your code to make swapping easy.
Install this skill directly: skilldb add ai-llm-services-skills
Related Skills
Anthropic Claude API
"Anthropic Claude API: messages API, tool use, streaming, vision, system prompts, extended thinking, batches, Node SDK"
Fireworks AI
"Fireworks AI: fast inference, function calling, grammar mode, JSON output, OpenAI-compatible API, fine-tuning"
Google Gemini API
"Google Gemini API: generateContent, multimodal (images/video/audio), function calling, streaming, embeddings, context caching"
Groq
"Groq: ultra-fast inference, OpenAI-compatible API, Llama/Mixtral models, tool use, JSON mode, streaming"
OpenAI API
"OpenAI API: chat completions, function calling/tools, streaming, embeddings, vision, JSON mode, assistants, Node SDK"
Replicate
"Replicate: run open-source models, image generation (Flux/SDXL), predictions API, webhooks, streaming, Node SDK"