Skip to content
Vercel AI SDK Provider 机制:多模型统一调用设计
目录
- Provider 机制概述
- 统一调用入口:generateText 与 streamText
- 模型参数向 Provider 的传递路径
- Provider 接口与适配器
- 模型 ID 解析与 Provider 路由
- providerOptions 与模型特有功能透传
- 流式输出:协议转换与 ReadableStream
- Tool Calling 的跨模型兼容层
- 官方 Provider 适配方式:OpenAI、Anthropic、Google
- 自定义 Provider:接入 OpenAI-compatible 服务
- 多模型切换、Fallback 与错误归一化
- TypeScript 类型推导与扩展工程实践
- 生态与统一 API 的趋势
- 参考链接
Provider 机制概述
Vercel AI SDK(npm 包名为 ai)是一个与具体模型提供商无关(provider-agnostic)的 TypeScript 工具包,用于构建 AI 应用和智能体。它支持 Next.js、React、Svelte、Vue、Angular 等 UI 框架,也支持 Node.js 运行时[1]。
在 AI SDK 的分层架构中,Provider 是连接「高层 API」与「具体模型服务」的中间层:
generateText / streamText / generateObject ...
│
▼
model 实例
│
▼
┌──────────────────────┐
│ Provider 抽象层 │
│ createOpenAI() │
│ createAnthropic() │
│ createGoogle() │
│ createXAI() │
└──────────────────────┘
│
▼
OpenAI / Anthropic / Google
/ xAI 等模型 HTTP APIProvider 的职责可以概括为四件事:
- 封装认证与请求构造:把 API key、请求头、URL 组织成对应提供商期望的格式。
- 屏蔽响应格式差异:OpenAI 的 Chat Completions 响应、Anthropic 的 Messages 响应、Google 的 GenerateContent 响应各有不同的 JSON 结构,Provider 层负责将差异消化掉。
- 提供模型实例:通过
openai('gpt-4o')、anthropic('claude-3-7-sonnet-latest')这样的工厂函数,返回一个满足LanguageModel接口的模型对象。 - 支持流式与工具调用:把不同提供商的流式分帧格式(如 SSE、文本增量)统一成一个接口。
安装 AI SDK 需要 Node.js 22+,核心包通过 npm 安装[1]:
bash
npm install ai使用某个具体提供商时,还需要安装对应的 Provider 包:
bash
npm install @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google上面的代码安装了三个官方 Provider 包,分别对应 OpenAI、Anthropic 和 Google[1]。每个包导出一个工厂函数,通过工厂函数创建模型实例后交给 generateText 或 streamText 调用。
统一调用入口:generateText 与 streamText
generateText 是 AI SDK 中最基本的高层 API。它接收一个包含 model 和 prompt 等字段的参数对象,返回一个包含 text 字段的结果对象[7]。
ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = await generateText({
model: openai('gpt-4o'),
prompt: '解释一下 Promise 的工作原理',
});
console.log(result.text);result.text 是归一化后的完整文本。无论底层是 OpenAI、Anthropic 还是 Google,高层调用方拿到的都是同一个 result 结构。
streamText 则用于流式返回[8]。它不会等到全部生成完毕才返回,而是产生一个可迭代的文本流:
ts
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const result = streamText({
model: anthropic('claude-3-7-sonnet-latest'),
prompt: '写一首以“编译”为主题的五言绝句',
});
// result.textStream 是一个 AsyncIterable<string>
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}streamText 返回的对象包含多个流,常用的有:
| 属性 | 类型 | 说明 |
|---|---|---|
textStream | AsyncIterable<string> | 增量文本流,每个 chunk 是一段文本 |
text | Promise<string> | 最终完整文本,在流结束后可读取 |
toolCalls | Promise<ToolCall[]> | 模型发出的工具调用列表 |
finishReason | Promise<string> | 结束原因,如 'stop'、'tool-calls' |
注意:textStream 是单次可消费的异步迭代器,适合逐段输出场景;text 是完整文本的 Promise,适合整体获取场景。streamText 内部会对文本增量做累积,因此 text 的读取不依赖外部是否消费过 textStream,实际使用时按需求选择其中一种即可。
模型参数向 Provider 的传递路径
调用 generateText({ model, prompt, temperature, maxTokens, topP }) 时,这些参数并不都直接原样发给模型服务器。
传递路径如下:
generateText接收用户的参数对象。- AI SDK 核心包从参数中提取模型无关的部分(
prompt、system、messages等)。 - AI SDK 将
temperature、maxTokens、topP等采样参数放入一个标准化结构。 - 核心包调用
model.doGenerate()方法,把标准化参数传入 Provider。 - Provider 将这些标准化参数映射为具体厂商 API 的字段名,例如把
maxTokens映射为 OpenAI 的max_tokens、Google 的maxOutputTokens。 - Provider 发起 HTTP 请求,解析响应,再映射回统一的响应结构。
因为参数在核心层是标准化的,maxTokens 在 OpenAI、Anthropic、Google 三个 Provider 中写法一致,由各 Provider 负责转换为各自的字段名。
Provider 接口与适配器
load 方法、语言模型与嵌入模型
Provider 包导出的工厂函数创建的是「模型实例」。在 AI SDK 内部,模型实例需要实现 LanguageModel 或 EmbeddingModel 接口。
LanguageModel 是最核心的接口,它描述的是一种「能够基于文本输入生成文本」的模型。该接口包含[9]:
- 模型标识:
modelId、provider等字段,用于日志、路由和类型推导。 doGenerate方法:执行非流式调用,返回统一格式的结果。doStream方法:执行流式调用,返回统一格式的流。supportsStructuredOutputs、supportsToolCalls等能力标志:描述模型是否支持某些特性。
EmbeddingModel 则用于文本向量化。
Provider 工厂函数的返回值是一个对象,这个对象本身也被称为 Provider。以 @ai-sdk/openai 为例:
ts
import { createOpenAI } from '@ai-sdk/openai';
const myOpenAI = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const model = myOpenAI('gpt-4o');myOpenAI 是一个 Provider 实例。调用 myOpenAI('gpt-4o') 返回一个 LanguageModel 实例。
Provider 实例上还有一个可选的 load 方法,用于从模型字符串创建模型实例。例如:
ts
// 内部大致逻辑
const provider = {
(modelId: string) => createLanguageModel(modelId),
load(modelId: string) {
return this(modelId);
},
};load 方法的存在让 AI SDK 可以在不直接引用具体 Provider 包的情况下,通过模型 ID 字符串路由到正确的 Provider。
请求与响应的统一数据结构
Provider 需要实现的统一数据结构包括三类。
1. 参数结构(Request)
核心包传给 Provider 的是一个标准化参数对象,包含:
| 字段 | 说明 |
|---|---|
prompt | 消息数组,每条消息有 role 和 content |
temperature | 采样温度 |
maxTokens | 最大生成 token 数 |
topP | 核采样参数 |
frequencyPenalty | 频率惩罚 |
presencePenalty | 存在惩罚 |
2. 非流式响应结构(Response)
doGenerate 的返回值包含:
| 字段 | 说明 |
|---|---|
text | 生成的文本 |
toolCalls | 工具调用数组 |
finishReason | 停止原因 |
usage | token 用量,包含 promptTokens、completionTokens、totalTokens |
rawResponse | Provider 的原始响应,便于调试 |
3. 流式响应结构(Stream)
doStream 的返回值包含一个 stream 字段,它是一个 ReadableStream,每个 chunk 的类型是统一的事件对象。chunk 可能携带 text-delta、tool-call、finish、error 等不同 payload。
AI SDK 的架构可以概括为:核心包定义接口,Provider 包实现接口。这个边界使得新增一个模型提供商不需要修改核心包。
模型 ID 解析与 Provider 路由
AI SDK 5.x 之后推荐使用「统一 Provider 架构」(Unified Provider Architecture)。在这种架构下,模型以字符串形式表示,格式为 providerId/modelId,例如:
openai/gpt-4oanthropic/claude-3-7-sonnet-latestgoogle/gemini-3-flashxai/grok-4.5
通过统一模型字符串,可以在不改动高层调用的情况下切换提供商。例如将 model 从 'openai/gpt-5.4' 改为 'google/gemini-3-flash'[1]:
ts
import { generateText } from 'ai';
// 使用统一模型字符串
const result = await generateText({
model: 'openai/gpt-5.4',
prompt: 'Hello',
});换成 Google 模型时,高层调用不变:
ts
const result = await generateText({
model: 'google/gemini-3-flash',
prompt: 'Hello',
});这里的路由动作发生在「模型字符串解析」阶段。AI SDK 会:
- 将
'openai/gpt-5.4'按/切分为providerId和modelId。 - 查找
providerId对应的 Provider 实例。 - 调用 Provider 的
load(modelId)方法返回模型实例。
这种设计将「选择哪个模型」从业务代码中解耦出来。一个业务逻辑可以同时面对多个提供商,只需在调用处传入不同的模型字符串。
需要明确的是:providerId 需要与某个 Provider 实例关联。在代码中通常这样注册:
ts
import { createOpenAI } from '@ai-sdk/openai';
import { createAnthropic } from '@ai-sdk/anthropic';
import { registry } from 'ai';
export const myRegistry = registry({
'openai': createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
'anthropic': createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
});之后 generateText({ model: 'openai/gpt-4o' }) 就会路由到注册的 OpenAI Provider。
如果只安装 Provider 包而没有注册,AI SDK 在部分配置下也能识别常见 Provider。具体规则以当前版本的官方文档为准。
providerOptions 与模型特有功能透传
标准化参数解决了大部分调用需求,但某些模型功能是某个提供商特有的。例如:
- OpenAI 的
logprobs、store - Anthropic 的
thinking参数 - Google 的
geminiSearch
AI SDK 通过 providerOptions 透传这些参数。其设计原则是:核心包不感知具体参数,只是原样传递;Provider 负责解析并发送给模型 API。
ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
const result = await generateText({
model: openai('gpt-4o'),
prompt: 'What is the capital of France?',
providerOptions: {
openai: {
logprobs: true,
},
},
});providerOptions 的 openai 字段会传给 OpenAI Provider。Anthropic 的 thinking 也可以这样传入:
ts
const result = await generateText({
model: anthropic('claude-3-7-sonnet-latest'),
prompt: 'Solve this step by step',
providerOptions: {
anthropic: {
thinking: {
type: 'enabled',
budgetTokens: 4096,
},
},
},
});注意:providerOptions 不会经过核心包的校验,因此拼写错误不会被 TypeScript 抛出(除非 Provider 包自带了类型定义)。使用时应确认对应 Provider 包的文档。
流式输出:协议转换与 ReadableStream
不同模型的流式协议差异很大:
- OpenAI 使用 SSE(Server-Sent Events),事件类型包括
chat.completion.chunk。 - Anthropic 使用 SSE,事件类型包括
content_block_delta、message_delta等。 - Google 使用逐行 JSON 流或 SSE,取决于参数设置。
AI SDK 的流式输出架构把「传输协议」和「业务消费」分开:
模型 HTTP 流(SSE/NDJSON/自定义)
│
▼
Provider 包解析协议
│
▼
统一事件流 ReadableStream
{ type: 'text-delta', textDelta: '...' }
{ type: 'tool-call', toolCallId: '...' }
{ type: 'finish', finishReason: 'stop' }
│
▼
核心包将事件流转换为
textStream / toolCalls / finishReasonProvider 内部的大致逻辑是读取上游 HTTP 响应体,按协议拆分成事件,再将每个事件映射为统一的 stream chunk。
如果 Provider 本身不支持流式,AI SDK 是否会降级为非流式取决于具体实现。官方 Provider 通常都支持流式。
对于自定义 Provider,实现 doStream 时需要注意返回的 stream 必须是标准 ReadableStream,chunk 对象要有明确的 type 字段。
Tool Calling 的跨模型兼容层
工具调用(tool calling)是 AI 应用的核心能力。不同模型的工具调用格式存在差异:
- OpenAI 用
tools数组,参数为 JSON Schema,响应中的tool_calls包含function.name和function.arguments(JSON 字符串)。 - Anthropic 用
tools数组,参数也是 JSON Schema,但响应中的tool_useblock 不同。 - Google 用
functionDeclarations,响应中的functionCall有不同结构。
AI SDK 的做法是定义一套统一的工具描述格式,然后由 Provider 转换成各自的格式:
ts
const result = await generateText({
model: openai('gpt-4o'),
tools: {
weather: {
description: '查询指定城市的天气',
parameters: {
type: 'object',
properties: {
city: { type: 'string' },
},
required: ['city'],
},
execute: async ({ city }) => {
return { temperature: 24, condition: '晴' };
},
},
},
prompt: '北京今天天气怎么样?',
});result.toolCalls 返回归一化后的工具调用列表[10]。execute 函数由核心包负责调用,结果会自动回传给模型(如果有后续对话)。如果不需要执行,只声明工具,模型会返回 toolCalls,由业务代码自行处理。
归一化后的 toolCalls 结构大致如下:
ts
[
{
toolCallId: 'call_123',
toolName: 'weather',
args: { city: '北京' },
},
]args 是反序列化后的对象,而不是 JSON 字符串。这是跨模型兼容的关键——AI SDK 在 Provider 层完成反序列化,上层拿到的是普通对象。
对于不支持工具调用的模型,传入 tools 后 AI SDK 会抛出运行时错误,因为核心层无法在没有工具调用能力的情况下执行 execute 流程。具体错误信息因版本而异。
官方 Provider 适配方式:OpenAI、Anthropic、Google
OpenAI
ts
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const model = openai('gpt-4o');@ai-sdk/openai 还支持使用 OpenAI 兼容的第三方服务。此时传入 baseURL 即可:
ts
const openai = createOpenAI({
baseURL: 'https://api.example.com/v1',
apiKey: 'sk-xxx',
});Anthropic
ts
import { createAnthropic } from '@ai-sdk/anthropic';
const anthropic = createAnthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const model = anthropic('claude-3-7-sonnet-latest');Google
ts
import { createGoogleGenerativeAI } from '@ai-sdk/google';
const google = createGoogleGenerativeAI({
apiKey: process.env.GOOGLE_API_KEY,
});
const model = google('gemini-3-flash');三者创建出的 model 都满足同一接口,因此可以互换:
ts
import type { LanguageModel } from 'ai';
async function callModel(model: LanguageModel, prompt: string) {
const { text } = await generateText({ model, prompt });
return text;
}
await callModel(openai('gpt-4o'), 'hello');
await callModel(anthropic('claude-3-7-sonnet-latest'), 'hello');
await callModel(google('gemini-3-flash'), 'hello');callModel 内部不感知具体提供商。传入不同 Provider 创建的模型实例,得到的调用行为一致。
自定义 Provider:接入 OpenAI-compatible 服务
很多模型服务商提供 OpenAI-compatible 的 API。这种情况下,最简单的做法是使用 @ai-sdk/openai 并指定 baseURL。如果服务完全兼容 OpenAI 的 /chat/completions 接口,无需编写额外代码:
ts
import { createOpenAI } from '@ai-sdk/openai';
const localAI = createOpenAI({
baseURL: 'http://localhost:8080/v1',
apiKey: 'local-key',
});
const model = localAI('my-model');
const { text } = await generateText({
model,
prompt: 'Say hello',
});如果目标服务不兼容 OpenAI 协议,则需编写自定义 Provider。一个最小的自定义 Provider 需要:
- 实现 Provider 工厂函数。
- 实现
LanguageModel接口。 - 在
doGenerate中发起 HTTP 请求并解析响应。
以下示例接入一个假设的「简单文本生成 API」,仅用于说明接口最小结构:
ts
import type { LanguageModel } from 'ai';
interface MyModelConfig {
apiKey: string;
baseURL: string;
}
function createMyProvider(config: MyModelConfig) {
const provider = (modelId: string): LanguageModel => ({
provider: 'my-provider',
modelId,
async doGenerate(options) {
const response = await fetch(`${config.baseURL}/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`,
},
body: JSON.stringify({
model: modelId,
// 简单示例:按目标 API 的消息格式序列化 options.prompt
prompt: options.prompt,
max_tokens: options.maxTokens,
temperature: options.temperature,
}),
});
const data = await response.json();
return {
text: data.output.text,
finishReason: data.output.finishReason ?? 'stop',
usage: {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
totalTokens: data.usage.total_tokens,
},
rawResponse: { headers: response.headers },
};
},
async doStream(options) {
// 流式场景需在此返回统一的 ReadableStream 事件流
throw new Error('Not implemented');
},
});
provider.load = (modelId: string) => provider(modelId);
return provider;
}该示例仅展示 doGenerate 的最小结构,doStream 以占位代码示意方法签名。实际实现中,LanguageModel 接口还包含 supportsStructuredOutputs、supportsToolCalls 等能力标志,且 doStream 需要返回标准的 ReadableStream 事件流,不能直接抛出未实现错误。具体要求以当前 AI SDK 的类型定义为准。
多模型切换、Fallback 与错误归一化
错误归一化
AI SDK 将不同 Provider 的错误统一为 APICallError、RetryError 等类型,它们都继承自 AISDKError 基类[11]。业务代码可以通过 instanceof 统一捕获:
ts
import { APICallError } from 'ai';
try {
await generateText({ model, prompt });
} catch (error) {
if (error instanceof APICallError) {
console.error(`API 错误:状态 ${error.statusCode}`);
console.error(`URL:${error.url}`);
} else {
console.error(error);
}
}APICallError 暴露的字段包括 url、requestBodyValues、statusCode、responseHeaders、responseBody 等。具体字段在不同版本中有差异,以实际类型为准。
多模型切换
多模型切换有两种典型场景。
场景一:手动选择模型
ts
const modelId = process.env.MODEL_PROVIDER === 'anthropic'
? 'anthropic/claude-3-7-sonnet-latest'
: 'openai/gpt-4o';
const { text } = await generateText({
model: modelId,
prompt: 'Hello',
});场景二:按优先级降级(Fallback)
generateText 的 model 参数一次只接受一个模型实例或模型字符串。需要降级时,在业务层按优先级依次尝试:
ts
const models = [
() => createOpenAI({ apiKey: keyA })('gpt-4o'),
() => createAnthropic({ apiKey: keyB })('claude-3-7-sonnet-latest'),
() => createGoogleGenerativeAI({ apiKey: keyC })('gemini-3-flash'),
];
for (const createModel of models) {
try {
const { text } = await generateText({
model: createModel(),
prompt,
});
return text;
} catch (error) {
console.warn('当前模型调用失败,尝试下一个模型', error);
}
}这种逐级降级的方式在 Provider 故障、配额耗尽、网络超时时有效。实现的要点是:捕获错误、记录日志、继续下一次尝试。
TypeScript 类型推导与扩展工程实践
AI SDK 的一个显著优势是类型安全。以 generateText 为例,result.text、result.toolCalls、result.finishReason 都有明确的类型。
工具调用的 args 类型来自 parameters 的 TypeScript 推断:
ts
const result = await generateText({
model,
tools: {
getWeather: {
description: '获取天气',
parameters: {
type: 'object',
properties: {
city: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
},
required: ['city'],
},
execute: async ({ city, unit }) => {
// city 被推断为 string
// unit 被推断为 'celsius' | 'fahrenheit' | undefined
return { city, unit };
},
},
},
prompt: '上海的天气',
});
if (result.toolCalls.length > 0) {
// result.toolCalls[0].args 的类型由 parameters 推导
}这种推导让开发者可以在编译期发现参数拼写错误。
对于跨模块复用的工具集,可以将 tools 定义为一个类型化变量:
ts
import type { ToolSet } from 'ai';
export const sharedTools = {
getWeather: {
description: '获取天气',
parameters: {
type: 'object',
properties: {
city: { type: 'string' },
},
required: ['city'],
},
execute: async (args: { city: string }) => {
return { city };
},
},
} satisfies ToolSet;使用 satisfies 可以确保结构匹配 ToolSet,同时保留字面量类型。这样在多个调用处复用同一组工具时,类型定义只维护一份。
生态与统一 API 的趋势
AI SDK 的 Provider 抽象不是孤立的设计。它的目标很明确:让上层应用不绑定某个模型供应商。这个目标在生态中的体现包括:
Provider 包数量持续增长。官方维护的 Provider 包覆盖 OpenAI、Anthropic、Google、xAI、Mistral、Amazon Bedrock、Azure 等,第三方 Provider 可以通过独立的 npm 包发布。
AI SDK 在 Provider 之上扩展了更多能力。npm 页面显示包的 keywords 包含
tool-calling、structured-output、agent等关键字,表明这些能力都建立在 Provider 层之上[1]。模型 ID 字符串成为交换单位。统一模型字符串
'provider/model'可以被写入配置、环境变量或 URL query,实现了模型选择的配置化。官方对编码代理场景的支持。官方建议在编码代理(如 Claude Code、Cursor)中通过
npx skills add vercel/ai添加 AI SDK skill,便于自动获得 SDK 使用提示[1]。
从 API 设计角度看,AI SDK 统一了三个层面的差异:
- 参数层面:统一的
prompt、messages、tools结构。 - 行为层面:统一的流式、工具调用、结构化输出语义。
- 错误层面:统一错误类型,减少分支判断。
这套设计也存在边界。Provider 层无法抹平模型的真实能力差异,例如某些模型不支持工具调用、某些模型不支持 function calling 的参数约束、某些模型对长上下文的处理差异巨大。统一 API 解决的是「调用方式」的一致,而不是「模型能力」的一致。
当业务的模型策略需要调整时,代码层面的改动被限制在一个很小的范围内——通常是修改模型字符串或 Provider 配置,而不是重写调用层逻辑。
