Build backend AI with Vercel AI SDK v6 stable. Covers Output API (replaces generateObject/streamObject), speech synthesis, transcription, embeddings, MCP tools. Includes v4→v5 migration and 12 error solutions. Use when: implementing AI SDK v5/v6, migrating versions, troubleshooting AI_APICallError, Workers startup issues, or Output API validation errors.
/plugin marketplace add jezweb/claude-skills/plugin install jezweb-tooling-skills@jezweb/claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
README.mdVERIFICATION_REPORT.mdreferences/links-to-official-docs.mdreferences/production-patterns.mdreferences/providers-quickstart.mdreferences/top-errors.mdreferences/v5-breaking-changes.mdrules/ai-sdk-core.mdscripts/check-versions.shtemplates/agent-with-tools.tstemplates/anthropic-setup.tstemplates/cloudflare-worker-integration.tstemplates/generate-object-zod.tstemplates/generate-text-basic.tstemplates/google-setup.tstemplates/multi-step-execution.tstemplates/nextjs-server-action.tstemplates/openai-setup.tstemplates/package.jsontemplates/stream-object-zod.tsBackend AI with Vercel AI SDK v5 and v6.
Installation:
npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google zod
Status: Stable Latest: ai@6.0.26 (Jan 2026)
⚠️ CRITICAL: generateObject() and streamObject() are DEPRECATED and will be removed in a future version. Use the new Output API instead.
Before (v5 - DEPRECATED):
// ❌ DEPRECATED - will be removed
import { generateObject } from 'ai';
const result = await generateObject({
model: openai('gpt-5'),
schema: z.object({ name: z.string(), age: z.number() }),
prompt: 'Generate a person',
});
After (v6 - USE THIS):
// ✅ NEW OUTPUT API
import { generateText, Output } from 'ai';
const result = await generateText({
model: openai('gpt-5'),
output: Output.object({ schema: z.object({ name: z.string(), age: z.number() }) }),
prompt: 'Generate a person',
});
// Access the typed object
console.log(result.object); // { name: "Alice", age: 30 }
import { generateText, Output } from 'ai';
// Object with Zod schema
output: Output.object({ schema: myZodSchema })
// Array of typed objects
output: Output.array({ schema: personSchema })
// Enum/choice from options
output: Output.choice({ choices: ['positive', 'negative', 'neutral'] })
// Plain text (explicit)
output: Output.text()
// Unstructured JSON (no schema validation)
output: Output.json()
import { streamText, Output } from 'ai';
const result = streamText({
model: openai('gpt-5'),
output: Output.object({ schema: personSchema }),
prompt: 'Generate a person',
});
// Stream partial objects
for await (const partialObject of result.objectStream) {
console.log(partialObject); // { name: "Ali..." } -> { name: "Alice", age: ... }
}
// Get final object
const finalObject = await result.object;
1. Agent Abstraction
Unified interface for building agents with ToolLoopAgent class:
2. Tool Execution Approval (Human-in-the-Loop)
tools: {
payment: tool({
needsApproval: true, // Always ask
// OR dynamic:
needsApproval: async ({ amount }) => amount > 1000,
inputSchema: z.object({ amount: z.number() }),
execute: async ({ amount }) => { /* process payment */ },
}),
}
3. Reranking for RAG
import { rerank } from 'ai';
const result = await rerank({
model: cohere.reranker('rerank-v3.5'),
query: 'user question',
documents: searchResults,
topK: 5,
});
4. MCP Tools (Model Context Protocol)
import { experimental_createMCPClient } from 'ai';
const mcpClient = await experimental_createMCPClient({
transport: { type: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem'] },
});
const tools = await mcpClient.tools();
const result = await generateText({
model: openai('gpt-5'),
tools,
prompt: 'List files in the current directory',
});
5. Language Model Middleware
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';
const wrappedModel = wrapLanguageModel({
model: anthropic('claude-sonnet-4-5-20250929'),
middleware: extractReasoningMiddleware({ tagName: 'think' }),
});
// Reasoning extracted automatically from <think>...</think> tags
6. Telemetry (OpenTelemetry)
const result = await generateText({
model: openai('gpt-5'),
prompt: 'Hello',
experimental_telemetry: {
isEnabled: true,
functionId: 'my-chat-function',
metadata: { userId: '123' },
recordInputs: true,
recordOutputs: true,
},
});
Official Docs: https://ai-sdk.dev/docs
GPT-5.2 (Dec 2025):
GPT-5.1 (Nov 2025):
GPT-5 (Aug 2025):
o3 Reasoning Models (Dec 2025):
import { openai } from '@ai-sdk/openai';
const gpt52 = openai('gpt-5.2');
const gpt51 = openai('gpt-5.1');
const gpt5 = openai('gpt-5');
const o3 = openai('o3');
const o3mini = openai('o3-mini');
Claude 4 Family (May-Oct 2025):
import { anthropic } from '@ai-sdk/anthropic';
const sonnet45 = anthropic('claude-sonnet-4-5-20250929'); // Latest
const opus41 = anthropic('claude-opus-4-1-20250805');
const haiku45 = anthropic('claude-haiku-4-5-20251015');
Gemini 2.5 Family (Mar-Sept 2025):
import { google } from '@ai-sdk/google';
const pro = google('gemini-2.5-pro');
const flash = google('gemini-2.5-flash');
const lite = google('gemini-2.5-flash-lite');
generateText() - Text completion with tools streamText() - Real-time streaming
Output.object() - Typed objects with Zod schema (replaces generateObject) Output.array() - Typed arrays Output.choice() - Enum selection Output.json() - Unstructured JSON
See "AI SDK 6" section above for usage examples.
import { experimental_generateSpeech as generateSpeech } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = await generateSpeech({
model: openai.speech('tts-1-hd'),
voice: 'alloy',
text: 'Hello, how can I help you today?',
});
// result.audio is an ArrayBuffer containing the audio
const audioBuffer = result.audio;
Supported Providers:
import { experimental_transcribe as transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = await transcribe({
model: openai.transcription('whisper-1'),
audio: audioFile, // File, Blob, ArrayBuffer, or URL
});
console.log(result.text); // Transcribed text
console.log(result.segments); // Timestamped segments
Supported Providers:
import { generateImage } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = await generateImage({
model: openai.image('dall-e-3'),
prompt: 'A futuristic city at sunset',
size: '1024x1024',
n: 1,
});
// result.images is an array of generated images
const imageUrl = result.images[0].url;
const imageBase64 = result.images[0].base64;
Supported Providers:
import { embed, embedMany, cosineSimilarity } from 'ai';
import { openai } from '@ai-sdk/openai';
// Single embedding
const result = await embed({
model: openai.embedding('text-embedding-3-small'),
value: 'Hello world',
});
console.log(result.embedding); // number[]
// Multiple embeddings (parallel processing)
const results = await embedMany({
model: openai.embedding('text-embedding-3-small'),
values: ['Hello', 'World', 'AI'],
maxParallelCalls: 5, // Parallel processing
});
// Compare similarity
const similarity = cosineSimilarity(
results.embeddings[0],
results.embeddings[1]
);
console.log(`Similarity: ${similarity}`); // 0.0 to 1.0
Supported Providers:
import { generateText } from 'ai';
import { google } from '@ai-sdk/google';
const result = await generateText({
model: google('gemini-2.5-pro'),
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'Summarize this document' },
{ type: 'file', data: pdfBuffer, mimeType: 'application/pdf' },
],
}],
});
// Or with images
const result = await generateText({
model: openai('gpt-5'),
messages: [{
role: 'user',
content: [
{ type: 'text', text: 'What is in this image?' },
{ type: 'image', image: imageBuffer },
],
}],
});
See official docs for full API: https://ai-sdk.dev/docs/ai-sdk-core
When returning streaming responses from an API, use the correct method:
| Method | Output Format | Use Case |
|---|---|---|
toTextStreamResponse() | Plain text chunks | Simple text streaming |
toUIMessageStreamResponse() | SSE with JSON events | Chat UIs (text-start, text-delta, text-end, finish) |
For chat widgets and UIs, always use toUIMessageStreamResponse():
const result = streamText({
model: workersai('@cf/qwen/qwen3-30b-a3b-fp8'),
messages,
system: 'You are helpful.',
});
// ✅ For chat UIs - returns SSE with JSON events
return result.toUIMessageStreamResponse({
headers: { 'Access-Control-Allow-Origin': '*' },
});
// ❌ For simple text - returns plain text chunks only
return result.toTextStreamResponse();
Note: toDataStreamResponse() does NOT exist in AI SDK v5 (common misconception).
IMPORTANT: workers-ai-provider@2.x requires AI SDK v5, NOT v4.
# ✅ Correct - AI SDK v5 with workers-ai-provider v2
npm install ai@^5.0.0 workers-ai-provider@^2.0.0 zod@^3.25.0
# ❌ Wrong - AI SDK v4 causes error
npm install ai@^4.0.0 workers-ai-provider@^2.0.0
# Error: "AI SDK 4 only supports models that implement specification version v1"
Zod Version: AI SDK v5 requires zod@^3.25.0 or later for zod/v3 and zod/v4 exports. Older versions (3.22.x) cause build errors: "Could not resolve zod/v4".
Problem: AI SDK v5 + Zod causes >270ms startup time (exceeds Workers 400ms limit).
Solution:
// ❌ BAD: Top-level imports cause startup overhead
import { createWorkersAI } from 'workers-ai-provider';
const workersai = createWorkersAI({ binding: env.AI });
// ✅ GOOD: Lazy initialization inside handler
app.post('/chat', async (c) => {
const { createWorkersAI } = await import('workers-ai-provider');
const workersai = createWorkersAI({ binding: c.env.AI });
// ...
});
Additional:
Breaking Changes:
parameters → inputSchema (Zod schema)args → input, result → outputToolExecutionError removed (now tool-error content parts)maxSteps parameter removed → Use stopWhen(stepCountIs(n))New in v5:
AI SDK v5 introduced extensive breaking changes. If migrating from v4, follow this guide.
Parameter Renames
maxTokens → maxOutputTokensproviderMetadata → providerOptionsTool Definitions
parameters → inputSchemaargs → input, result → outputMessage Types
CoreMessage → ModelMessageMessage → UIMessageconvertToCoreMessages → convertToModelMessagesTool Error Handling
ToolExecutionError class removedtool-error content partsMulti-Step Execution
maxSteps → stopWhenstepCountIs() or hasToolCall()Message Structure
content string → parts arrayStreaming Architecture
Tool Streaming
toolCallStreaming option removedPackage Reorganization
ai/rsc → @ai-sdk/rscai/react → @ai-sdk/reactLangChainAdapter → @ai-sdk/langchainBefore (v4):
import { generateText } from 'ai';
const result = await generateText({
model: openai.chat('gpt-4-turbo'),
maxTokens: 500,
providerMetadata: { openai: { user: 'user-123' } },
tools: {
weather: {
description: 'Get weather',
parameters: z.object({ location: z.string() }),
execute: async (args) => { /* args.location */ },
},
},
maxSteps: 5,
});
After (v5):
import { generateText, tool, stopWhen, stepCountIs } from 'ai';
const result = await generateText({
model: openai('gpt-4-turbo'),
maxOutputTokens: 500,
providerOptions: { openai: { user: 'user-123' } },
tools: {
weather: tool({
description: 'Get weather',
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => { /* input.location */ },
}),
},
stopWhen: stepCountIs(5),
});
maxTokens to maxOutputTokensproviderMetadata to providerOptionsparameters to inputSchemaargs → inputmaxSteps with stopWhen(stepCountIs(n))CoreMessage → ModelMessageToolExecutionError handlingai/rsc → @ai-sdk/rsc)AI SDK provides a migration tool:
npx ai migrate
This will update most breaking changes automatically. Review changes carefully.
Official Migration Guide: https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0
Cause: API request failed (network, auth, rate limit).
Solution:
import { AI_APICallError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt: 'Hello',
});
} catch (error) {
if (error instanceof AI_APICallError) {
console.error('API call failed:', error.message);
console.error('Status code:', error.statusCode);
console.error('Response:', error.responseBody);
// Check common causes
if (error.statusCode === 401) {
// Invalid API key
} else if (error.statusCode === 429) {
// Rate limit - implement backoff
} else if (error.statusCode >= 500) {
// Provider issue - retry
}
}
}
Prevention:
Cause: Model didn't generate valid object matching schema.
Solution:
import { AI_NoObjectGeneratedError } from 'ai';
try {
const result = await generateObject({
model: openai('gpt-4-turbo'),
schema: z.object({ /* complex schema */ }),
prompt: 'Generate data',
});
} catch (error) {
if (error instanceof AI_NoObjectGeneratedError) {
console.error('No valid object generated');
// Solutions:
// 1. Simplify schema
// 2. Add more context to prompt
// 3. Provide examples in prompt
// 4. Try different model (gpt-5 or claude-sonnet-4-5 for complex objects)
}
}
Prevention:
Cause: AI SDK v5 + Zod initialization overhead in Cloudflare Workers exceeds startup limits.
Solution:
// BAD: Top-level imports cause startup overhead
import { createWorkersAI } from 'workers-ai-provider';
import { complexSchema } from './schemas';
const workersai = createWorkersAI({ binding: env.AI });
// GOOD: Lazy initialization inside handler
export default {
async fetch(request, env) {
const { createWorkersAI } = await import('workers-ai-provider');
const workersai = createWorkersAI({ binding: env.AI });
// Use workersai here
}
}
Prevention:
GitHub Issue: Search for "Workers startup limit" in Vercel AI SDK issues
Cause: Stream errors can be swallowed by createDataStreamResponse.
Status: ✅ RESOLVED - Fixed in ai@4.1.22 (February 2025)
Solution (Recommended):
// Use the onError callback (added in v4.1.22)
const stream = streamText({
model: openai('gpt-4-turbo'),
prompt: 'Hello',
onError({ error }) {
console.error('Stream error:', error);
// Custom error logging and handling
},
});
// Stream safely
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
Alternative (Manual try-catch):
// Fallback if not using onError callback
try {
const stream = streamText({
model: openai('gpt-4-turbo'),
prompt: 'Hello',
});
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
} catch (error) {
console.error('Stream error:', error);
}
Prevention:
onError callback for proper error capture (recommended)GitHub Issue: #4726 (RESOLVED)
Cause: Missing or invalid API key.
Solution:
import { AI_LoadAPIKeyError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt: 'Hello',
});
} catch (error) {
if (error instanceof AI_LoadAPIKeyError) {
console.error('API key error:', error.message);
// Check:
// 1. .env file exists and loaded
// 2. Correct env variable name (OPENAI_API_KEY)
// 3. Key format is valid (starts with sk-)
}
}
Prevention:
Cause: Invalid parameters passed to function.
Solution:
import { AI_InvalidArgumentError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
maxOutputTokens: -1, // Invalid!
prompt: 'Hello',
});
} catch (error) {
if (error instanceof AI_InvalidArgumentError) {
console.error('Invalid argument:', error.message);
// Check parameter types and values
}
}
Prevention:
Cause: Model generated no content (safety filters, etc.).
Solution:
import { AI_NoContentGeneratedError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt: 'Some prompt',
});
} catch (error) {
if (error instanceof AI_NoContentGeneratedError) {
console.error('No content generated');
// Possible causes:
// 1. Safety filters blocked output
// 2. Prompt triggered content policy
// 3. Model configuration issue
// Handle gracefully:
return { text: 'Unable to generate response. Please try different input.' };
}
}
Prevention:
Cause: Zod schema validation failed on generated output.
Solution:
import { AI_TypeValidationError } from 'ai';
try {
const result = await generateObject({
model: openai('gpt-4-turbo'),
schema: z.object({
age: z.number().min(0).max(120), // Strict validation
}),
prompt: 'Generate person',
});
} catch (error) {
if (error instanceof AI_TypeValidationError) {
console.error('Validation failed:', error.message);
// Solutions:
// 1. Relax schema constraints
// 2. Add more guidance in prompt
// 3. Use .optional() for unreliable fields
}
}
Prevention:
.optional() for fields that may not always be presentCause: All retry attempts failed.
Solution:
import { AI_RetryError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt: 'Hello',
maxRetries: 3, // Default is 2
});
} catch (error) {
if (error instanceof AI_RetryError) {
console.error('All retries failed');
console.error('Last error:', error.lastError);
// Check root cause:
// - Persistent network issue
// - Provider outage
// - Invalid configuration
}
}
Prevention:
Cause: Exceeded provider rate limits (RPM/TPM).
Solution:
// Implement exponential backoff
async function generateWithBackoff(prompt: string, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await generateText({
model: openai('gpt-4-turbo'),
prompt,
});
} catch (error) {
if (error instanceof AI_APICallError && error.statusCode === 429) {
const delay = Math.pow(2, i) * 1000; // Exponential backoff
console.log(`Rate limited, waiting ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw new Error('Rate limit retries exhausted');
}
Prevention:
Cause: Complex Zod schemas slow down TypeScript type checking.
Solution:
// Instead of deeply nested schemas at top level:
// const complexSchema = z.object({ /* 100+ fields */ });
// Define inside functions or use type assertions:
function generateData() {
const schema = z.object({ /* complex schema */ });
return generateObject({ model: openai('gpt-4-turbo'), schema, prompt: '...' });
}
// Or use z.lazy() for recursive schemas:
type Category = { name: string; subcategories?: Category[] };
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(CategorySchema).optional(),
})
);
Prevention:
z.lazy() for recursive typesOfficial Docs: https://ai-sdk.dev/docs/troubleshooting/common-issues/slow-type-checking
Cause: Some models occasionally return invalid JSON.
Solution:
// Use built-in retry and mode selection
const result = await generateObject({
model: openai('gpt-4-turbo'),
schema: mySchema,
prompt: 'Generate data',
mode: 'json', // Force JSON mode (supported by GPT-4)
maxRetries: 3, // Retry on invalid JSON
});
// Or catch and retry manually:
try {
const result = await generateObject({
model: openai('gpt-4-turbo'),
schema: mySchema,
prompt: 'Generate data',
});
} catch (error) {
// Retry with different model
const result = await generateObject({
model: openai('gpt-4-turbo'),
schema: mySchema,
prompt: 'Generate data',
});
}
Prevention:
mode: 'json' when availableGitHub Issue: #4302 (Imagen 3.0 Invalid JSON)
More Errors: https://ai-sdk.dev/docs/reference/ai-sdk-errors (28 total)
AI SDK:
Latest Models (2026):
Check Latest:
npm view ai version
npm view ai dist-tags
Core:
Multi-Modal:
GitHub:
Last Updated: 2026-01-06 Skill Version: 2.0.1 AI SDK: 6.0.26 stable
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.