关于 vercel-ai-sdk
面向 TypeScript 应用开发者的 Vercel AI SDK v6 实践指南,涵盖聊天、Agent、工具、结构化输出、流式响应和 MCP 集成,并介绍当前 API 与验证流程。
主要功能
- 01聊天与流式响应模式
- 02使用 v6 API 调用工具
- 03ToolLoopAgent 实现指南
- 04使用 Output helpers 生成结构化输出
- 05嵌入与语义搜索指南
- 06MCP 服务集成指南
适用场景
- 01使用 useChat 实现聊天界面
- 02构建生成流式 AI 响应的 API 路由
- 03为 Agent 应用添加工具
- 04将结构化输出代码迁移至 v6
MCP 相关
指南介绍如何将 Model Context Protocol 服务集成到 Vercel AI SDK 应用中。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ op0ai/ claude-skills --skill vercel-ai-sdk安装后在 Claude Code 中输入/vercel-ai-sdk确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/agents.mdcore-functions.mdmcp.mdmiddleware.mdtools.mdui-hooks.mdworkflows.md详细介绍
Vercel AI SDK v6 实施指南
何时使用此技能
在以下情况下使用此技能:
- 使用
useChathook 实现 AI 聊天界面 - 创建用于生成或流式传输 AI 响应的 API 路由
- 使用
ToolLoopAgent构建 Agent 应用 - 添加工具调用/函数调用功能
- 使用
Output.object()、Output.array()等生成结构化输出 - 生成用于语义搜索或 RAG 的文本嵌入
- 从 AI SDK v5 迁移到 v6
- 集成 Model Context Protocol(MCP)服务
- 实现用于缓存、日志记录或防护机制的中间件
- 构建工作流模式(顺序、并行、路由等)
- 处理流式响应或消息持久化
结构化实施工作流
了解任务需求 - 确定需要哪些 AI 功能(聊天、生成、Agent、工具、嵌入) - 确定应采用客户端(useChat)还是服务端(API 路由)实现 - 检查是否需要流式或非流式响应 - 确认模型提供商(Anthropic、OpenAI 等) - 确定是否需要结构化输出(Output.object、Output.array 等) 如有疑问,核实当前 API 模式 - 如果 API 模式不明确,使用 WebFetch 查看 https://ai-sdk.dev/docs/ - 确认提供商的模型指定格式 - 核实复杂功能的函数签名 使用正确的 v6 模式实现 - 使用提供商函数指定模型:anthropic('claude-sonnet-4-5') - 对于聊天:使用 sendMessage(而非 append),并使用基于 parts 的消息 - 对于工具:必须从 'ai' 导入并使用 tool() 助手,必须使用 inputSchema(而非 parameters),必须使用 zod - 对于结构化输出:使用 Output.object()、Output.array()、Output.choice()、Output.json() - 对于流式传输:使用 toUIMessageStreamResponse() 或 toTextStreamResponse() - 对于 Agent:使用 ToolLoopAgent 类和 createAgentUIStreamResponse() - 对于嵌入:使用 provider.textEmbeddingModel() 确保 TypeScript 类型正确 - 检查是否从 'ai' 包正确导入 - 核实消息类型(useChat 使用 UIMessage) - 确保正确推断工具参数类型 - 为异步函数添加显式类型 使用正确的包管理器安装所有缺失的依赖项 - **关键:首先检测项目使用的包管理器** * 检查锁文件:pnpm-lock.yaml → 使用 pnpm,package-lock.json → 使用 npm,yarn.lock → 使用 yarn,bun.lockb → 使用 bun * 如果存在 pnpm-lock.yaml,则必须使用 pnpm(不能使用 npm!) - 检查是否已安装所有导入的包 - 如果构建失败并提示 "Module not found",请从错误中确定包名 - 将包添加到 package.json 的 dependencies 中 - 使用正确的包管理器安装: * 如果存在 pnpm-lock.yaml:`pnpm install [package]` 或 `pnpm add [package]` * 如果存在 package-lock.json:`npm install [package]` * 如果存在 yarn.lock:`yarn add [package]` * 如果存在 bun.lockb:`bun install [package]` 或 `bun add [package]` - 重新运行构建,验证安装是否成功 **绝对不要使用错误的包管理器!** - 项目使用 pnpm 时使用 npm,会在 pnpm-lock.yaml 旁边生成 package-lock.json - 这会导致依赖版本不匹配并破坏构建 - 始终检查现有锁文件,并使用与之匹配的包管理器 NEVER accept "Module not found" errors as environment issues
YOU must install the required packages with the CORRECT package manager
Common packages needed:
- ai (core AI SDK)
- @ai-sdk/openai (OpenAI provider)
- @ai-sdk/anthropic (Anthropic provider)
- @ai-sdk/mcp (MCP integration)
- @modelcontextprotocol/sdk (MCP client SDK)
- zod (for tool schemas)
</critical>
运行构建并修复所有错误,直到构建通过
- 运行:npm run build(或 bun run build)
- 如果构建失败,请仔细阅读完整错误消息
- 常见修复方法:
* "Module not found" → 安装该包(返回步骤 5)
* 类型错误 → 修复 TypeScript 类型
* 配置错误 → 检查 next.config.js/ts
* 框架错误 → 调查错误并尝试不同的方法
- 应用修复并重新运行构建
- 重复以上步骤,直到构建通过
绝不要以“构建失败,但代码没问题”为由停止
未经调试,绝不要将问题归咎于“环境”或“框架错误”
持续迭代,直到构建通过
如果遇到框架错误,请调查原因、尝试变通方法并进行调试
在构建通过之前,不要放弃
运行测试并调试所有失败,直到测试通过
- 运行:npm run test(或 bun run test)
- 如果测试失败,请仔细阅读完整测试错误消息
- 调试失败原因:
* 检查测试预期的内容与你提供的内容是否一致
* 查看测试断言
* 验证实现是否符合要求
* 检查是否存在类型不匹配或缺少属性
- 修复实现
- 重新运行测试
- 重复以上步骤,直到所有测试通过
绝不要在未调试的情况下接受测试失败
未经调查,绝不要将失败斥为“测试框架错误”
每个测试失败都在告诉你一些事情——认真听取
持续调试,直到所有测试通过
测试失败不可接受——这意味着实现尚不完整
验证所有检查均已通过
- 运行:npm run build && npm run lint && npm run test
- 三项都必须通过:✅ 构建、✅ Lint、✅ 测试
- 如果有任何一项失败,请返回并修复
- 所有检查都通过之前,不要认为任务已完成
满足以下条件后,任务才算完成:
- ✅ 构建通过
- ✅ Lint 通过
- ✅ 测试通过
"Code is correct" is NOT enough
You must achieve FULL PASSING status
This is what it means to be an autonomous agent
</critical>
## ⚠️ 自主 Agent 思维模式
你不只是在编写代码,而是在自主完成任务。
这意味着:
- ✅ 编写正确的实现
- ✅ 安装所有必需的依赖项
- ✅ 运行构建并修复所有错误
- ✅ 运行测试并调试所有失败项
- ✅ 反复迭代,直到所有检查都通过
- ✅ 绝不找借口或放弃
应避免的常见失败模式
❌ 错误:“代码没问题,只是没有安装软件包——这是环境问题” ✅ 正确:“构建因缺少软件包而失败——现在用 npm install [package] 安装”
❌ 错误:“测试通过了,但构建失败了——这不关我的事” ✅ 正确:“构建失败了——现在调试并修复错误”
❌ 错误:“这是框架的 bug,没法修复” ✅ 正确:“检测到框架错误——现在研究问题、尝试解决方法并持续调试,直到找到解决方案”
❌ 错误:“实现已完成”(但测试仍未通过) ✅ 正确:“测试失败了——现在调试并修复,直到全部通过”
依赖项安装工作流
遇到“Module not found”错误时:
-
先检测软件包管理器——检查锁文件:
ls -la | grep -E "lock" # Look for: pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb -
根据导入语句确定软件包
Error: Cannot find module '@ai-sdk/anthropic' Import: import { anthropic } from '@ai-sdk/anthropic' Package needed: @ai-sdk/anthropic -
使用正确的软件包管理器安装
# If pnpm-lock.yaml exists (MOST COMMON for Next.js evals): pnpm install @ai-sdk/anthropic # or pnpm add @ai-sdk/anthropic # If package-lock.json exists: npm install @ai-sdk/anthropic # If yarn.lock exists: yarn add @ai-sdk/anthropic # If bun.lockb exists: bun install @ai-sdk/anthropic -
重新运行构建进行验证
npm run build # or pnpm run build, yarn build, bun run build -
修复出现的任何新错误
⚠️ 严重警告: 使用错误的软件包管理器(例如,项目使用 pnpm,却使用 npm)会:
- 创建第二个相冲突的锁文件
- 安装不同版本的依赖项
- 导致依赖项版本不匹配
- 引发难以理解的错误,导致构建失败,例如“Cannot read properties of null”
构建错误调试工作流
构建失败时:
- 完整阅读错误消息——不要略读
- 找出根本原因:
- 未找到模块 → 安装软件包
- 类型错误 → 修复类型
- 配置错误 → 检查配置文件
- Next.js 错误 → 研究问题并尝试不同方法
- 应用修复
- 重新运行构建
- 重复以上步骤,直到构建通过
测试失败调试工作流
测试失败时:
- 完整阅读测试错误——弄清预期结果
- 比较预期行为与实际行为
- 根据测试断言检查实现
- 在代码中修复问题
- 重新运行测试
- 重复以上步骤,直到所有测试通过
成功标准
只有满足以下条件,任务才算完成:
- ✅ 构建通过(
npm run build成功) - ✅ Lint 检查通过(
npm run lint成功) - ✅ 测试通过(
npm run test成功)
绝不要停留在“代码没问题”——务必确保所有检查全部通过!
⚠️ v6 关键变更:结构化输出
在 v6 中,generateObject 和 streamObject 已弃用。 请改用 generateText/streamText 和 Output 辅助函数。
❌ 错误——已弃用的 v5 模式
// DO NOT USE - DEPRECATED in v6
import { generateObject } from "ai";
const result = await generateObject({
model: anthropic("claude-sonnet-4-5"),
schema: z.object({
sentiment: z.enum(["positive", "neutral", "negative"]),
}),
prompt: "Analyze sentiment",
});
✅ 正确——v6 Output 模式
import { generateText, Output } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const { output } = await generateText({
model: anthropic("claude-sonnet-4-5"),
output: Output.object({
schema: z.object({
sentiment: z.enum(["positive", "neutral", "negative"]),
topics: z.array(z.string()),
}),
}),
prompt: "Analyze this feedback...",
});
// Access typed output
console.log(output.sentiment); // 'positive' | 'neutral' | 'negative'
console.log(output.topics); // string[]
Output 辅助函数类型
| 辅助函数 | 用途 | 示例 |
|---|---|---|
Output.object() | 生成带类型的对象 | Output.object({ schema: z.object({...}) }) |
Output.array() | 生成带类型的数组 | Output.array({ schema: z.string() }) |
Output.choice() | 生成枚举值 | Output.choice({ choices: ['A', 'B', 'C'] }) |
Output.json() | 无结构的 JSON | Output.json() |
⚠️ 关键:工具调用 API——必须使用 tool() 辅助函数
实现工具调用时,必须使用 'ai' 软件包中的 tool() 辅助函数。
❌ 错误——普通对象(会导致构建错误)
// DO NOT DO THIS - This pattern is INCORRECT
import { z } from 'zod';
tools: {
myTool: {
description: 'My tool',
parameters: z.object({...}), // ❌ WRONG - "parameters" doesn't exist in v6
execute: async ({...}) => {...},
}
}
这会导致以下错误: Type '{ description: string; parameters: ... }' is not assignable to type '{ inputSchema: FlexibleSchema<any>; ... }'
✅ 正确——使用 tool() 辅助函数(必需)
// ALWAYS DO THIS - This is the ONLY correct pattern
import { tool } from 'ai'; // ⚠️ MUST import tool
import { z } from 'zod';
tools: {
myTool: tool({ // ⚠️ MUST wrap with tool()
description: 'My tool',
inputSchema: z.object({...}), // ⚠️ MUST use "inputSchema" (not "parameters")
execute: async ({...}) => {...},
}),
}
工具调用清单
实现任何工具前,请确认:
- [ ] 从 'ai' 包导入了
tool:import { tool } from 'ai'; - [ ] 使用
tool({ ... })封装了工具定义 - [ ] 使用了
inputSchema属性(而非parameters) - [ ] 使用了 zod 模式:
z.object({ ... }) - [ ] 使用异步回调定义了
execute函数 - [ ] 为工具添加了
description字符串
⚠️ v6 新增:面向 Agent 应用的 ToolLoopAgent
Agent 定义
import { ToolLoopAgent, tool, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const myAgent = new ToolLoopAgent({
model: anthropic("claude-sonnet-4-5"),
instructions: "You are a helpful assistant that can search and analyze data.",
tools: {
getData: tool({
description: "Fetch data from API",
inputSchema: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
// Implement data fetching
return { result: "data for " + query };
},
}),
analyzeData: tool({
description: "Analyze fetched data",
inputSchema: z.object({
data: z.string(),
}),
execute: async ({ data }) => {
return { analysis: "Analysis of " + data };
},
}),
},
stopWhen: stepCountIs(20), // Stop after 20 steps max
});
// Non-streaming execution
const { text, toolCalls } = await myAgent.generate({
prompt: "Find and analyze user data",
});
// Streaming execution
const stream = myAgent.stream({ prompt: "Find and analyze user data" });
for await (const chunk of stream) {
// Handle streaming chunks
}
Agent API 路由集成
// app/api/agent/route.ts
import { createAgentUIStreamResponse } from "ai";
import { myAgent } from "@/agents/my-agent";
export async function POST(request: Request) {
const { messages } = await request.json();
return createAgentUIStreamResponse({
agent: myAgent,
uiMessages: messages,
});
}
Agent 配置选项
| 参数 | 用途 | 示例 |
|---|---|---|
model | 要使用的 AI 模型 | anthropic('claude-sonnet-4-5') |
instructions | 系统提示词 | 'You are a helpful assistant.' |
tools | 可用工具 | { toolName: tool({...}) } |
stopWhen | 终止条件 | stepCountIs(20) |
toolChoice | 工具使用模式 | 'auto'、'required'、'none' |
output | 结构化输出模式 | Output.object({...}) |
prepareStep | 每个步骤的动态调整 | 返回步骤配置的函数 |
prepareCall | 运行时选项注入 | 用于 RAG 等的异步函数 |
⚠️ 重要:v5 到 v6 的常见破坏性变更
1. useChat Hook 变更
❌ 错误(v5 模式):
const { messages, input, setInput, append } = useChat();
// Sending message
append({ content: text, role: "user" });
✅ 正确(v6 模式):
const { messages, sendMessage, status, addToolOutput } = useChat();
const [input, setInput] = useState('');
// Sending message
sendMessage({ text: input });
// New in v6: Handle tool outputs
addToolOutput({ toolCallId: 'xxx', result: { ... } });
2. 消息结构
❌ 错误(v5 简单内容):
<div>{message.content}</div>
✅ 正确(基于 v6 parts 的结构):
<div>
{message.parts.map((part, index) =>
part.type === 'text' ? <span key={index}>{part.text}</span> : null
)}
</div>
3. 响应方法
❌ 错误(v5):
return result.toDataStreamResponse();
✅ 正确(v6):
return result.toUIMessageStreamResponse();
4. 模型指定
import { anthropic } from "@ai-sdk/anthropic";
import { openai } from "@ai-sdk/openai";
// Use provider functions (direct provider access)
model: anthropic("claude-sonnet-4-5");
model: anthropic("claude-opus-4-5");
model: anthropic("claude-haiku-4-5");
model: openai("gpt-4o");
model: openai("gpt-4o-mini");
5. Vercel AI Gateway
用途: 使用 Vercel AI Gateway,通过多个提供商统一访问模型,并实现速率限制、缓存和可观测性。
导入:
import { gateway } from "ai";
通过 Gateway 提供的 Anthropic 模型:
model: gateway("anthropic/claude-sonnet-4-5");
model: gateway("anthropic/claude-haiku-4-5");
model: gateway("anthropic/claude-opus-4-5");
适用 Gateway 的场景:
- 需要速率限制和缓存的生产应用
- 需要统一接口的多提供商应用
- 需要可观测性和分析功能的应用
- 需要自动重试和错误处理时
适用直接使用提供商的场景:
- 开发/测试环境
- 需要使用 Gateway 不支持的提供商专属功能时
- 需要直接控制 API 调用时
示例:
import { generateText, gateway } from "ai";
const result = await generateText({
model: gateway("anthropic/claude-sonnet-4-5"),
prompt: "Hello, world!",
});
对比:
// Option 1: Direct provider
import { anthropic } from "@ai-sdk/anthropic";
model: anthropic("claude-sonnet-4-5");
// Option 2: Gateway (recommended for production)
import { gateway } from "ai";
model: gateway("anthropic/claude-sonnet-4-5");
核心 API 参考
1. generateText - 非流式文本生成
用途: 为非交互式场景生成文本(电子邮件草稿、摘要、使用工具的 Agent)。
签名:
import { generateText, Output } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const result = await generateText({
model: anthropic('claude-sonnet-4-5'),
prompt: 'Your prompt here',
system: 'Optional system message',
tools?: { ... },
maxSteps?: 5,
output?: Output.object({ schema: z.object({...}) }),
});
返回值:
{
text: string; // Generated text output
output?: T; // Typed structured output (if Output specified)
toolCalls: ToolCall[]; // Tool invocations made
finishReason: string; // Why generation stopped
usage: TokenUsage; // Token consumption
response: RawResponse; // Raw provider response
warnings: Warning[]; // Provider-specific alerts
}
示例:
// app/api/generate/route.ts
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export async function GET() {
const result = await generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: "Why is the sky blue?",
});
return Response.json({ text: result.text });
}
2. streamText - 流式文本生成
用途: 为交互式聊天应用流式传输响应。
签名:
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const result = streamText({
model: anthropic('claude-sonnet-4-5'),
prompt: 'Your prompt here',
system: 'Optional system message',
messages?: ModelMessage[],
tools?: { ... },
onChunk?: (chunk) => { ... },
onStepFinish?: (step) => { ... },
onFinish?: async (result) => { ... },
onError?: async (error) => { ... },
});
返回方法:
// For chat applications with useChat hook
result.toUIMessageStreamResponse();
// For simple text streaming
result.toTextStreamResponse();
示例 - 聊天 API 路由:
// app/api/chat/route.ts
import { streamText, convertToModelMessages } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import type { UIMessage } from "ai";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-5"),
system: "You are a helpful assistant.",
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}
3. useChat Hook - 客户端聊天界面
用途: 构建支持流式传输的交互式聊天 UI。
签名:
import { useChat } from '@ai-sdk/react';
const {
messages, // Array of UIMessage with parts-based structure
sendMessage, // Function to send messages (replaces append)
status, // 'submitted' | 'streaming' | 'ready' | 'error'
stop, // Abort current streaming
regenerate, // Reprocess last message
setMessages, // Manually modify history
error, // Error object if request fails
clearError, // Clear error state
addToolOutput, // Submit tool results (NEW in v6)
resumeStream, // Resume interrupted stream (NEW in v6)
} = useChat({
api: '/api/chat',
id?: 'chat-id',
messages?: initialMessages,
onToolCall?: async (toolCall) => { ... },
onFinish?: (message) => { ... },
onError?: (error) => { ... },
sendAutomaticallyWhen?: (messages) => boolean,
resume?: true,
});
完整示例:
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function ChatPage() {
const { messages, sendMessage, status, addToolOutput } = useChat({
onToolCall: async ({ toolCall }) => {
// Handle client-side tool execution
if (toolCall.name === 'confirm') {
const result = await showConfirmDialog(toolCall.args);
addToolOutput({ toolCallId: toolCall.id, result });
}
},
});
const [input, setInput] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
sendMessage({ text: input });
setInput('');
};
return (
<div>
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, index) => {
switch (part.type) {
case 'text':
return <span key={index}>{part.text}</span>;
case 'tool-call':
return <div key={index}>Tool: {part.name}</div>;
default:
return null;
}
})}
</div>
))}
</div>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message..."
disabled={status === 'streaming'}
/>
<button type="submit" disabled={status === 'streaming'}>
Send
</button>
</form>
</div>
);
}
4. 工具调用 / 函数调用
用途: 让 AI 模型能够使用结构化参数调用函数。
定义工具:
import { tool } from "ai";
import { z } from "zod";
const weatherTool = tool({
description: "Get the weather in a location",
inputSchema: z.object({
location: z.string().describe("The location to get the weather for"),
unit: z.enum(["C", "F"]).describe("Temperature unit"),
}),
outputSchema: z.object({
temperature: z.number(),
condition: z.string(),
}),
execute: async ({ location, unit }) => {
// Fetch or mock weather data
return {
temperature: 24,
condition: "Sunny",
};
},
});
将工具与 generateText/streamText 配合使用:
// app/api/chat/route.ts
import { streamText, convertToModelMessages, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
import type { UIMessage } from "ai";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-5"),
messages: convertToModelMessages(messages),
tools: {
getWeather: tool({
description: "Get the weather for a location",
inputSchema: z.object({
city: z.string().describe("The city to get the weather for"),
unit: z
.enum(["C", "F"])
.describe("The unit to display the temperature in"),
}),
execute: async ({ city, unit }) => {
// API call or mock data
return `It is currently 24°${unit} and Sunny in ${city}!`;
},
}),
},
toolChoice: "auto", // 'auto' | 'required' | 'none' | { type: 'tool', toolName: 'xxx' }
});
return result.toUIMessageStreamResponse();
}
多步骤工具调用:
const result = await generateText({
model: anthropic("claude-sonnet-4-5"),
tools: {
weather: weatherTool,
search: searchTool,
},
prompt: "What is the weather in San Francisco and find hotels there?",
maxSteps: 5, // Allow up to 5 tool call steps
});
5. 文本嵌入
用途: 将文本转换为数值向量,用于语义搜索、RAG 或相似度计算。
签名:
import { embed, embedMany } from "ai";
import { openai } from "@ai-sdk/openai";
// Single embedding
const result = await embed({
model: openai.textEmbeddingModel("text-embedding-3-small"),
value: "Text to embed",
});
// Batch embeddings
const batchResult = await embedMany({
model: openai.textEmbeddingModel("text-embedding-3-small"),
values: ["Text 1", "Text 2", "Text 3"],
});
返回值:
{
embedding: number[]; // Numerical array representing the text
usage: { tokens: number }; // Token consumption
response: RawResponse; // Raw provider response
}
示例 - 嵌入 API 路由:
// app/api/embed/route.ts
import { embed } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { text } = await req.json();
const { embedding, usage } = await embed({
model: openai.textEmbeddingModel("text-embedding-3-small"),
value: text,
});
return Response.json({ embedding, usage });
}
6. 中间件
用途: 拦截并修改模型行为,用于日志记录、缓存、防护措施、RAG 等。
内置中间件:
import {
extractReasoningMiddleware,
simulateStreamingMiddleware,
defaultSettingsMiddleware,
wrapLanguageModel,
} from "ai";
// Extract reasoning from models like Claude
const modelWithReasoning = wrapLanguageModel({
model: anthropic("claude-sonnet-4-5"),
middleware: extractReasoningMiddleware({ tagName: "thinking" }),
});
// Apply default settings
const modelWithDefaults = wrapLanguageModel({
model: anthropic("claude-sonnet-4-5"),
middleware: defaultSettingsMiddleware({
temperature: 0.7,
maxOutputTokens: 1000,
}),
});
自定义中间件:
import { LanguageModelMiddleware, wrapLanguageModel } from "ai";
// Logging middleware
const loggingMiddleware: LanguageModelMiddleware = {
transformParams: async ({ params }) => {
console.log("Request params:", params);
return params;
},
wrapGenerate: async ({ doGenerate, params }) => {
const result = await doGenerate();
console.log("Response:", result);
return result;
},
};
// Caching middleware
const cache = new Map<string, string>();
const cachingMiddleware: LanguageModelMiddleware = {
wrapGenerate: async ({ doGenerate, params }) => {
const cacheKey = JSON.stringify(params.prompt);
if (cache.has(cacheKey)) {
return { text: cache.get(cacheKey)! };
}
const result = await doGenerate();
cache.set(cacheKey, result.text);
return result;
},
};
// RAG middleware
const ragMiddleware: LanguageModelMiddleware = {
transformParams: async ({ params }) => {
const relevantDocs = await vectorSearch(params.prompt);
return {
...params,
prompt: `Context: ${relevantDocs}\n\nQuery: ${params.prompt}`,
};
},
};
// Apply multiple middleware
const enhancedModel = wrapLanguageModel({
model: anthropic("claude-sonnet-4-5"),
middleware: [loggingMiddleware, cachingMiddleware, ragMiddleware],
});
7. Model Context Protocol(MCP)集成
**用途:**连接外部 MCP 服务,以动态访问工具。
安装:
bun add @ai-sdk/mcp @modelcontextprotocol/sdk
HTTP 传输(生产环境):
import { createMCPClient } from "@ai-sdk/mcp";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
export async function POST(req: Request) {
const { prompt } = await req.json();
const httpTransport = new StreamableHTTPClientTransport(
new URL("https://mcp-server.example.com/mcp"),
{ headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` } }
);
const mcpClient = await createMCPClient({ transport: httpTransport });
try {
const tools = await mcpClient.tools();
const response = streamText({
model: anthropic("claude-sonnet-4-5"),
tools,
prompt,
onFinish: async () => {
await mcpClient.close();
},
onError: async () => {
await mcpClient.close();
},
});
return response.toTextStreamResponse();
} catch (error) {
await mcpClient.close();
return new Response("Internal Server Error", { status: 500 });
}
}
Stdio 传输(开发环境):
import { createMCPClient } from "@ai-sdk/mcp";
import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp";
const stdioTransport = new Experimental_StdioMCPTransport({
command: "npx",
args: [
"-y",
"@modelcontextprotocol/server-filesystem",
"/path/to/allowed/dir",
],
});
const mcpClient = await createMCPClient({ transport: stdioTransport });
要点:
- 始终在
onFinish和onError中关闭客户端 - 使用
mcpClient.tools()动态获取工具 - 生产环境使用 HTTP/SSE,开发环境使用 Stdio
- 可以合并工具集合,以组合多个 MCP 客户端
8. 消息实用工具
convertToModelMessages:
将 useChat 中的 UI 消息转换为供 AI 函数使用的 ModelMessage 对象。
import { convertToModelMessages } from "ai";
import type { UIMessage } from "ai";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-5"),
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}
工作流模式
1. 顺序(链式)模式
async function sequentialWorkflow(input: string) {
// Step 1: Generate initial content
const { text: draft } = await generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: `Write marketing copy for: ${input}`,
});
// Step 2: Evaluate quality
const { output: evaluation } = await generateText({
model: anthropic("claude-sonnet-4-5"),
output: Output.object({
schema: z.object({
score: z.number().min(1).max(10),
feedback: z.string(),
}),
}),
prompt: `Evaluate this copy: ${draft}`,
});
// Step 3: Improve if needed
if (evaluation.score < 7) {
const { text: improved } = await generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: `Improve this copy based on feedback:\n\nCopy: ${draft}\n\nFeedback: ${evaluation.feedback}`,
});
return improved;
}
return draft;
}
2. 并行模式
async function parallelReview(code: string) {
const [securityReview, performanceReview, maintainabilityReview] =
await Promise.all([
generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: `Review for security issues:\n\n${code}`,
}),
generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: `Review for performance issues:\n\n${code}`,
}),
generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: `Review for maintainability:\n\n${code}`,
}),
]);
return {
security: securityReview.text,
performance: performanceReview.text,
maintainability: maintainabilityReview.text,
};
}
3. 路由模式
async function routeQuery(query: string) {
// Classify the query
const { output: classification } = await generateText({
model: anthropic("claude-sonnet-4-5"),
output: Output.choice({
choices: ["technical", "billing", "general"] as const,
}),
prompt: `Classify this customer query: ${query}`,
});
// Route to appropriate handler
switch (classification) {
case "technical":
return handleTechnicalQuery(query);
case "billing":
return handleBillingQuery(query);
default:
return handleGeneralQuery(query);
}
}
4. 编排器-工作者模式
async function implementFeature(requirement: string) {
// Orchestrator: Break down the task
const { output: plan } = await generateText({
model: anthropic("claude-sonnet-4-5"),
output: Output.object({
schema: z.object({
tasks: z.array(
z.object({
type: z.enum(["frontend", "backend", "database"]),
description: z.string(),
})
),
}),
}),
prompt: `Break down this feature into tasks: ${requirement}`,
});
// Workers: Execute tasks in parallel
const results = await Promise.all(
plan.tasks.map((task) =>
generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: `Implement this ${task.type} task: ${task.description}`,
})
)
);
return results.map((r) => r.text);
}
5. 评估器-优化器模式
async function optimizeOutput(input: string, maxIterations = 3) {
let output = await generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: input,
});
for (let i = 0; i < maxIterations; i++) {
const { output: evaluation } = await generateText({
model: anthropic("claude-sonnet-4-5"),
output: Output.object({
schema: z.object({
isGood: z.boolean(),
improvements: z.array(z.string()),
}),
}),
prompt: `Evaluate this output: ${output.text}`,
});
if (evaluation.isGood) break;
output = await generateText({
model: anthropic("claude-sonnet-4-5"),
prompt: `Improve based on: ${evaluation.improvements.join(", ")}\n\nOriginal: ${output.text}`,
});
}
return output.text;
}
消息部分类型(v6)
| 部分类型 | 描述 | 属性 |
| ----------- | -------------------- | ------------------------------------------ | ---------- | -------- | --------------- |
| text | 文本内容 | text、isStreaming |
| tool-call | 工具调用 | name、args、state('input-streaming' | 'invoking' | 'output' | 'output-error') |
| reasoning | 模型思考 | text、isStreaming |
| file | 文件附件 | mediaType、url 或 data |
| source | RAG 来源引用 | url 或 documentId、title |
| step | 工作流边界 | 标记步骤边界 |
| data | 自定义数据 | 任意自定义负载 |
TypeScript 最佳实践
类型导入
import type {
UIMessage, // Message type from useChat
ModelMessage, // Message type for model functions
ToolCall, // Tool call information
TokenUsage, // Token consumption data
} from "ai";
Agent 类型安全
import type { InferAgentUIMessage } from "ai";
// Type-safe messages from agent
type MyAgentMessage = InferAgentUIMessage<typeof myAgent>;
强类型工具
import { tool } from "ai";
import { z } from "zod";
// Tool helper infers execute parameter types
const myTool = tool({
description: "My tool",
inputSchema: z.object({
param1: z.string(),
param2: z.number(),
}),
outputSchema: z.object({
result: z.string(),
}),
execute: async ({ param1, param2 }) => {
// param1 is inferred as string
// param2 is inferred as number
return { result: "success" };
},
});
常见模式
模式 1:简单聊天应用
客户端(app/page.tsx):
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const { messages, sendMessage, status } = useChat();
const [input, setInput] = useState('');
return (
<div>
{messages.map((m) => (
<div key={m.id}>
<strong>{m.role}:</strong>
{m.parts.map((part, i) =>
part.type === 'text' ? <span key={i}>{part.text}</span> : null
)}
</div>
))}
<form onSubmit={(e) => {
e.preventDefault();
sendMessage({ text: input });
setInput('');
}}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button disabled={status === 'streaming'}>Send</button>
</form>
</div>
);
}
服务端(app/api/chat/route.ts):
import { streamText, convertToModelMessages } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import type { UIMessage } from "ai";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-5"),
system: "You are a helpful assistant.",
messages: convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}
模式 2:具有结构化输出的聊天
import { streamText, convertToModelMessages, Output } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
import type { UIMessage } from "ai";
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-5"),
messages: convertToModelMessages(messages),
output: Output.object({
schema: z.object({
response: z.string(),
sentiment: z.enum(["positive", "neutral", "negative"]),
confidence: z.number().min(0).max(1),
}),
}),
});
return result.toUIMessageStreamResponse();
}
模式 3:使用多个工具的 Agent
import {
ToolLoopAgent,
tool,
stepCountIs,
createAgentUIStreamResponse,
} from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
const researchAgent = new ToolLoopAgent({
model: anthropic("claude-sonnet-4-5"),
instructions:
"You are a research assistant that can search and analyze information.",
tools: {
webSearch: tool({
description: "Search the web for information",
inputSchema: z.object({
query: z.string().describe("Search query"),
}),
execute: async ({ query }) => {
// Implement web search
return { results: ["..."] };
},
}),
analyze: tool({
description: "Analyze collected information",
inputSchema: z.object({
data: z.string().describe("Data to analyze"),
}),
execute: async ({ data }) => {
return { analysis: "..." };
},
}),
summarize: tool({
description: "Summarize findings",
inputSchema: z.object({
findings: z.array(z.string()),
}),
execute: async ({ findings }) => {
return { summary: "..." };
},
}),
},
stopWhen: stepCountIs(10),
});
// API Route
export async function POST(request: Request) {
const { messages } = await request.json();
return createAgentUIStreamResponse({
agent: researchAgent,
uiMessages: messages,
});
}
模式 4:使用嵌入的语义搜索
// app/api/search/route.ts
import { embed } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { query } = await req.json();
// Generate embedding for search query
const { embedding } = await embed({
model: openai.textEmbeddingModel("text-embedding-3-small"),
value: query,
});
// Use embedding for similarity search in vector database
// const results = await vectorDB.search(embedding);
return Response.json({ embedding, results: [] });
}
常见陷阱及解决方案
陷阱 1:使用已弃用的 generateObject/streamObject
// ❌ WRONG - Deprecated in v6
import { generateObject } from 'ai';
const result = await generateObject({
schema: z.object({...}),
prompt: '...',
});
// ✅ CORRECT - Use Output with generateText
import { generateText, Output } from 'ai';
const { output } = await generateText({
output: Output.object({ schema: z.object({...}) }),
prompt: '...',
});
陷阱 2:未使用 tool() 辅助函数定义工具
// ❌ WRONG - Plain object (WILL CAUSE BUILD FAILURE)
tools: {
myTool: {
description: 'My tool',
parameters: z.object({...}), // ❌ Wrong property name
execute: async ({...}) => {...},
},
}
// ✅ CORRECT - Use tool() helper (REQUIRED)
import { tool } from 'ai';
tools: {
myTool: tool({
description: 'My tool',
inputSchema: z.object({...}), // ⚠️ Use inputSchema
execute: async ({...}) => {...},
}),
}
陷阱 3:在 v6 中使用 v5 的 useChat API
// ❌ WRONG - v5 pattern
const { input, setInput, append } = useChat();
append({ content: "Hello", role: "user" });
// ✅ CORRECT - v6 pattern
const { sendMessage } = useChat();
const [input, setInput] = useState("");
sendMessage({ text: "Hello" });
陷阱 4:访问 message.content 而非 message.parts
// ❌ WRONG - v5 pattern
<div>{message.content}</div>
// ✅ CORRECT - v6 parts-based
<div>
{message.parts.map((part, i) =>
part.type === 'text' ? <span key={i}>{part.text}</span> : null
)}
</div>
陷阱 5:使用错误的响应方法
// ❌ WRONG - v5 method
return result.toDataStreamResponse();
// ✅ CORRECT - v6 method
return result.toUIMessageStreamResponse();
陷阱 6:忘记清理 MCP 客户端
// ❌ WRONG - no cleanup
const mcpClient = await createMCPClient({ transport });
const tools = await mcpClient.tools();
const response = streamText({ model, tools, prompt });
return response.toTextStreamResponse();
// ✅ CORRECT - cleanup in callbacks
const response = streamText({
model,
tools,
prompt,
onFinish: async () => {
await mcpClient.close();
},
onError: async () => {
await mcpClient.close();
},
});
迁移检查清单(v5 → v6)
从 v5 迁移到 v6 时,请更新:
- [ ] 将
generateObject/streamObject替换为generateText/streamText+Output - [ ] 在 useChat 中将
append替换为sendMessage - [ ] 从 useChat 解构中移除
input、setInput、handleInputChange - [ ] 为输入添加本地状态管理:
const [input, setInput] = useState('') - [ ] 将消息渲染从
message.content更新为message.parts.map(...) - [ ] 将 sendMessage 调用更新为使用
{ text: input }结构 - [ ] 将
toDataStreamResponse()替换为toUIMessageStreamResponse() - [ ] 将工具定义更新为使用带有
inputSchema的tool()辅助函数 - [ ] 更新模型 ID(例如,
claude-sonnet-4-5) - [ ] 考虑在 Agent 应用中使用
ToolLoopAgent - [ ] 更新 TypeScript 类型(
UIMessage、ModelMessage) - [ ] 如果使用客户端工具,添加
addToolOutput处理逻辑 - [ ] 考虑实现中间件,用于日志记录、缓存或护栏控制
决策指南
实现 AI SDK 功能时,请思考:
-
这是客户端还是服务端功能?
- 客户端:使用
useChathook - 服务端:使用
generateText或streamText - Agent:使用
ToolLoopAgent和createAgentUIStreamResponse
- 客户端:使用
-
我需要流式还是非流式处理?
- 流式聊天:
streamText+toUIMessageStreamResponse() - 非流式:
generateText - 简单文本流:
streamText+toTextStreamResponse()
- 流式聊天:
-
我需要结构化输出吗?
- 是:使用
Output.object()、Output.array()、Output.choice()或Output.json() - 通过
output参数传递给generateText或streamText
- 是:使用
-
我需要调用工具吗?
- 是:使用
tool()助手函数和inputSchema(zod)定义工具 - 将工具对象传递给
generateText、streamText或ToolLoopAgent
- 是:使用
-
我是在构建 Agent 吗?
- 是:使用
ToolLoopAgent类 - 根据需要配置
stopWhen、toolChoice、prepareStep - 在 API 路由中使用
createAgentUIStreamResponse
- 是:使用
-
我使用的消息格式正确吗?
- 客户端(useChat):返回包含
parts属性的UIMessage[] - 服务端:使用
convertToModelMessages()转换为ModelMessage[] - 使用
message.parts.map(...)渲染消息
- 客户端(useChat):返回包含
-
我的模型指定正确吗?
- 直接使用提供商:
anthropic('claude-sonnet-4-5') - Gateway(生产环境):
gateway('anthropic/claude-sonnet-4-5') - Embeddings:
openai.textEmbeddingModel('text-embedding-3-small')
- 直接使用提供商:
-
我需要 Embeddings 吗?
- 单个值使用
embed - 批量处理使用
embedMany - 使用
textEmbeddingModel()方法
- 单个值使用
-
我需要中间件吗?
- 日志记录:使用
transformParams/wrapGenerate编写自定义中间件 - 缓存:编写可存储结果的自定义中间件
- RAG:编写用于注入上下文的自定义中间件
- 护栏:编写用于过滤输出的自定义中间件
- 日志记录:使用
快速参考
| 任务 | 函数 | 关键参数 |
|---|---|---|
| 生成文本 | generateText() | model、prompt、system、tools、output |
| 流式生成文本 | streamText() | model、messages、tools、output、onFinish |
| 聊天 UI | useChat() | api、onToolCall、onFinish、onError |
| 构建 Agent | ToolLoopAgent | model、instructions、tools、stopWhen |
| 工具调用 | tool() | description、inputSchema、outputSchema、execute |
| 结构化输出 | Output.object() | schema(zod) |
| 文本 Embedding | embed() | model、value |
| 批量 Embedding | embedMany() | model、values |
| 消息转换 | convertToModelMessages() | messages(UIMessage[]) |
| MCP 集成 | createMCPClient() | transport |
| 添加中间件 | wrapLanguageModel() | model、middleware |
| Gateway 模型 | gateway() | "provider/model-name"(例如:"anthropic/claude-sonnet-4-5") |
其他资源
如有疑问,请查阅官方文档:
- 主文档:https://ai-sdk.dev/docs
- Agent:https://ai-sdk.dev/docs/agents
- API 参考:https://ai-sdk.dev/docs/reference
- 示例:https://ai-sdk.dev/examples
**请记住:**AI SDK v6 使用提供商函数模型规范(或在生产环境中使用 gateway())、基于 parts 的消息、使用 sendMessage 代替 append、使用 Output 助手代替 generateObject、使用 toUIMessageStreamResponse 代替 toDataStreamResponse,并且要求在 API 路由中使用 convertToModelMessages。