ai-sdk-core by jezweb/claude-skills
npx skills add https://github.com/jezweb/claude-skills --skill ai-sdk-core使用 Vercel AI SDK v5 和 v6 的后端 AI。
安装:
npm install ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google zod
状态: 稳定 最新版本: ai@6.0.26 (2026年1月)
⚠️ 重要提示:generateObject() 和 streamObject() 已弃用,并将在未来版本中移除。请使用新的 Output API。
之前 (v5 - 已弃用):
// ❌ 已弃用 - 将被移除
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',
});
之后 (v6 - 请使用此方式):
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
// ✅ 新的 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',
});
// 访问类型化的对象
console.log(result.object); // { name: "Alice", age: 30 }
import { generateText, Output } from 'ai';
// 带有 Zod 模式的对象
output: Output.object({ schema: myZodSchema })
// 类型化对象数组
output: Output.array({ schema: personSchema })
// 从选项中选择枚举/选项
output: Output.choice({ choices: ['positive', 'negative', 'neutral'] })
// 纯文本 (显式指定)
output: Output.text()
// 非结构化 JSON (无模式验证)
output: Output.json()
import { streamText, Output } from 'ai';
const result = streamText({
model: openai('gpt-5'),
output: Output.object({ schema: personSchema }),
prompt: 'Generate a person',
});
// 流式传输部分对象
for await (const partialObject of result.objectStream) {
console.log(partialObject); // { name: "Ali..." } -> { name: "Alice", age: ... }
}
// 获取最终对象
const finalObject = await result.object;
1. 智能体抽象 使用 ToolLoopAgent 类构建智能体的统一接口:
2. 工具执行审批 (人在回路)
使用选择性审批以获得更好的用户体验。并非每次工具调用都需要审批。
tools: {
payment: tool({
// 基于输入的动态审批
needsApproval: async ({ amount }) => amount > 1000,
inputSchema: z.object({ amount: z.number() }),
execute: async ({ amount }) => { /* process payment */ },
}),
readFile: tool({
needsApproval: false, // 安全操作不需要审批
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => fs.readFile(path),
}),
deleteFile: tool({
needsApproval: true, // 破坏性操作始终需要审批
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => fs.unlink(path),
}),
}
最佳实践:
来源:
3. RAG 重排序
import { rerank } from 'ai';
const result = await rerank({
model: cohere.reranker('rerank-v3.5'),
query: '用户问题',
documents: searchResults,
topK: 5,
});
4. MCP 工具 (模型上下文协议)
⚠️ 安全警告:MCP 工具在生产环境中有重大风险。请参阅下面的安全部分。
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: '列出当前目录中的文件',
});
已知问题:MCP 工具可能在流模式下无法执行 (Vercel 社区讨论)。对于 MCP 工具,请使用 generateText() 而不是 streamText()。
MCP 安全注意事项
⚠️ 重要提示:生产环境中的动态 MCP 工具存在安全风险:
风险:
解决方案 - 使用静态工具生成:
// ❌ 有风险:动态工具在你控制之外变化
const mcpClient = await experimental_createMCPClient({ /* ... */ });
const tools = await mcpClient.tools(); // 随时可能改变!
// ✅ 安全:生成静态的、版本化的工具定义
// 步骤 1: 安装 mcp-to-ai-sdk
npm install -g mcp-to-ai-sdk
// 步骤 2: 生成静态工具 (一次性,版本控制)
npx mcp-to-ai-sdk generate stdio 'npx -y @modelcontextprotocol/server-filesystem'
// 步骤 3: 导入静态工具
import { tools } from './generated-mcp-tools';
const result = await generateText({
model: openai('gpt-5'),
tools, // 静态的、经过审查的、版本化的
prompt: '使用工具',
});
最佳实践:生成静态工具,审查它们,提交到版本控制,并且只在有意时更新。
5. 语言模型中间件
import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';
const wrappedModel = wrapLanguageModel({
model: anthropic('claude-sonnet-4-5-20250929'),
middleware: extractReasoningMiddleware({ tagName: 'think' }),
});
// 自动从 <think>...</think> 标签中提取推理过程
6. 遥测 (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,
},
});
官方文档: https://ai-sdk.dev/docs
GPT-5.2 (2025年12月):
GPT-5.1 (2025年11月):
GPT-5 (2025年8月):
o3 推理模型 (2025年12月):
o3, o3-pro, o3-mini - 高级推理
o4-mini - 快速推理
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 系列 (2025年5月-10月):
Opus 4 (5月22日):最适合复杂推理,每百万令牌 $15/$75
Sonnet 4 (5月22日):平衡的性能,每百万令牌 $3/$15
Opus 4.1 (8月5日):增强的智能体任务,真实世界编码
Sonnet 4.5 (9月29日):最适合编码、智能体、计算机使用
Haiku 4.5 (10月15日):小巧、快速、低延迟模型
import { anthropic } from '@ai-sdk/anthropic'; const sonnet45 = anthropic('claude-sonnet-4-5-20250929'); // 最新 const opus41 = anthropic('claude-opus-4-1-20250805'); const haiku45 = anthropic('claude-haiku-4-5-20251015');
Gemini 2.5 系列 (2025年3月-9月):
Pro (2025年3月):最智能,发布时在 LMArena 排名第一
Pro Deep Think (2025年5月):增强的推理模式
Flash (2025年5月):快速、经济高效
Flash-Lite (2025年9月):更新的效率
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() - 带有工具的文本补全 streamText() - 实时流式处理
Output.object() - 带有 Zod 模式的类型化对象 (替代 generateObject) Output.array() - 类型化数组 Output.choice() - 枚举选择 Output.json() - 非结构化 JSON
使用示例请参阅上面的 "AI SDK 6" 部分。
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: '你好,我今天能为你提供什么帮助?',
});
// result.audio 是包含音频的 ArrayBuffer
const audioBuffer = result.audio;
支持的提供商:
import { experimental_transcribe as transcribe } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = await transcribe({
model: openai.transcription('whisper-1'),
audio: audioFile, // 文件、Blob、ArrayBuffer 或 URL
});
console.log(result.text); // 转录的文本
console.log(result.segments); // 带时间戳的片段
支持的提供商:
import { generateImage } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = await generateImage({
model: openai.image('dall-e-3'),
prompt: '日落时的未来城市',
size: '1024x1024',
n: 1,
});
// result.images 是生成的图像数组
const imageUrl = result.images[0].url;
const imageBase64 = result.images[0].base64;
支持的提供商:
import { embed, embedMany, cosineSimilarity } from 'ai';
import { openai } from '@ai-sdk/openai';
// 单个嵌入
const result = await embed({
model: openai.embedding('text-embedding-3-small'),
value: 'Hello world',
});
console.log(result.embedding); // number[]
// 多个嵌入 (并行处理)
const results = await embedMany({
model: openai.embedding('text-embedding-3-small'),
values: ['Hello', 'World', 'AI'],
maxParallelCalls: 5, // 并行处理
});
// 比较相似度
const similarity = cosineSimilarity(
results.embeddings[0],
results.embeddings[1]
);
console.log(`相似度:${similarity}`); // 0.0 到 1.0
支持的提供商:
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: '总结这个文档' },
{ type: 'file', data: pdfBuffer, mimeType: 'application/pdf' },
],
}],
});
// 或者使用图像
const result = await generateText({
model: openai('gpt-5'),
messages: [{
role: 'user',
content: [
{ type: 'text', text: '这张图片里有什么?' },
{ type: 'image', image: imageBuffer },
],
}],
});
完整 API 请参阅官方文档:https://ai-sdk.dev/docs/ai-sdk-core
从 API 返回流式响应时,请使用正确的方法:
| 方法 | 输出格式 | 使用场景 |
|---|---|---|
toTextStreamResponse() | 纯文本块 | 简单的文本流式处理 |
toUIMessageStreamResponse() | 带有 JSON 事件的 SSE | 聊天 UI (text-start, text-delta, text-end, finish) |
对于聊天小部件和 UI,始终使用 toUIMessageStreamResponse():
const result = streamText({
model: workersai('@cf/qwen/qwen3-30b-a3b-fp8'),
messages,
system: '你是乐于助人的。',
});
// ✅ 对于聊天 UI - 返回带有 JSON 事件的 SSE
return result.toUIMessageStreamResponse({
headers: { 'Access-Control-Allow-Origin': '*' },
});
// ❌ 对于简单文本 - 仅返回纯文本块
return result.toTextStreamResponse();
注意: toDataStreamResponse() 在 AI SDK v5 中不存在 (常见误解)。
重要提示: workers-ai-provider@2.x 需要 AI SDK v5,而不是 v4。
# ✅ 正确 - AI SDK v5 与 workers-ai-provider v2
npm install ai@^5.0.0 workers-ai-provider@^2.0.0 zod@^3.25.0
# ❌ 错误 - AI SDK v4 会导致错误
npm install ai@^4.0.0 workers-ai-provider@^2.0.0
# 错误:"AI SDK 4 only supports models that implement specification version v1"
Zod 版本: AI SDK v5 需要 zod@^3.25.0 或更高版本以支持 zod/v3 和 zod/v4 导出。旧版本 (3.22.x) 会导致构建错误:"Could not resolve zod/v4"。
问题: AI SDK v5 + Zod 导致 >270ms 的启动时间 (超过 Workers 400ms 限制)。
解决方案:
// ❌ 不好:顶层导入导致启动开销
import { createWorkersAI } from 'workers-ai-provider';
const workersai = createWorkersAI({ binding: env.AI });
// ✅ 好:在处理器内部延迟初始化
app.post('/chat', async (c) => {
const { createWorkersAI } = await import('workers-ai-provider');
const workersai = createWorkersAI({ binding: c.env.AI });
// ...
});
额外建议:
重大变更:
parameters → inputSchema (Zod 模式)args → input, result → outputToolExecutionError 已移除 (现在是 tool-error 内容部分)maxSteps 参数已移除 → 使用 stopWhen(stepCountIs(n))v5 新增功能:
AI SDK v5 引入了广泛的重大变更。如果从 v4 迁移,请遵循本指南。
参数重命名
maxTokens → maxOutputTokensproviderMetadata → providerOptions工具定义
parameters → inputSchemaargs → input, result → output消息类型
CoreMessage → ModelMessageMessage → UIMessageconvertToCoreMessages → convertToModelMessages工具错误处理
ToolExecutionError 类已移除tool-error 内容部分多步执行
maxSteps → stopWhenstepCountIs() 或 hasToolCall()消息结构
content 字符串 → parts 数组流式架构
工具流式处理
toolCallStreaming 选项已移除包重组
ai/rsc → @ai-sdk/rscai/react → @ai-sdk/reactLangChainAdapter → @ai-sdk/langchain之前 (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: '获取天气',
parameters: z.object({ location: z.string() }),
execute: async (args) => { /* args.location */ },
},
},
maxSteps: 5,
});
之后 (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: '获取天气',
inputSchema: z.object({ location: z.string() }),
execute: async ({ location }) => { /* input.location */ },
}),
},
stopWhen: stepCountIs(5),
});
maxTokens 更新为 maxOutputTokensproviderMetadata 更新为 providerOptionsparameters 转换为 inputSchemaargs → inputmaxSteps 替换为 stopWhen(stepCountIs(n))CoreMessage → ModelMessageToolExecutionError 处理ai/rsc → @ai-sdk/rsc)AI SDK 提供了迁移工具:
npx ai migrate
这将自动更新大多数重大变更。请仔细审查更改。
原因: API 请求失败 (网络、认证、速率限制)。
解决方案:
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 调用失败:', error.message);
console.error('状态码:', error.statusCode);
console.error('响应:', error.responseBody);
// 检查常见原因
if (error.statusCode === 401) {
// 无效的 API 密钥
} else if (error.statusCode === 429) {
// 速率限制 - 实现退避
} else if (error.statusCode >= 500) {
// 提供商问题 - 重试
}
}
}
预防措施:
原因: 模型没有生成与模式匹配的有效对象。
解决方案:
import { AI_NoObjectGeneratedError } from 'ai';
try {
const result = await generateObject({
model: openai('gpt-4-turbo'),
schema: z.object({ /* 复杂模式 */ }),
prompt: '生成数据',
});
} catch (error) {
if (error instanceof AI_NoObjectGeneratedError) {
console.error('未生成有效对象');
// 解决方案:
// 1. 简化模式
// 2. 在提示中添加更多上下文
// 3. 在提示中提供示例
// 4. 尝试不同的模型 (对于复杂对象,尝试 gpt-5 或 claude-sonnet-4-5)
}
}
预防措施:
原因: Cloudflare Workers 中的 AI SDK v5 + Zod 初始化开销超过启动限制。
解决方案:
// 不好:顶层导入导致启动开销
import { createWorkersAI } from 'workers-ai-provider';
import { complexSchema } from './schemas';
const workersai = createWorkersAI({ binding: env.AI });
// 好:在处理器内部延迟初始化
export default {
async fetch(request, env) {
const { createWorkersAI } = await import('workers-ai-provider');
const workersai = createWorkersAI({ binding: env.AI });
// 在这里使用 workersai
}
}
预防措施:
GitHub 问题: 在 Vercel AI SDK 问题中搜索 "Workers startup limit"
原因: 流错误可能被 createDataStreamResponse 吞掉。
状态: ✅ 已解决 - 在 ai@4.1.22 (2025年2月) 中修复
解决方案 (推荐):
// 使用 onError 回调 (在 v4.1.22 中添加)
const stream = streamText({
model: openai('gpt-4-turbo'),
prompt: 'Hello',
onError({ error }) {
console.error('流错误:', error);
// 自定义错误日志记录和处理
},
});
// 安全地流式处理
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
替代方案 (手动 try-catch):
// 如果不使用 onError 回调的回退方案
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('流错误:', error);
}
预防措施:
onError 回调 进行适当的错误捕获 (推荐)GitHub 问题: #4726 (已解决)
原因: 缺少或无效的 API 密钥。
解决方案:
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 密钥错误:', error.message);
// 检查:
// 1. .env 文件存在且已加载
// 2. 正确的环境变量名称 (OPENAI_API_KEY)
// 3. 密钥格式有效 (以 sk- 开头)
}
}
预防措施:
原因: 传递给函数的参数无效。
解决方案:
import { AI_InvalidArgumentError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
maxOutputTokens: -1, // 无效!
prompt: 'Hello',
});
} catch (error) {
if (error instanceof AI_InvalidArgumentError) {
console.error('无效参数:', error.message);
// 检查参数类型和值
}
}
预防措施:
原因: 模型没有生成任何内容 (安全过滤器等)。
解决方案:
import { AI_NoContentGeneratedError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt: '某些提示',
});
} catch (error) {
if (error instanceof AI_NoContentGeneratedError) {
console.error('未生成任何内容');
// 可能的原因:
// 1. 安全过滤器阻止了输出
// 2. 提示触发了内容策略
// 3. 模型配置问题
// 优雅地处理:
return { text: '无法生成响应。请尝试不同的输入。' };
}
}
预防措施:
原因: 对生成的输出进行 Zod 模式验证失败。
解决方案:
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), // 严格验证
}),
prompt: '生成人员',
});
} catch (error) {
if (error instanceof AI_TypeValidationError) {
console.error('验证失败:', error.message);
// 解决方案:
// 1. 放宽模式约束
// 2. 在提示中添加更多指导
// 3. 对于不可靠的字段使用 .optional()
}
}
预防措施:
.optional()原因: 所有重试尝试都失败了。
解决方案:
import { AI_RetryError } from 'ai';
try {
const result = await generateText({
model: openai('gpt-4-turbo'),
prompt: 'Hello',
maxRetries: 3, // 默认为 2
});
} catch (error) {
if (error instanceof AI_RetryError) {
console.error('所有重试都失败了');
console.error('最后错误:', error.lastError);
// 检查根本原因:
// - 持续的网络问题
// - 提供商中断
// - 无效的配置
}
}
预防措施:
原因: 超过提供商速率限制 (RPM/TPM)。
解决方案:
// 实现指数退避
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; // 指数退避
console.log(`速率受限,等待 ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw new Error('速率限制重试已用尽');
}
预防措施:
原因: 复杂的 Zod 模式会减慢 TypeScript 类型检查速度。
解决方案:
// 不要在顶层使用深度嵌套的模式:
// const complexSchema = z.object({ /* 100+ 字段 */ });
// 在函数内部定义或使用类型断言:
function generateData() {
const schema = z.object({ /* 复杂模式 */ });
return generateObject({ model: openai('gpt-4-turbo'), schema, prompt: '...' });
}
// 或者对于递归模式使用 z.lazy():
type Category = { name: string; subcategories?: Category[] };
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(CategorySchema).optional(),
})
);
预防措施:
z.lazy()原因: 某些模型偶尔返回无效的 JSON。
解决方案:
// 使用内置的重试和模式选择
const result = await generateObject({
model: openai('gpt-4-turbo'),
schema: mySchema,
prompt: '生成数据',
mode: 'json', // 强制 JSON 模式 (GPT-4 支持)
maxRetries: 3, // 在无效 JSON 时重试
});
// 或者手动捕获并重试:
try {
const result = await generateObject({
model: openai('gpt-4-turbo
Backend 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)
Use selective approval for better UX. Not every tool call needs approval.
tools: {
payment: tool({
// Dynamic approval based on input
needsApproval: async ({ amount }) => amount > 1000,
inputSchema: z.object({ amount: z.number() }),
execute: async ({ amount }) => { /* process payment */ },
}),
readFile: tool({
needsApproval: false, // Safe operations don't need approval
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => fs.readFile(path),
}),
deleteFile: tool({
needsApproval: true, // Destructive operations always need approval
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }) => fs.unlink(path),
}),
}
Best Practices :
Sources :
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)
⚠️ SECURITY WARNING : MCP tools have significant production risks. See security section below.
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',
});
Known Issue : MCP tools may not execute in streaming mode (Vercel Community Discussion). Use generateText() instead of streamText() for MCP tools.
MCP Security Considerations
⚠️ CRITICAL : Dynamic MCP tools in production have security risks:
Risks :
Solution - Use Static Tool Generation :
// ❌ RISKY: Dynamic tools change without your control
const mcpClient = await experimental_createMCPClient({ /* ... */ });
const tools = await mcpClient.tools(); // Can change anytime!
// ✅ SAFE: Generate static, versioned tool definitions
// Step 1: Install mcp-to-ai-sdk
npm install -g mcp-to-ai-sdk
// Step 2: Generate static tools (one-time, version controlled)
npx mcp-to-ai-sdk generate stdio 'npx -y @modelcontextprotocol/server-filesystem'
// Step 3: Import static tools
import { tools } from './generated-mcp-tools';
const result = await generateText({
model: openai('gpt-5'),
tools, // Static, reviewed, versioned
prompt: 'Use tools',
});
Best Practice : Generate static tools, review them, commit to version control, and only update intentionally.
Source : Vercel Blog: MCP Security
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):
o3, o3-pro, o3-mini - Advanced reasoning
o4-mini - Fast reasoning
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):
Opus 4 (May 22): Best for complex reasoning, $15/$75 per million tokens
Sonnet 4 (May 22): Balanced performance, $3/$15 per million tokens
Opus 4.1 (Aug 5): Enhanced agentic tasks, real-world coding
Sonnet 4.5 (Sept 29): Most capable for coding, agents, computer use
Haiku 4.5 (Oct 15): Small, fast, low-latency model
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):
Pro (March 2025): Most intelligent, #1 on LMArena at launch
Pro Deep Think (May 2025): Enhanced reasoning mode
Flash (May 2025): Fast, cost-effective
Flash-Lite (Sept 2025): Updated efficiency
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 usetoUIMessageStreamResponse():
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 → Before (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 → 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)
Error : No error, but higher API costs due to disabled caching Cause : Google Gemini 3 Flash's cost-saving implicit caching doesn't work when any tools are defined, even if never used. Source : GitHub Issue #11513
Why It Happens : Gemini API disables caching when tools are present in the request, regardless of whether they're invoked.
Prevention :
// Conditionally add tools only when needed
const needsTools = await analyzePrompt(userInput);
const result = await generateText({
model: google('gemini-3-flash'),
tools: needsTools ? { weather: weatherTool } : undefined,
prompt: userInput,
});
Impact : High - Can significantly increase API costs for repeated context
Error : SyntaxError: "[object Object]" is not valid JSON Cause : Anthropic provider built-in tools (web_fetch, etc.) return error objects that SDK tries to JSON.parse Source : GitHub Issue #11856
Why It Happens : When Anthropic built-in tools fail (e.g., url_not_allowed), they return error objects. AI SDK incorrectly tries to parse these as JSON strings.
Prevention :
try {
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
tools: { web_fetch: { type: 'anthropic_defined', name: 'web_fetch' } },
prompt: userPrompt,
});
} catch (error) {
if (error.message.includes('is not valid JSON')) {
// Tool returned error result, handle gracefully
console.error('Tool execution failed - likely blocked URL or permission issue');
// Retry without tool or use custom tool
}
throw error;
}
Impact : High - Production crashes when using Anthropic built-in tools
Error : Anthropic API error - tool-result in assistant message not allowed Cause : Server-executed tools incorrectly place tool-result parts in assistant messages Source : GitHub Issue #11855
Why It Happens : When using server-executed tools (tools where execute runs on server, not sent to model), the AI SDK incorrectly includes tool-result parts in the assistant message. Anthropic expects tool-result only in user messages.
Prevention :
// Workaround: Filter messages before sending
const filteredMessages = messages.map(msg => {
if (msg.role === 'assistant') {
return {
...msg,
content: msg.content.filter(part => part.type !== 'tool-result'),
};
}
return msg;
});
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
tools: { database: databaseTool },
messages: filteredMessages,
prompt: 'Get user data',
});
Impact : High - Breaks server-executed tool pattern with Anthropic provider
Status : Known issue, PR #11854 submitted
More Errors: https://ai-sdk.dev/docs/reference/ai-sdk-errors (31 total)
Issue : When using useChat with memoized options (common for performance), the onData and onFinish callbacks have stale closures and don't see updated state variables.
Source : GitHub Issue #11686
Reproduction :
const [count, setCount] = useState(0);
const chatOptions = useMemo(() => ({
onFinish: (message) => {
console.log('Count:', count); // ALWAYS 0, never updates!
},
}), []); // Empty deps = stale closure
const { messages, append } = useChat(chatOptions);
Workaround 1 - Don't Memoize Callbacks :
const { messages, append } = useChat({
onFinish: (message) => {
console.log('Count:', count); // Now sees current count
},
});
Workaround 2 - Use useRef :
const countRef = useRef(count);
useEffect(() => { countRef.current = count; }, [count]);
const chatOptions = useMemo(() => ({
onFinish: (message) => {
console.log('Count:', countRef.current); // Always current
},
}), []);
Full Repro : https://github.com/alechoey/ai-sdk-stale-ondata-repro
Issue : When users switch browser tabs or background the app during an AI stream, the stream does not resume when they return. The connection is lost and does not automatically reconnect.
Source : GitHub Issue #11865
Impact : High - Major UX issue for long-running streams
Workaround 1 - Implement onError Handler :
const { messages, append, reload } = useChat({
api: '/api/chat',
onError: (error) => {
if (error.message.includes('stream') || error.message.includes('aborted')) {
// Attempt to reload last message
reload();
}
},
});
Workaround 2 - Detect Visibility Change :
useEffect(() => {
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') {
// Check if stream was interrupted
const lastMessage = messages[messages.length - 1];
if (lastMessage?.role === 'assistant' && !lastMessage.content) {
reload();
}
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
}, [messages, reload]);
Status : Known limitation, no auto-reconnection built-in
AI SDK:
Latest Models (2026):
Check Latest:
npm view ai version
npm view ai dist-tags
Core:
Multi-Modal:
GitHub:
Last Updated: 2026-01-20 Skill Version: 2.1.0 Changes: Added 3 new errors (Gemini caching, Anthropic tool errors, tool-result placement), MCP security guidance, tool approval best practices, React hooks edge cases, stream resumption workarounds AI SDK: 6.0.26 stable (avoid v6.0.40)
Weekly Installs
479
Repository
GitHub Stars
652
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
claude-code377
opencode319
gemini-cli313
cursor295
codex276
antigravity265
AI 代码实施计划编写技能 | 自动化开发任务分解与 TDD 流程规划工具
42,300 周安装
LLM提示词缓存优化指南:降低90%成本,实现多级缓存与语义匹配
323 周安装
小红书内容转换器:一键将通用文章转为小红书爆款笔记格式 | AI写作助手
323 周安装
内容摘要AI工具:智能提取YouTube、网页、PDF和推文内容,支持测验学习和深度探索
324 周安装
Notion知识捕获工具 - 将对话笔记自动转化为结构化Notion页面 | 知识管理自动化
324 周安装
现代Angular最佳实践指南:TypeScript严格性、信号响应式、性能优化与测试
324 周安装
iOS VoIP 通话开发:CallKit + PushKit 集成原生通话 UI 指南
324 周安装
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/langchainModelMessageToolExecutionError handlingai/rsc → @ai-sdk/rsc)