译:AI SDK 5
原文: https://vercel.com/blog/ai-sdk-5
作者: Lars Grammel, Nico Albanese, Josh
译者: Gemini 2.5 Pro
为全栈 AI 应用引入类型安全的聊天和 Agentic 循环控制
AI SDK 每周下载量超过 200 万次,是 TypeScript 和 JavaScript 领域领先的开源 AI 应用工具包。它统一的 provider API 让你能使用任何语言模型,并能与主流 Web 框架进行强大集成。
“当客户问我该如何构建他们的 agent 时,我总是推荐 AI SDK。这个行业发展太快,一切都在不断变化。AI SDK 是我迄今为止见过的唯一完美的抽象。v5 延续了这一记录。你可以看出来,它是由一群对 Typescript 痴迷的人构建的。一切都恰到好处。”
Ben Hylak, raindrop.ai
用 TypeScript 构建应用,就是为 Web 构建应用。今天,我们发布 AI SDK 5,这是首个为 React、Svelte、Vue 和 Angular 提供完全类型化、高度可定制的聊天集成的 AI 框架。
AI SDK 5 引入了:
让我们深入了解细节。
重新设计的 Chat
在 AI SDK 5 中,我们从头开始重建了聊天功能。我们采用了开发者们喜爱的、用于处理 LLM 的强大原语,并在其之上构建了一个世界级的 UI 集成,为你的整个应用提供了端到端的类型安全。从服务器到客户端,每一份数据、每一次工具调用和元数据都是完全类型化的。这代表了 Web AI 库的下一次进化。
分离 UI 和模型消息
在之前版本的 AI SDK 中,开发者面临的一大挑战是管理不同类型的消息,以及弄清楚如何正确地持久化聊天记录。
这是我们重建 useChat
时的核心考量,并由此创建了两种截然不同的消息类型:
- UIMessage: 这是你应用状态的事实来源,包含了所有消息、元数据、工具结果等等。我们建议使用 UIMessages 进行持久化,这样你总能恢复面向用户的正确聊天记录。
- ModelMessage: 这是一种为发送给语言模型而优化的精简表示。
我们在 API 中明确了这一区别:
// 显式地将你的 UIMessages 转换为 ModelMessages
const uiMessages: UIMessage[] = [ /* ... */ ]
const modelMessages = convertToModelMessages(uiMessages);
const result = await streamText({
model: openai('gpt-4o'),
// 将富含信息的 UIMessage 格式转换为 ModelMessage 格式
// 这里可以用任何返回 ModelMessage[] 的函数替代
messages: modelMessages,
});
// 完成时:获取完整的 UIMessage 数组用于持久化
return result.toUIMessageStreamResponse({
originalMessages: uiMessages,
onFinish: ({ messages, responseMessage }) => {
// 保存完整的 UIMessage 数组——你的全部事实来源
saveChat({ chatId, messages });
// 或者只保存响应消息
saveMessage({ chatId, message: responseMessage })
},
});
这种 UI 消息和模型消息的分离,让持久化变得直截了当。onFinish
回调以你需要的格式提供了所有消息,无需任何显式转换。
想看用 AI SDK 实现消息持久化的完整示例,请查阅我们的聊天机器人持久化文档和持久化模板仓库。
可定制的 UI 消息
使用 AI SDK 5,你可以定制 UIMessage,创建你自己的类型,其数据、工具和元数据的结构完全为你自己的应用量身打造。你可以将这个类型作为泛型参数传递给服务器端的 createUIMessageStream
和客户端的 useChat
,从而实现全栈类型安全。
// 只需定义一次你的自定义消息类型
import { UIMessage } from 'ai';
// ... 导入你的工具和数据部分类型
export type MyUIMessage = UIMessage<MyMetadata, MyDataParts, MyTools>;
// 在客户端使用它
const { messages } = useChat<MyUIMessage>();
// 并在服务器端使用它
const stream = createUIMessageStream<MyUIMessage>(/* ... */);
要了解更多,请查阅 UIMessage 文档。
数据部分 (Data Parts)
现代 AI 应用需要从服务器向客户端发送的,远不止是 LLM 的纯文本响应(例如,从状态更新到部分工具结果)。如果没有恰当的类型定义,流式传输自定义数据会把你的前端变成一团乱麻,到处都是运行时检查和类型断言。Data parts 通过提供一种一流的方式,从服务器向客户端流式传输任意、类型安全的数据,从而解决了这个问题,确保你的代码在应用增长时依然可维护。
在服务器端,你可以通过指定你的部分类型(例如 data-weather
)然后传递数据来流式传输一个数据部分。你可以通过指定一个 ID 来更新同一个数据部分:
// 在服务器端,创建一个 UIMessage 流
// 用你的自定义消息类型为流指定类型
const stream = createUIMessageStream<MyUIMessage>({
async execute({ writer }) {
// 如果没有 LLM 调用,则手动写入开始步骤
const dataPartId = 'weather-1';
// 1. 发送初始的加载状态
writer.write({
type: 'data-weather', // 根据 MyUIMessage 进行类型检查
id: dataPartId,
data: { city: 'San Francisco', status: 'loading' },
});
// 2. 稍后,用最终结果更新同一个部分(相同 id)
writer.write({
type: 'data-weather',
id: dataPartId,
data: { city: 'San Francisco', weather: 'sunny', status: 'success' },
});
},
});
在客户端,你就可以渲染这个特定的部分了。当你使用相同的 ID 时,AI SDK 会用新的数据部分替换现有的:
// 在客户端,数据部分是完全类型化的
const { messages } = useChat<MyUIMessage>();
{
messages.map(message =>
message.parts.map((part, index) => {
switch (part.type) {
case 'data-weather':
return (
<div key={index}>
{/* TS 知道 part.data 有 city、status 和可选的 weather 属性 */}
{part.data.status === 'loading'
? `正在获取 ${part.data.city} 的天气...`
: `${part.data.city} 的天气是:${part.data.weather}`}
</div>
);
}
}),
);
}
还有些情况,你希望发送一些不想持久化的数据,只是用来传达状态更新,或者对 UI 做其他更改——这就是瞬时数据部分和 onData
钩子发挥作用的地方。
瞬时部分会被发送到客户端,但不会被添加到消息历史中。它们只能通过 onData
useChat 处理器访问:
// 服务器端
writer.write({
type: 'data-notification',
data: { message: '处理中...', level: 'info' },
transient: true, // 不会被添加到消息历史中
});
// 客户端
const [notification, setNotification] = useState();
const { messages } = useChat({
onData: ({ data, type }) => {
if (type === 'data-notification') {
setNotification({ message: data.message, level: data.level });
}
},
});
要了解更多,请查阅 data parts 文档。
类型安全的工具调用
useChat 中的工具调用已经被重新设计,使用了特定于类型的部件标识符。现在每个工具都会创建一个像 tool-TOOLNAME
这样的部件类型,而不是使用通用的 tool-invocation
部件。
AI SDK 5 在此基础上进行了三项改进:
- 类型安全:通过在你的自定义消息类型中定义工具的结构,你可以为输入(工具的
inputSchema
)和输出(工具的outputSchema
)都获得端到端的类型安全。 - 自动输入流式传输:工具调用的输入现在默认是流式的,在模型生成它们时提供部分更新。
- 明确的错误状态:工具执行错误被限制在工具本身,并且可以重新提交给 LLM。
这些功能结合在一起,使你能够构建可维护的 UI,向用户精确展示整个工具执行过程中的情况——从初始调用、流式更新,到最终结果或错误:
// 在客户端,工具部分使用新结构完全类型化
const { messages } = useChat<MyUIMessage>();
{
messages.map(message => (
<>
{message.parts.map(part => {
switch (part.type) {
// 具有特定(`tool-${toolName}`)类型的静态工具
case 'tool-getWeather':
// 用于流式传输和错误处理的新状态
switch (part.state) {
case 'input-streaming':
// 自动流式传输的部分输入
return <div>正在获取 {part.input.location} 的天气...</div>;
case 'input-available':
return <div>正在获取 {part.input.location} 的天气...</div>;
case 'output-available':
return <div>天气是: {part.output}</div>;
case 'output-error':
// 带有信息的明确错误状态
return <div>错误: {part.errorText}</div>;
}
}
})}
</>
));
}
聊天功能还支持动态工具(下文有更多介绍)。动态工具(例如,来自 MCP 服务器的工具)在开发时是未知的,可以使用 dynamic-tool
部分来渲染:
const { messages } = useChat<MyUIMessage>();
{
messages.map(message => (
<>
{message.parts.map(part => {
switch (part.type) {
// 动态工具使用通用的 `dynamic-tool` 类型
case 'dynamic-tool':
return (
<div key={index}>
<h4>工具: {part.toolName}</h4>
{part.state === 'input-streaming' && (
<pre>{JSON.stringify(part.input, null, 2)}</pre>
)}
{part.state === 'output-available' && (
<pre>{JSON.stringify(part.output, null, 2)}</pre>
)}
{part.state === 'output-error' && (
<div>错误: {part.errorText}</div>
)}
</div>
);
}
})}
</>
));
}
要了解更多信息,请参阅下方的动态工具部分或查阅工具调用文档。
消息元数据
对于关于消息的信息,例如时间戳、模型 ID 或 token 数量,你现在可以为消息附加类型安全的元数据。你可以用它来附加与你的应用相关的元数据。
从服务器发送元数据:
// 在服务器端
const result = streamText({
/* ... */
});
return result.toUIMessageStreamResponse({
messageMetadata: ({ part }) => {
if (part.type === "start") {
return {
// 这个对象会根据你的元数据类型进行检查
model: "gpt-4o",
};
}
if (part.type === "finish") {
return {
model: part.response.modelId,
totalTokens: part.totalUsage.totalTokens,
};
}
},
});
然后你可以在客户端访问它:
// 在客户端
const { messages } = useChat<MyUIMessage>();
{
messages.map(message => (
<div key={message.id}>
{/* TS 知道 message.metadata 可能有 model 和 totalTokens */}
{message.metadata?.model && (
<span>模型: {message.metadata.model}</span>
)}
{message.metadata?.totalTokens && (
<span>{message.metadata.totalTokens} tokens</span>
)}
</div>
));
}
当你在消息生命周期的不同点更新元数据值时,客户端总是显示最新的值。
要了解更多信息,请查阅消息元数据文档。
模块化架构与可扩展性
新的 useChat
hook 以模块化为核心进行了重新设计,支持三种强大的扩展模式:
- 灵活的传输层: 将默认的基于
fetch
的传输层换成自定义实现。你可以使用 WebSockets 进行实时通信,或者在没有后端的情况下直接连接到 LLM 提供商,这适用于纯客户端应用、浏览器扩展和注重隐私的场景。要了解更多,请查阅传输层文档。 - 解耦的状态管理: hook 的状态是完全解耦的,可以与 Zustand、Redux 或 MobX 等外部状态管理库无缝集成。你可以在整个应用中共享聊天状态,同时保留
useChat
的所有强大功能。 - 与框架无关的聊天: 使用暴露的
AbstractChat
类为任何框架构建你自己的聊天 hook。你可以创建自定义集成,同时保持与 AI SDK 生态系统的完全兼容。
支持 Vue、Svelte 和 Angular
AI SDK 5 将重新设计的聊天体验带到了所有主流 Web 框架。Vue 和 Svelte 现在与 React 的功能完全对等,我们还引入了对 Angular 的支持。
现在所有框架都获得了同样强大的功能:满足你应用特定需求的自定义消息类型、用于流式传输任意类型化数据的 data parts、带有自动输入流的完全类型化工具调用,以及类型安全的消息元数据。无论你是在 React 中使用 useChat
,还是使用 Vue 的组合式 API、Svelte 的 stores,或 Angular 的 signals,你都在使用同样强大的原语和端到端的类型安全。
要了解更多,请查看 Vue、Svelte 和 Angular 示例。
SSE 流式传输
AI SDK 现在使用服务器发送事件 (SSE) 作为从服务器到客户端流式传输数据的标准。所有主流浏览器和环境都原生支持 SSE。这一变化使我们的流式协议更健壮,更容易用标准浏览器开发工具进行调试,也更易于在其上构建。
Agentic 循环控制
构建可靠的 AI agent 需要对执行流程和上下文进行精确控制。在 AI SDK 5 中,我们引入了一些原语,让你能完全控制 agent 的运行方式以及它们在每一步拥有的上下文和工具。
AI SDK 5 引入了三个用于构建 agent 的功能:
- stopWhen:定义何时停止工具调用循环。
- prepareStep:为每一步调整参数。
- Agent 抽象:使用预定义设置的
generateText
和streamText
。
stopWhen
当你使用 generateText
和 streamText
发出请求时,默认只运行一步。stopWhen
参数将你的单个请求转换为一个工具调用循环,该循环会持续进行,直到:
stopWhen
条件被满足,或- 模型生成了文本而不是工具调用(这永远是一个停止条件)
常见的停止条件包括:
- 步骤限制:
stepCountIs(5)
- 最多运行 5 步 - 特定工具:
hasToolCall('finalAnswer')
- 当某个特定工具被调用时停止
import { openai } from "@ai-sdk/openai";
import { generateText, stepCountIs, hasToolCall } from "ai";
const result = await generateText({
model: openai("gpt-4o"),
tools: {
/* 你的工具 */
},
// 在 5 步后停止工具调用循环,或者
// 当 weather 工具被调用时
stopWhen: [stepCountIs(5), hasToolCall("weather")],
});
prepareStep
stopWhen
让你的 agent 持续运行,而 prepareStep
则让你能控制每一步的设置。
在每一步执行前,你可以调整:
- 消息:压缩或过滤上下文以保持在限制内,或过滤掉不相关的 token。
- 模型:根据任务复杂度切换模型。
- 系统提示:为不同任务调整指令。
- 工具:根据需要启用/禁用工具。
- 工具选择:在需要时强制使用特定工具(或不使用)。
const result = await streamText({
model: openai('gpt-4o'),
messages: convertToModelMessages(messages),
tools: {
/* 你的工具 */
},
prepareStep: async ({ stepNumber, messages }) => {
if (stepNumber === 0) {
return {
// 第一步使用不同的模型
model: openai('gpt-4o-mini'),
// 强制选择一个特定工具
toolChoice: { type: 'tool', toolName: 'analyzeIntent' },
};
}
// 对于较长的对话,压缩上下文
if (messages.length > 10) {
return {
// 使用一个具有更大上下文窗口的模型
model: openai('gpt-4.1'),
messages: messages.slice(-10),
};
}
},
});
Agent 抽象
Agent
类提供了一种面向对象的方法来构建 agent。它不增加新功能——所有你能用 Agent
做的事,都可以用 generateText
或 streamText
完成。它的作用是让你能够封装你的 agent 配置和执行过程:
import { openai } from "@ai-sdk/openai";
import { Experimental_Agent as Agent, stepCountIs } from "ai";
const codingAgent = new Agent({
model: openai("gpt-4o"),
system: "你是一个编程 agent。你专注于 Next.js 和 TypeScript。",
stopWhen: stepCountIs(10),
tools: {
/* 你的工具 */
},
});
// 调用 `generateText`
const result = codingAgent.generate({
prompt: "构建一个 AI 编程 agent。",
});
// 调用 `streamText`
const result = codingAgent.stream({
prompt: "构建一个 AI 编程 agent。",
});
实验性的语音生成与转录
AI SDK 5 将我们统一的 provider 抽象扩展到了语音领域。就像我们对文本和图像生成所做的那样,我们为语音生成和转录都带来了同样一致、类型安全的接口。无论你使用 OpenAI、ElevenLabs 还是 DeepGram,你都使用同样熟悉的 API 模式,并且只需修改一行代码就能切换 provider。
import {
experimental_generateSpeech as generateSpeech,
experimental_transcribe as transcribe,
} from 'ai';
import { openai } from '@ai-sdk/openai';
// 文本转语音:从文本生成音频
const { audio } = await generateSpeech({
model: openai.speech('tts-1'),
text: '你好,世界!',
voice: 'alloy',
});
// 语音转文本:将音频转录为文本
const { text, segments } = await transcribe({
model: openai.transcription('whisper-1'),
audio: await readFile('audio.mp3'),
});
工具改进
AI SDK 5 通过全面的改进增强了工具能力,包括动态工具、provider 执行的函数、生命周期钩子以及贯穿整个工具调用过程的类型安全。
参数与结果重命名
在 AI SDK 5 中,我们通过重命名关键概念,使我们的工具定义接口更紧密地与模型上下文协议 (MCP) 规范保持一致:
- parameters → inputSchema:这个重命名更好地描述了该 schema 的用途——验证和类型化工具的输入。
- result → output:同样,工具的输出现在也有一致的命名。
AI SDK 5 还引入了一个可选的 outputSchema
属性,它与 MCP 规范保持一致,并为客户端工具调用提供了类型安全。
这些变化使工具定义更直观,并与新兴的行业标准保持一致:
// 之前 (v4)
const weatherTool = tool({
name: 'getWeather',
parameters: z.object({ location: z.string() }),
execute: async ({ location }) => {
return `在 ${location} 的天气:晴,72°F`;
}
});
// 之后 (v5)
const weatherTool = tool({
description: '获取一个地点的天气',
inputSchema: z.object({ location: z.string() }),
outputSchema: z.string(), // v5 新增 (可选)
execute: async ({ location }) => {
return `在 ${location} 的天气:晴,72°F`;
}
});
动态工具
AI 应用常常需要处理一些无法预先知道的工具:
- 没有 schema 的 MCP (模型上下文协议) 工具
- 在运行时加载的用户自定义函数
- 外部工具提供商
动态工具和 dynamicTool
函数使得工具的输入输出类型可以在运行时而非开发时确定。动态工具与静态工具是分开的,从而同时为你提供类型安全和灵活性。
import { dynamicTool } from 'ai';
import { z } from 'zod';
const customDynamicTool = dynamicTool({
description: '执行一个自定义的用户定义函数',
inputSchema: z.object({}),
// input 的类型是 'unknown'
execute: async input => {
const { action, parameters } = input as any;
// 执行你的动态逻辑
return {
result: `用 ${JSON.stringify(parameters)} 执行了 ${action}`,
};
},
});
const weatherTool = tool({ /* ... */ })
const result = await generateText({
model: 'openai/gpt-4o',
tools: {
// 具有已知类型的静态工具
weatherTool,
// 动态工具
customDynamicTool,
},
onStepFinish: ({ toolCalls, toolResults }) => {
// 类型安全的迭代
for (const toolCall of toolCalls) {
if (toolCall.dynamic) {
// 动态工具:input 是 'unknown'
console.log('动态:', toolCall.toolName, toolCall.input);
continue;
}
// 静态工具:完整的类型推断
switch (toolCall.toolName) {
case 'weather':
console.log(toolCall.input.location); // 类型为 string
break;
}
}
},
});
要了解更多信息,请查阅动态工具文档。
由 Provider 执行的工具
许多 AI provider 都引入了由 provider 执行的工具。当这些工具被调用时,provider 会执行该工具并将工具结果作为响应的一部分返回(例如 OpenAI 的网页搜索和文件搜索,xAI 的网页搜索等)。
AI SDK 现在原生支持由 provider 执行的工具,会自动将结果附加到消息历史中,无需任何额外配置。
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';
const result = await generateText({
model: openai.responses('gpt-4o-mini'),
tools: {
web_search_preview: openai.tools.webSearchPreview({}),
},
// ...
});
工具生命周期钩子
AI SDK 5 引入了精细的工具生命周期钩子(onInputStart
、onInputDelta
、onInputAvailable
),可以与数据部分配合使用,将与输入相关的信息(例如状态更新)发送回客户端。
const weatherTool = tool({
description: 'Get the weather for a given city',
inputSchema: z.object({ city: z.string() }),
onInputStart: ({ toolCallId }) => {
console.log('Tool input streaming started:', toolCallId);
},
onInputDelta: ({ inputTextDelta, toolCallId }) => {
console.log('Tool input delta:', inputTextDelta);
},
onInputAvailable: ({ input, toolCallId }) => {
console.log('Tool input ready:', input);
},
execute: async ({ city }) => {
return `Weather in ${city}: sunny, 72°F`;
},
});
工具 Provider 选项
AI SDK 5 增加了对工具级 provider 选项的支持。例如,你可以用它来为多步 agent 缓存 Anthropic 的工具定义,从而减少 token 使用量、处理时间和成本:
const result = await generateText({
model: anthropic('claude-3-5-sonnet-20240620'),
tools: {
cityAttractions: tool({
inputSchema: z.object({ city: z.string() }),
// 将特定于 provider 的选项应用于单个工具
providerOptions: {
anthropic: {
cacheControl: { type: 'ephemeral' },
},
},
execute: async ({ city }) => {
// 实现
},
}),
},
});
V2 规范
AI SDK 的基础是规范层,它标准化了不同的语言模型、嵌入模型等如何接入像 streamText
这样的函数。规范层是 AI SDK provider 架构的基石。
在 AI SDK 5 中,我们已将所有规范更新到 V2。这些新规范包含了底层 API 能力的变化(如 provider 执行的工具),并具备了像 provider 元数据和选项这样的可扩展机制。它们将成为 AI SDK 5 及未来的基础。
要了解更多关于 V2 规范的信息,请访问自定义 provider 文档。
全局 Provider
AI SDK 5 包含一个全局 provider 功能,让你仅用一个普通的模型 ID 字符串就能指定模型:
import { streamText } from 'ai';
const result = await streamText({
model: 'openai/gpt-4o', // Uses the global provider (defaults to AI Gateway)
prompt: 'Invent a new holiday and describe its traditions.',
});
默认情况下,全局 provider 设置为 Vercel AI Gateway。
自定义全局 Provider
你可以设置自己偏好的全局 provider:
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
// 在启动时初始化一次:
globalThis.AI_SDK_DEFAULT_PROVIDER = openai;
// 在你代码库的其他地方:
const result = streamText({
model: 'gpt-4o', // 使用 OpenAI provider,无需前缀
prompt: '发明一个新的节日并描述它的传统。',
});
这简化了 provider 的使用,让你在切换 provider 时更容易,而无需在整个代码库中更改模型引用。
访问原始响应
当你需要完全控制,或想在官方支持前实现新功能时,AI SDK 提供了对原始请求和响应数据的完整访问。这个“逃生通道”对于调试、实现特定于 provider 的功能或处理边缘情况来说,非常宝贵。
原始流式数据块
使用 AI SDK 5,你可以通过流式函数访问从 provider 接收到的原始数据块:
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
const result = streamText({
model: openai("gpt-4o-mini"),
prompt: "发明一个新的节日并描述它的传统。",
includeRawChunks: true,
});
// 通过 fullStream 访问原始数据块
for await (const part of result.fullStream) {
if (part.type === "raw") {
// 访问特定于 provider 的数据结构
// 例如,OpenAI 的 choices、usage 等
console.log("原始数据块:", part.rawValue);
}
}
请求和响应体
你还可以访问发送给 provider 的确切请求和收到的完整响应:
const result = await generateText({
model: openai("gpt-4o"),
prompt: "写一首关于调试的俳句",
});
// 访问发送给 provider 的原始请求体
// 查看确切的提示格式、参数等
console.log("请求:", result.request.body);
// 访问来自 provider 的原始响应体
// 包含元数据的完整 provider 响应
console.log("响应:", result.response.body);
支持 Zod 4
AI SDK 5 支持 Zod 4。你可以在所有启用验证的 API 中,使用 Zod 3 或新的 Zod 4 mini schema 来进行输入和输出验证。
我们建议新项目使用 Zod 4。请遵循 Zod v4 文档上的建议。
迁移到 AI SDK 5
AI SDK 5 包含一些破坏性变更,移除了已弃用的 API。我们通过自动化的迁移工具简化了迁移过程。你可以运行我们的自动化 codemod 来处理部分变更。
npx @ai-sdk/codemod upgrade
要详细了解所有变更以及可能需要的手动步骤,请参阅我们的 AI SDK 5 迁移指南。该指南包含分步说明和示例,以确保平稳更新。
开始使用
有了重新设计的聊天、agentic 控制和新的规范,现在是开始使用 AI SDK 构建 AI 应用的最佳时机。
- 开始一个新的 AI 项目: 准备好构建新东西了吗?查看我们最新的指南。
- 探索我们的模板: 访问我们的模板库,看看 AI SDK 的实际应用。
- 迁移到 v5: 正在升级现有项目?我们全面的迁移指南和 codemod 已准备好提供帮助。
- Chat SDK: 查看 Chat SDK 开源模板,它可以帮助你快速构建强大的聊天机器人应用,而无需从零开始。
- 加入社区: 在我们的 GitHub Discussions 中分享你正在构建的东西并获得帮助。