跳到主要内容

01 · Provider 抽象层

本章讲什么: AI SDK 凭什么"换模型只改一行"?答案全在这条最底层的分界线——LanguageModelV4 接口。本章拆三件事:接口长什么样、model 字符串怎么变成真实模型、中间件怎么在不改模型的前提下改它的行为。

1.1 核心契约:LanguageModelV4

整个库的"provider 无关"建立在一个纯类型接口上。它在 @ai-sdk/provider 包里,这个包几乎没有运行时代码,只有类型——它定义"一个模型必须长什么样",但不实现任何模型。

接口本体小得惊人——核心就两个方法:

// packages/provider/src/language-model/v4/language-model-v4.ts —— 简化
export type LanguageModelV4 = {
readonly specificationVersion: 'v4'; // 版本标记
readonly provider: string; // 'openai' / 'anthropic' …
readonly modelId: string; // 'gpt-5.4' …
supportedUrls: /* 哪些 URL 模型能原生吃,不必下载 */;
doGenerate(options): PromiseLike<LanguageModelV4GenerateResult>; // 一次性
doStream(options): PromiseLike<LanguageModelV4StreamResult>; // 流式
};

LanguageModelV4(packages/provider/src/language-model/v4/language-model-v4.ts:8)。

几个设计要点:

  • do 前缀是故意的。 注释明说:加 do 前缀是为了防止用户直接调用这两个方法(language-model-v4.ts:43-45)。用户应该走 generateText 这类高层函数,而不是手动 model.doGenerate
  • specificationVersion: 'v4' 是个字面量标记。编排层靠它判断模型实现的是哪一版接口——库同时兼容 v2/v3/v4(见下文 resolveLanguageModel)。
  • 两个方法对应两种模式: 非流式 doGenerate 一把返回完整结果;流式 doStream 返回一个 part 流。generateText 用前者,streamText 用后者。

统一的 prompt 格式

模型接口收到的不是你写的 prompt: 'hello',而是一个规范化的消息数组 LanguageModelV4Prompt。它有四种角色:

rolecontent 能放什么
system一段字符串
user文本片段 + 文件片段
assistant文本 / 文件 / reasoning / 工具调用 / 工具结果
tool工具结果 / 工具审批响应

LanguageModelV4Message(packages/provider/src/language-model/v4/language-model-v4-prompt.ts:19)。注释强调这"不是面向用户的 prompt"——是 SDK 把用户写的 chat/instruction prompt 映射成的内部中立格式。这层映射(convertToLanguageModelPrompt)正是"各家方言→统一格式"的翻译器。

同理,Provider 是模型的工厂

比 model 高一级的是 ProviderV4——一个 provider 能按 id 返回各类模型:

// packages/provider/src/provider/v4/provider-v4.ts —— 简化
export interface ProviderV4 {
readonly specificationVersion: 'v4';
languageModel(modelId: string): LanguageModelV4;
embeddingModel(modelId: string): EmbeddingModelV4;
imageModel(modelId: string): ImageModelV4;
transcriptionModel?(modelId: string): TranscriptionModelV4;
// … speech / reranking / files / skills(可选)
}

ProviderV4(packages/provider/src/provider/v4/provider-v4.ts:13)。openai('gpt-5.4') 本质就是"调某个 ProviderV4 的 languageModel('gpt-5.4')"。

1.2 model 怎么变成真实模型:resolveLanguageModel

你给 generateTextmodel 可以是字符串也可以是对象。统一在入口处被 resolveLanguageModel 规整:

// packages/ai/src/model/resolve-model.ts —— 简化
export function resolveLanguageModel(model: LanguageModel): LanguageModelV4 {
if (typeof model === 'string') {
return getGlobalProvider().languageModel(model); // 字符串 → 全局 provider
}
if (!['v4', 'v3', 'v2'].includes(model.specificationVersion)) {
throw new UnsupportedModelVersionError(/* … */);
}
return asLanguageModelV4(model); // 老版本对象适配成 v4
}

resolveLanguageModel(packages/ai/src/model/resolve-model.ts:28)。两条路径:

  • 传字符串(如 'openai/gpt-5.4')→ 交给"全局 provider"的 languageModel(...)。默认全局 provider 是 Vercel AI Gateway(resolve-model.ts 顶部 import { gateway } from '@ai-sdk/gateway'),所以裸字符串会被路由到网关,由网关再转发给真实厂商。这就是 README 里"不装任何 provider 包也能跑"的原因。
  • 传对象(如 anthropic('claude-opus-4-6'))→ 校验版本号、必要时把 v2/v3 适配成 v4。asLanguageModelV4老 provider 包在新编排层里继续可用——这是库能平滑升级大版本的关键缝合点。

