Search across all documentation pages
npm install ai @ai-sdk/openai @ai-sdk/anthropic// 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"),
messages,
});
return result.toDataStreamResponse();
}// app/chat/page.tsx
"use client";
import { useChat } from "@ai-sdk/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
When to reach for this: You need to integrate LLM chat, completions, or tool calling into a React or Next.js app with streaming support and minimal boilerplate.
// app/components/ChatWithTools.tsx
"use client";
import { useChat } from "@ai-sdk/react";
export default function ChatWithTools() {
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat({
api:
// app/api/chat-tools/route.ts
import { streamText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
export async function POST(req: Request) {
const { messages }
What this demonstrates:
useChat hookmaxStepsstreamText returns a StreamTextResult that converts to a Response via toDataStreamResponse(), sending Server-Sent Events to the clientuseChat manages the full conversation state: message history, input state, loading, error, and abort controllermaxSteps iterationsgenerateText is the non-streaming counterpart for batch processing or server-side generationUsing OpenRouter for model access:
npm install @openrouter/ai-sdk-providerimport { createOpenRouter } from "@openrouter/ai-sdk-provider";
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const result = streamText({
model: openrouter("anthropic/claude-sonnet-4-20250514"),
messages,
});Non-streaming generation:
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
const { text, usage } = await generateText({
model: openai("gpt-4o"),
prompt: "Summarize this article in 3 bullets.",
});Structured output with generateObject:
import { generateObject } from "ai";
import { z } from "zod";
const { object } = await generateObject({
model: openai("gpt-4o"),
schema: z.object({
recipe: z.string(),
useCompletion for single-turn prompts:
"use client";
import { useCompletion } from "@ai-sdk/react";
export default function Completion() {
const { completion, input, handleInputChange, handleSubmit } = useCompletion({
api: "/api/completion",
});
Message type from ai includes id, role, content, and optional toolInvocationsexecute functionsuseChat returns strongly typed messages: Message[]providerOptions fieldimport type { Message } from "ai";
// Tool result types are inferred from the Zod schema
const weatherTool = tool({
parameters: z.object({ city: z.string() }),
execute: async ({ city }) => {
// city is typed as string
return { temp:
Missing API keys — The SDK throws at runtime if the provider's API key env var is not set. Fix: Set OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. in .env.local. Each provider has its own expected env var name.
useChat not updating — If messages don't stream in, the API route may not be returning a data stream response. Fix: Always return result.toDataStreamResponse(), not result.text or a plain JSON response.
Tool calls not executing — Tools defined but maxSteps not set defaults to 1, so multi-step tool use won't work. Fix: Set maxSteps: 5 (or higher) to allow the model to process tool results and continue.
CORS errors in development — API routes must be in the same Next.js app. Fix: Use Next.js API routes (app/api/) rather than calling an external server directly from useChat.
Large bundle from provider imports — Importing multiple providers increases client bundle size. Fix: Provider imports are server-only; keep them in API routes or server actions, never in "use client" files.
| Library | Best For | Trade-off |
|---|---|---|
| Vercel AI SDK | Full-stack React/Next.js AI apps | Opinionated, tied to Vercel ecosystem conventions |
| LangChain.js | Complex chains, RAG, agents | Heavier abstraction, steeper learning curve |
| OpenAI SDK directly | Simple OpenAI-only usage | No streaming React hooks, single provider |
| Anthropic SDK directly | Simple Claude-only usage | No streaming React hooks, single provider |
| LlamaIndex.ts | Data indexing and retrieval | Focused on RAG, less on chat UI |
streamText return and how does it reach the client?streamText returns a StreamTextResult object.toDataStreamResponse() to convert it into a Response with Server-Sent EventsuseChat hook on the client consumes this stream and updates messages in real timeresult.text or plain JSON -- the hook expects the data stream protocolimport { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
// Just change the model line:
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
messages,
});The unified interface means only the model parameter changes.
maxSteps and why do my tool calls not execute without it?maxSteps controls how many tool-call-and-result rounds the model can performmaxSteps: 5 or higher for multi-step tool useimport { tool } from "ai";
import { z } from "zod";
const weatherTool = tool({
description: "Get weather for a city",
parameters: z.object({
city: z.string().describe("City name"),
useChat component?result.toDataStreamResponse(), not a JSON responseapi prop in useChat points to the correct routestreamText and generateText?streamText sends tokens to the client as they are generated (streaming)generateText waits for the full response and returns it at oncestreamText for chat UIs where you want real-time token displaygenerateText for batch processing or server-side generation where streaming is not neededimport { generateObject } from "ai";
import { z } from "zod";
const { object } = await generateObject({
model: openai("gpt-4o"),
schema: z.object({
title: z.string
useCompletion for single-turn prompts instead of multi-turn chat?useCompletion manages a single prompt/response cycle, not a conversationcompletion (the response text), input, and form handlersapi to a route that uses streamText with a prompt instead of messages@ai-sdk/openai, @ai-sdk/anthropic) are server-only"use client" file bundles them into the client JavaScriptuseChat and useCompletion are the only AI SDK imports safe for client componentsuseChat messages defined?useChat returns messages: Message[] where Message includes id, role, content, and optional toolInvocationsMessage from "ai" if you need to type props that accept messagesimport type { Message } from "ai";import { createOpenRouter } from "@openrouter/ai-sdk-provider";
const openrouter = createOpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const result = streamText({
model: openrouter("anthropic/claude-sonnet-4-20250514"),
messages,
});Tool parameter types are inferred from the Zod schema automatically.