AI SDK cookbook guides overview
The AI SDK provides use-case specific guides for building real applications. Available guides cover RAG Agent, Multi-Modal Agent, Slackbot Agent, Natural Language Postgres SQL Agent, Computer Use, Agent Skills, Agent Context Compaction, and getting started with various models including Gemini 2.5, Claude 4, Claude 3.7 Sonnet, Llama 3.1, GPT-5, OpenAI o1, OpenAI o3-mini, DeepSeek R1, and OpenAI Responses API.
Server route handler for text generation
Create a POST route handler at app/api/completion/route.ts. Extract the prompt from the request JSON. Call generateText with model, system, and prompt parameters. Return the result as JSON with the text property, making it accessible to the client.
Client-side generateText request pattern
When calling a generateText endpoint from a React client component marked with 'use client', use fetch with POST method. Send the prompt in the request body as JSON. Parse the response JSON to retrieve the generated text. Manage loading state separately to provide user feedback during generation.
generateText function for text generation
The generateText function from the ai module generates text based on an input prompt. It accepts an object with model, system, and prompt properties. The model property specifies which AI model to use (e.g., 'openai/gpt-4o'). The system property contains the system prompt that defines the assistant's behavior. The prompt property is the user's input prompt. The function returns an object with a text property containing the generated response.
Basic generateText implementation with Next.js
The following code shows a complete example of text generation with Next.js. The client component makes a POST request to /api/completion, passing a prompt in the request body. The server route handler receives the request, calls generateText with the prompt, and returns the generated text as JSON. Client-side state management tracks loading state and displays the generated text once available.
ModelMessage type structure
The ModelMessage type from the AI SDK represents a single message in a conversation. Each message has a role property (typically 'user' or 'assistant') and a content property that can be either a string or an array of content parts with type and text fields.
Chat completion with generateText API
Use the generateText function with a messages array to generate text based on a series of chat messages. The function accepts model, system, and messages parameters. Pass an array of ModelMessage objects to the messages parameter, where each message has a role ('user' or 'assistant') and content property.
Generate object example with Next.js
This example shows how to generate structured notifications data. The client component makes a POST request to /api/completion with a prompt. The server route handler uses generateText with Output.object and a Zod schema defining a notifications array with name (string), message (string), and minutesAgo (number) fields. The server returns the generated object as JSON.
Client code (app/page.tsx):
```tsx
'use client';
import { useState } from 'react';
export default function Page() {
const [generation, setGeneration] = useState();
const [isLoading, setIsLoading] = useState(false);
return (
<div>
<div
onClick={async () => {
setIsLoading(true);
await fetch('/api/completion', {
method: 'POST',
body: JSON.stringify({
prompt: 'Messages during finals week.',
}),
}).then(response => {
response.json().then(json => {
setGeneration(json.notifications);
setIsLoading(false);
});
});
}}
>
Generate
</div>
{isLoading ? (
'Loading...'
) : (
<pre>{JSON.stringify(generation, null, 2)}</pre>
)}
</div>
);
}
```
Server code (app/api/completion/route.ts):
```typescript
import { generateText, Output } from 'ai';
import { z } from 'zod';
export async function POST(req: Request) {
const { prompt }: { prompt: string } = await req.json();
const result = await generateText({
model: 'openai/gpt-4o',
system: 'You generate three notifications for a messages app.',
prompt,
output: Output.object({
schema: z.object({
notifications: z.array(
z.object({
name: z.string().describe('Name of a fictional person.'),
message: z.string().describe('Do not use emojis or links.'),
minutesAgo: z.number(),
}),
),
}),
}),
});
return Response.json(result.output);
}
```
Complete example: Multi-step tool calling with location and weather
Example implementation showing useChat on client with input handling, and /api/chat endpoint with getLocation and getWeather tools. Client: imports useChat, sets up DefaultChatTransport to '/api/chat', renders messages by switching on part.type (text, tool-getLocation, tool-getWeather). Server: defines tools with zod schemas, uses streamText with stopWhen isStepCount(5), converts messages and returns UI stream response. The getLocation tool returns coordinates, which the model can then use to call getWeather with the appropriate city parameter.
generateText basic usage for text generation
The generateText function generates text based on an input prompt. It takes a model identifier and a prompt string, then returns a result that can be logged or processed. This is the most basic LLM use case for generating a response to a question or summarizing text.
generateText code example with OpenAI
import { generateText } from 'ai';
const result = await generateText({
model: 'openai/gpt-4o',
prompt: 'Why is the sky blue?',
});
console.log(result);
generateText API parameters
The generateText function accepts an object with at least two properties: model (string, the provider/model identifier like 'openai/gpt-4o'), and prompt (string, the input text to generate a response from).
Render visual interfaces by streaming React components
Language models that can call tools can be used to render visual interfaces by streaming React components to the client. This allows dynamic UI generation based on tool calls.
generateText chat completion example
This example shows how to generate text from a conversation. It imports generateText from 'ai', calls it with model 'openai/gpt-4o', sets maxOutputTokens to 1024, provides a system prompt, and passes messages array with user and assistant messages. The result.text property contains the generated response.
generateText with chat messages
Use generateText() with a messages array to generate text based on a series of messages representing a conversation. Each message has a role (user or assistant) and content array with text objects.
Chat message structure
Each message object in the messages array has a role property (user or assistant) and a content array. The content array contains objects with type (e.g., 'text') and the actual content (e.g., text property for text type).
generateText parameters for chat
generateText accepts: model (required, string like 'openai/gpt-4o'), maxOutputTokens (number), system (optional system prompt string), and messages (array of message objects with role and content).
Generate structured objects with Output.object
The generateText function can generate structured data like JSON by providing an Output parameter. Use Output.object() to specify that you want structured output instead of unstructured text. This allows the AI model to generate data in a predictable, validated format.
Reasoning model structured output example
This example shows how to generate structured city prediction data using a reasoning model. First, call deepseek-r1 with a natural language prompt asking for predictions about the top 3 largest cities by 2050. Then use the raw text output from that call as input to gpt-4o-mini with a structured schema using Output.array() and Zod to extract city name, country, reason, and estimated population as numbers. The code demonstrates: (1) importing generateText and Output from 'ai', (2) using deepseek/deepseek-r1 model for reasoning, (3) passing the reasoning model's text output to a second generateText call, (4) using Output.array() with a z.object() schema that includes field descriptions, (5) logging the structured output.
```ts
import { generateText, Output } from 'ai';
import 'dotenv/config';
import { z } from 'zod';
async function main() {
const { text: rawOutput } = await generateText({
model: 'deepseek/deepseek-r1',
prompt:
'Predict the top 3 largest city by 2050. For each, return the name, the country, the reason why it will on the list, and the estimated population in millions.',
});
const { output } = await generateText({
model: 'openai/gpt-4o-mini',
prompt: 'Extract the desired information from this text: \n' + rawOutput,
output: Output.array({
element: z.object({
name: z.string().describe('the name of the city'),
country: z.string().describe('the name of the country'),
reason: z
.string()
.describe(
'the reason why the city will be one of the largest cities by 2050',
),
estimatedPopulation: z.number(),
}),
}),
});
console.log(output);
}
main().catch(console.error);
```
Output.array with Zod schema for structured data
Use Output.array() from the AI SDK with a Zod schema element to define structured output. Each field in the z.object() can have a .describe() method to provide field documentation. This allows extraction of multiple structured objects from model output, with field types enforced by Zod (e.g., z.string() for text fields, z.number() for numeric fields).
Reasoning models lack structured output support
DeepSeek R1 and OpenAI o1 reasoning models do not support tool-calling or structured outputs natively. These models are optimized for complex reasoning tasks but require a secondary extraction step using a different model to produce structured data.
Lightweight models for structured extraction
Models like gpt-4o-mini are suitable for structured data extraction tasks because they support both structured outputs and add minimal overhead in terms of speed and cost when used as a secondary extraction step after a reasoning model.
Two-model pipeline for reasoning model structured output
Reasoning models like DeepSeek R1 and OpenAI o1 do not support tool-calling or structured outputs. To generate structured data with a reasoning model, use a two-step pipeline: first call the reasoning model to generate a text response to a complex query, then pass that raw text output to a smaller model like gpt-4o-mini with a structured output schema to extract and format the data.
Intercept fetch requests with custom fetch function
Many providers support setting a custom `fetch` function using the `fetch` argument in their factory function. A custom `fetch` function can intercept and modify requests before they are sent to the provider's API, and intercept and modify responses before they are returned to the caller.
Use cases for fetch request interception
Common use cases for intercepting requests include: logging requests and responses, adding authentication headers, modifying request bodies, caching responses, and using a custom HTTP client.
Example: logging API requests with custom fetch
This example shows how to intercept fetch requests using a custom fetch function passed to createGateway(). The custom fetch function logs the URL, headers, and body of the API call before passing it to the actual fetch function.
```ts
import { generateText, createGateway } from 'ai';
const gateway = createGateway({
// example fetch wrapper that logs the input to the API call:
fetch: async (url, options) => {
console.log('URL', url);
console.log('Headers', JSON.stringify(options!.headers, null, 2));
console.log(
`Body ${JSON.stringify(JSON.parse(options!.body! as string), null, 2)}`,
);
return await fetch(url, options);
},
});
const { text } = await generateText({
model: gateway('openai/gpt-4o'),
prompt: 'Why is the sky blue?',
});
```
Repair malformed JSON from language models with jsonrepair
When language models produce malformed JSON output, use the jsonrepair library to automatically fix common issues. Install with 'pnpm add jsonrepair'. The pattern is to generate text with generateText and Output.text(), repair the text with jsonrepair(), then parse and validate with safeParseJSON and a Zod schema.
jsonrepair can fix common JSON issues
The jsonrepair library automatically fixes: missing closing brackets ('{"name": "test"' to '{"name": "test"}'), single quotes ('{"name": "test"}' to '{"name": "test"}'), missing quotes around keys ('{name: "test"}' to '{"name": "test"}'), trailing commas ('{"items": [1, 2, 3,]}' to '{"items": [1, 2, 3]}'), comments in JSON, and unescaped special characters.
Example: repair JSON from generateText with jsonrepair
import { generateText, Output } from 'ai';
import { safeParseJSON } from '@ai-sdk/provider-utils';
import { jsonrepair } from 'jsonrepair';
import { z } from 'zod';
const recipeSchema = z.object({
recipe: z.object({
name: z.string(),
ingredients: z.array(
z.object({
name: z.string(),
amount: z.string(),
}),
),
steps: z.array(z.string()),
}),
});
const result = await generateText({
model: __MODEL__,
output: Output.text(),
prompt: 'Generate a lasagna recipe.',
});
const repairedText = jsonrepair(result.text);
const parseResult = safeParseJSON({ text: repairedText });
if (!parseResult.success) {
throw parseResult.error;
}
const output = recipeSchema.parse(parseResult.value);
console.log(output.recipe);
jsonrepair is best-effort, schema validation still required
Even after repair with jsonrepair, the repaired object must still validate against your schema. jsonrepair cannot fix semantically incorrect data that happens to be valid JSON, and cannot fix structurally wrong data. For severely truncated responses, consider increasing maxOutputTokens or simplifying your schema.
Common reasons for malformed JSON from language models
Language models produce malformed JSON for several reasons: truncated responses from hitting token limits, syntax errors like using single quotes instead of double quotes, missing closing brackets or braces, and trailing commas.
isAnthropicModel detection logic
Detect Anthropic models by checking if the model parameter (string or object) includes 'anthropic' or 'claude' in its provider name or modelId. If model is a string, check for 'anthropic' or 'claude' substrings. If model is an object, check model.provider and model.modelId for 'anthropic' or 'claude' substrings. This allows provider-agnostic code to safely detect Anthropic models.
Message-level vs block-level cache control translation
The AI SDK automatically translates message-level providerOptions to block-level cache_control directives in the API request. When you set providerOptions on a message, the SDK applies it to the last content block when constructing the API request to Anthropic. This allows message-level cache control specification while Anthropic's API expects block-level cache_control.
addCacheControlToMessages utility function
Function that adds Anthropic cache control to messages. Takes an object with messages array, model (LanguageModel), and optional providerOptions. Returns messages with cache control applied only to the final message in the array if the model is Anthropic. For non-Anthropic models, returns messages unchanged. Signature: addCacheControlToMessages({ messages: ModelMessage[], model: LanguageModel, providerOptions?: Record<string, Record<string, JSONValue>> }): ModelMessage[]
Dynamic prompt caching with Anthropic via cacheControl
Prompt caching reduces API costs for repeated context in multi-turn conversations. Anthropic supports caching via the cacheControl directive. Mark the final message with providerOptions containing anthropic.cacheControl set to { type: 'ephemeral' } to cache conversation prefixes. This tells Anthropic to cache everything up to that point so subsequent requests only pay full price for new content.
addCacheControlToMessages implementation
```ts
import type { ModelMessage, JSONValue, LanguageModel } from 'ai';
function isAnthropicModel(model: LanguageModel): boolean {
if (typeof model === 'string') {
return model.includes('anthropic') || model.includes('claude');
}
return (
model.provider === 'anthropic' ||
model.provider.includes('anthropic') ||
model.modelId.includes('anthropic') ||
model.modelId.includes('claude')
);
}
export function addCacheControlToMessages({
messages,
model,
providerOptions = {
anthropic: { cacheControl: { type: 'ephemeral' } },
},
}: {
messages: ModelMessage[];
model: LanguageModel;
providerOptions?: Record<string, Record<string, JSONValue>>;
}): ModelMessage[] {
if (messages.length === 0) return messages;
if (!isAnthropicModel(model)) return messages;
return messages.map((message, index) => {
if (index === messages.length - 1) {
return {
...message,
providerOptions: {
...message.providerOptions,
...providerOptions,
},
};
}
return message;
});
}
```
Using cache control with generateText and prepareStep
Integrate the addCacheControlToMessages utility into your agent using the prepareStep callback with generateText. In the prepareStep function, call addCacheControlToMessages with the messages and model parameters, returning an object with the modified messages. This applies cache control directives to messages before they are sent to the model.
generateText with dynamic prompt caching example
```ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText, tool, isStepCount } from 'ai';
import { z } from 'zod';
import { addCacheControlToMessages } from './add-cache-control-to-messages';
async function main() {
const result = await generateText({
model: anthropic('claude-sonnet-4-5'),
prompt: 'Help me analyze this codebase and suggest improvements.',
stopWhen: isStepCount(10),
tools: {
analyzeFile: tool({
description: 'Analyze a file in the codebase',
inputSchema: z.object({
path: z.string().describe('Path to the file'),
}),
execute: async ({ path }) => {
return { analysis: `Analysis of ${path}` };
},
}),
},
prepareStep: ({ messages, model }) => ({
messages: addCacheControlToMessages({ messages, model }),
}),
});
console.log(result.text);
}
main().catch(console.error);
```
Anthropic prompt caching minimum token threshold
Anthropic requires a minimum number of tokens before caching activates. Short conversations may not benefit from caching. Check Anthropic's documentation for the exact minimum token threshold.
Anthropic ephemeral cache TTL is 5 minutes
Anthropic's ephemeral cache has a 5-minute time-to-live (TTL). Inactive conversations lose their cache after 5 minutes. Other providers may have different cache duration requirements.
Anthropic prompt caching cost structure
With Anthropic, cached tokens cost 10% of the input token price. However, cache writes cost 25% more than regular input tokens. You save money when cache hits exceed cache misses. Cost structures vary significantly by provider.
Prompt caching use cases
Prompt caching is particularly useful when building agents with long conversations where multi-turn agent interactions accumulate context that gets resent with every request, and when using tools heavily since tool calls and results add significant token overhead that benefits from caching.
generateText with model and prompt parameters
The generateText function requires two parameters: model (a string identifier like 'openai/gpt-5.4') and prompt (a string containing the input prompt).
generateText basic usage with RSC
The generateText function from the ai module generates text based on a prompt. It is called on the server side with a model identifier and prompt string. It returns an object containing text, finishReason, and usage properties. Example: await generateText({ model: 'openai/gpt-5.4', prompt: question }) returns { text, finishReason, usage }.
Client-side text generation with getAnswer server action
A client component marked with 'use client' can call a server action getAnswer that wraps generateText. The client calls the server action asynchronously with a question string and receives the generated text to update state. The component exports maxDuration = 30 to allow streaming responses up to 30 seconds.
generateText return values
The generateText function returns an object with three properties: text (the generated response string), finishReason (the reason generation stopped), and usage (token usage information).
React Server Components text generation pattern
When using React Server Components, implement text generation by creating a server action (marked with 'use server') that calls generateText, and call this server action from a client component (marked with 'use client') in response to user interactions.
Chat completion with series of messages
A chat completion generates text based on a series of messages representing interactions between any number of systems. The most common use case is a conversation between a user and a model.
RSC chat generation server action example
This example shows a server action that receives a message history, calls generateText with a system prompt and messages array, and returns the updated conversation including the model's response.
```typescript
'use server';
import { generateText } from 'ai';
export interface Message {
role: 'user' | 'assistant';
content: string;
}
export async function continueConversation(history: Message[]) {
'use server';
const { text } = await generateText({
model: 'openai/gpt-5.4',
system: 'You are a friendly assistant!',
messages: history,
});
return {
messages: [
...history,
{
role: 'assistant' as const,
content: text,
},
],
};
}
```
generateText with chat messages parameter
The generateText function accepts a messages parameter to generate text based on a series of messages. Combine messages with system prompt and model selection to continue a conversation.
RSC chat generation client example
This example shows a React component that maintains conversation state with useState, accepts user input, and calls a server action to continue the conversation. The maxDuration export is set to 30 seconds to allow streaming responses.
```tsx
'use client';
import { useState } from 'react';
import { Message, continueConversation } from './actions';
export const maxDuration = 30;
export default function Home() {
const [conversation, setConversation] = useState<Message[]>([]);
const [input, setInput] = useState<string>('');
return (
<div>
<div>
{conversation.map((message, index) => (
<div key={index}>
{message.role}: {message.content}
</div>
))}
</div>
<div>
<input
type="text"
value={input}
onChange={event => {
setInput(event.target.value);
}}
/>
<button
onClick={async () => {
const { messages } = await continueConversation([
...conversation,
{ role: 'user', content: input },
]);
setConversation(messages);
}}
>
Send Message
</button>
</div>
</div>
);
}
```
RSC tool calling client component example
This example shows a client component using 'use client' that maintains conversation state with useState. It calls a server action continueConversation with the current conversation history plus the new user message, then updates the conversation state with the returned messages.
RSC tool calling server action example
This example shows a server action 'use server' that uses generateText with messages history, a system prompt, and tools object. The celsiusToFahrenheit tool is defined with description, inputSchema using Zod, and execute async function. The action returns messages array combining history with the assistant's response, using either text or toolResults.
Transform ServerMessages to ClientMessages in onGetUIState
Use the onGetUIState callback in the AI provider configuration to transform stored ServerMessage[] objects into ClientMessage[] for display. Call getAIState() to retrieve the current message history, then map over it to create client messages with generated IDs and rendered display content. For function-type messages, render them as React components (e.g., Stock component); for other roles, use the content string directly.
Restore conversation history from database with initialAIState
To restore previous conversations from a database, fetch the stored messages and pass them to the AI provider using the initialAIState prop. In the RootLayout component, call getSavedMessages() to retrieve ServerMessage[] from your database, then pass this array to the AI component's initialAIState prop. This allows users to continue conversations or review past interactions.
ClientMessage interface definition
ClientMessage is an interface with four properties: id (type string, required), role (type 'user' | 'assistant' | 'function', required), and display (type ReactNode, required). This interface represents the client-side message format used for rendering UI components.
Example: Restore and display messages from database
// app/layout.tsx
import { ServerMessage } from './actions';
import { AI } from './ai';
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const savedMessages: ServerMessage[] = getSavedMessages();
return (
<html lang="en">
<body>
<AI initialAIState={savedMessages} initialUIState={[]}>
{children}
</AI>
</body>
</html>
);
}
// app/ai.ts
import { createAI } from '@ai-sdk/rsc';
import { ServerMessage, ClientMessage, continueConversation } from './actions';
import { Stock } from '@ai-studio/components/stock';
import { generateId } from 'ai';
export const AI = createAI<ServerMessage[], ClientMessage[]>({
actions: {
continueConversation,
},
onGetUIState: async () => {
'use server';
const history: ServerMessage[] = getAIState();
return history.map(({ role, content }) => ({
id: generateId(),
role,
display:
role === 'function' ? <Stock {...JSON.parse(content)} /> : content,
}));
},
});
// app/actions.tsx
export async function getSavedMessages(): Promise<ServerMessage[]> {
'use server';
return await fetchMessagesFromDatabase();
}
This example shows how to fetch messages from a database, pass them as initial state to the AI provider, and transform them for client-side rendering.
Update UI state when sending and receiving messages
When a user sends a message, immediately update the conversation UI state by calling setConversation with a new array that includes the user's message with a generated ID. After receiving an AI response from a server action, add the response message to the conversation state using setConversation again. This keeps the UI synchronized with both user input and AI responses.
AI SDK core functions
The main core APIs provided by the AI SDK are generateText, streamText, generateObject, streamObject for text and structured output; embed and embedMany for embeddings; generateImage for image generation; and tool for defining tools. These are imported from the 'ai' package.
AI SDK example organization
Examples are placed under examples/ai-functions/src/<function>/<provider>/ with basic.ts as the provider entry example file. All other examples in the same provider folder use descriptive kebab-case file names. Do not create flat top-level provider files like src/stream-text/openai.ts.