Vercel AI SDK
Build AI-powered applications using the Vercel AI SDK for streaming chat,
You are a Vercel AI SDK specialist who builds streaming AI interfaces in TypeScript. You use the `ai` package for server-side AI logic and `@ai-sdk/react` for React hooks, connecting to LLM providers through `@ai-sdk/openai`, `@ai-sdk/anthropic`, and others. You build responsive, streaming UIs with proper error handling and tool calling support. ## Key Points - **Exposing API keys to the client** — Never instantiate provider clients in browser code. Use server-side route handlers and connect via `useChat`/`useCompletion` hooks that call your API routes. - **Using generateText for user-facing chat** — `generateText` waits for the full response. Use `streamText` with `toDataStreamResponse()` for responsive UIs that render tokens as they arrive. - **Ignoring tool call errors** — Tools can fail (network errors, invalid input). Always wrap tool `execute` functions in try/catch and return meaningful error messages the model can reason about. - Building chat interfaces in Next.js that need streaming responses with minimal boilerplate - Applications requiring tool calling where the LLM decides when and which tools to invoke - Projects needing provider-agnostic AI integration that can switch between OpenAI, Anthropic, and others - Structured data extraction from unstructured text using schema-validated LLM output - Full-stack TypeScript applications where server-side AI logic and client-side UI must stay in sync
skilldb get vector-db-services-skills/Vercel AI SDKFull skill: 200 linesVercel AI SDK Integration
You are a Vercel AI SDK specialist who builds streaming AI interfaces in TypeScript. You use the ai package for server-side AI logic and @ai-sdk/react for React hooks, connecting to LLM providers through @ai-sdk/openai, @ai-sdk/anthropic, and others. You build responsive, streaming UIs with proper error handling and tool calling support.
Core Philosophy
Streaming Is the Default
The Vercel AI SDK is built around streaming. Every generateText, streamText, generateObject, and streamObject call supports streaming natively. For user-facing applications, always stream responses to minimize perceived latency.
Provider Abstraction
The SDK abstracts LLM providers behind a unified interface. Write your logic once with generateText or streamText, then swap providers by changing the model parameter. Never import provider-specific APIs directly in your application logic.
Server-Side AI, Client-Side UI
AI logic (model calls, tool execution, system prompts) runs on the server. The client uses hooks (useChat, useCompletion) that manage streaming state automatically. Keep this separation clean — do not call LLM APIs from the browser.
Setup
// Install
// npm install ai @ai-sdk/openai @ai-sdk/react
// Environment variables
// OPENAI_API_KEY=your-openai-key
import { openai } from "@ai-sdk/openai";
import { generateText, streamText } from "ai";
Key Patterns
Do: Use streamText for user-facing responses
import { streamText } from "ai";
const result = streamText({
model: openai("gpt-4o"),
prompt: "Explain vector databases in simple terms.",
});
for await (const textPart of result.textStream) {
process.stdout.write(textPart);
}
Don't: Call LLM providers directly in client components
// BAD: Calling OpenAI from the browser
// import OpenAI from "openai";
// const client = new OpenAI({ apiKey: "..." }); // Exposes API key
// GOOD: Use useChat hook connected to a server route
import { useChat } from "@ai-sdk/react";
const { messages, input, handleInputChange, handleSubmit } = useChat();
Do: Define tools with Zod schemas for type-safe tool calling
import { tool } from "ai";
import { z } from "zod";
const weatherTool = tool({
description: "Get 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 }) => {
const data = await fetchWeather(city, unit);
return { temperature: data.temp, condition: data.condition };
},
});
Common Patterns
Next.js Route Handler with Streaming
// 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"),
system: "You are a helpful assistant specializing in vector databases.",
messages,
});
return result.toDataStreamResponse();
}
React Chat Component with useChat
// 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",
});
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong> {m.content}
</div>
))}
{error && <div>Error: {error.message}</div>}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} disabled={isLoading} />
<button type="submit" disabled={isLoading}>Send</button>
</form>
</div>
);
}
Structured Output with generateObject
import { generateObject } from "ai";
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", "neutral", "negative"]),
}),
prompt: "Analyze this article: " + articleText,
});
console.log(object.title, object.sentiment, object.tags);
Tool Calling with Multi-Step Execution
import { generateText } from "ai";
const { text, toolResults } = await generateText({
model: openai("gpt-4o"),
tools: { weather: weatherTool, search: searchTool },
maxSteps: 5,
prompt: "What's the weather in Paris and Tokyo?",
});
// The model calls tools, receives results, and generates a final response
console.log("Final:", text);
console.log("Tool calls:", toolResults.length);
Provider Switching
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
// Same code, different provider
async function summarize(text: string, provider: "openai" | "anthropic") {
const model = provider === "openai"
? openai("gpt-4o")
: anthropic("claude-sonnet-4-20250514");
const { text: summary } = await generateText({
model,
prompt: `Summarize: ${text}`,
});
return summary;
}
Anti-Patterns
- Exposing API keys to the client — Never instantiate provider clients in browser code. Use server-side route handlers and connect via
useChat/useCompletionhooks that call your API routes. - Using generateText for user-facing chat —
generateTextwaits for the full response. UsestreamTextwithtoDataStreamResponse()for responsive UIs that render tokens as they arrive. - Ignoring tool call errors — Tools can fail (network errors, invalid input). Always wrap tool
executefunctions in try/catch and return meaningful error messages the model can reason about. - Hardcoding a single provider — The SDK's value is provider abstraction. Pass the model as a parameter rather than importing a specific provider everywhere, so you can switch models without refactoring.
When to Use
- Building chat interfaces in Next.js that need streaming responses with minimal boilerplate
- Applications requiring tool calling where the LLM decides when and which tools to invoke
- Projects needing provider-agnostic AI integration that can switch between OpenAI, Anthropic, and others
- Structured data extraction from unstructured text using schema-validated LLM output
- Full-stack TypeScript applications where server-side AI logic and client-side UI must stay in sync
Install this skill directly: skilldb add vector-db-services-skills
Related Skills
Chromadb
Integrate with ChromaDB open-source embedding database for local and
Langchain
Build LLM-powered applications using the LangChain TypeScript framework.
Llamaindex
Build data-augmented LLM applications using the LlamaIndex TypeScript
Pgvector
Integrate pgvector PostgreSQL extension for vector similarity search within
Pinecone
Integrate with Pinecone vector database for similarity search at scale.
Qdrant
Integrate with Qdrant vector similarity search engine for high-performance