精华: "换模型只改字符串"不是魔法,是"字符串→全局 Gateway provider"这一条默认路由。直连厂商时,provider 对象自己实现了 LanguageModelV4,编排层一视同仁。

1.3 中间件:在不改模型的前提下改它的行为

有时你想给任意模型加点横切能力:打日志、改默认参数、模拟流式、从输出里抽 reasoning。wrapLanguageModel 用"装饰器"思路实现:包进去一个 LanguageModelV4,返回另一个 LanguageModelV4——对调用方完全透明。

中间件能挂什么钩子

// packages/provider/src/language-model-middleware/v4/language-model-v4-middleware.ts —— 简化
export type LanguageModelV4Middleware = {
readonly specificationVersion: 'v4';
overrideProvider?(...): string; // 改 provider 名
overrideModelId?(...): string; // 改 modelId
transformParams?(...): Promise<LanguageModelV4CallOptions>; // 调用前改参数
wrapGenerate?(...): Promise<...>; // 包住 doGenerate
wrapStream?(...): Promise<...>; // 包住 doStream
};

LanguageModelV4Middleware(packages/provider/src/language-model-middleware/v4/language-model-v4-middleware.ts:10)。三个层次:改身份(override*)、改输入(transformParams)、包执行(wrapGenerate/wrapStream)。

它怎么"包"

wrapLanguageModel 返回一个新的 v4 对象,其 doGenerate/doStream 先跑 transformParams 再交给中间件的 wrapGenerate/wrapStream:

// packages/ai/src/middleware/wrap-language-model.ts —— 简化自 doWrap
return {
specificationVersion: 'v4',
provider: providerId ?? overrideProvider?.({ model }) ?? model.provider,
modelId: modelId ?? overrideModelId?.({ model }) ?? model.modelId,
async doGenerate(params) {
const transformed = await doTransform({ params, type: 'generate' });
const doGenerate = () => model.doGenerate(transformed); // 原始能力
return wrapGenerate
? await wrapGenerate({ doGenerate, doStream, params: transformed, model })
: await doGenerate();
},
// doStream 同理
};

doWrap(packages/ai/src/middleware/wrap-language-model.ts:43)。两个巧妙点:

  • 多个中间件按数组顺序组合。 wrapLanguageModel 把中间件数组 .reverse().reduce(...) 逐个包上(wrap-language-model.ts:36-41),于是数组里第一个中间件在最外层、最先看到调用。
  • wrapGenerate 同时拿到 doGeneratedoStream 这让"模拟流式"中间件成为可能:用户调 doStream,中间件却去调底层 doGenerate、再把整段结果切成假的流(就是 simulate-streaming-middleware.ts 干的事)。

库自带一批中间件(packages/ai/src/middleware/):defaultSettingsMiddleware(注入默认参数)、extractReasoningMiddleware(从文本里抠出 <think> 推理)、extractJsonMiddlewaresimulateStreamingMiddleware 等。

1.4 巧妙之处

  • 规格包零实现。 @ai-sdk/provider 只有类型。编排层 import 的是"形状"不是"代码",所以厂商包能独立发版、独立演进,核心包不必跟着改。
  • 版本号当适配开关。 specificationVersion: 'v4' + asLanguageModelV4 让一个新编排层同时吃 v2/v3/v4 的老 provider(resolve-model.ts:32)。大版本升级不必一次性重写所有厂商包。
  • 中间件和模型同构。 包出来的还是 LanguageModelV4,所以可以层层嵌套,也能塞回任何接受 model 的地方——这是"装饰器模式"的教科书级应用。

1.5 边界与局限

  • 接口只规定 doGenerate/doStream 的形状,不规定语义细节(如某参数对某模型是否生效)。不支持的能力靠运行时 warnings 上报,而非编译期报错。
  • 裸字符串默认走 Gateway,意味着默认有一个网络中间商;要纯直连得显式传 provider 对象。

1.6 代码地图

主题文件符号
模型接口packages/provider/src/language-model/v4/language-model-v4.tsLanguageModelV4
调用参数packages/provider/src/language-model/v4/language-model-v4-call-options.tsLanguageModelV4CallOptions
统一 promptpackages/provider/src/language-model/v4/language-model-v4-prompt.tsLanguageModelV4Message
Provider 接口packages/provider/src/provider/v4/provider-v4.tsProviderV4
模型路由packages/ai/src/model/resolve-model.tsresolveLanguageModel
中间件类型packages/provider/src/language-model-middleware/v4/language-model-v4-middleware.tsLanguageModelV4Middleware
包模型packages/ai/src/middleware/wrap-language-model.tswrapLanguageModel, doWrap
抽 reasoning 中间件packages/ai/src/middleware/extract-reasoning-middleware.tsextractReasoningMiddleware

下一章:02 · generateText 工具循环,全库的心脏。