Vercel AI SDK — 架构与原理
30 秒导读: Vercel AI SDK 是一个 provider 无关的 TypeScript 工具包。你写一次
generateText({ model, prompt, tools }),它负责:把统一的消息格式翻译成某个厂商(OpenAI / Anthropic / Google…)的请求、跑"调模型 → 执行工具 → 再调模型"的循环、把结果(或流)以一套标准协议吐给你的前端。换模型只改一个字符串。
1. 这是什么(零基础也能懂)
一句话定义: 它是 LLM 应用的"中间层"——把"和大模型对话"这件事,从"对着某一家厂商的 HTTP API 拼 JSON",变成一组与厂商无关的函数调用。
解决什么问题 / 给谁用:
假设你要做一个聊天机器人,今天用 OpenAI,明天老板说改用 Anthropic,后天又要支持工具调用、流式输出、结构化 JSON。如果直接对着各家 HTTP API 写,每家的请求体、流格式、工具协议都不一样,代码会被厂商细节绑死。
AI SDK 把这些差异藏进统一接口之下。它给三类人用:
- 应用开发者 —— 调
generateText/streamText写后端逻辑。 - 前端开发者 —— 用
@ai-sdk/react的useChat把流接到 UI。 - provider 作者 —— 实现一个标准接口(
LanguageModelV4),自家模型就能被整个生态使用。
它能做什么(功能):
- 文本生成(一次性
generateText/ 流式streamText) - 工具调用循环(模型要求调工具 → SDK 执行 → 把结果喂回模型,自动循环)
- Agent(
ToolLoopAgent:把模型 + 工具 + 系统提示打包成可复用对象) - 结构化输出(
generateObject/Output:强制模型吐出 符合 schema 的 JSON) - 前端流式 UI(一套
UIMessage协议 + 框架 hook) - 还有 embed / 图片 / 语音 / 转写 / rerank 等多模态能力(本文聚焦文本与 agent 主线)
用起来什么样: 最小例子——换模型只换字符串:
import { generateText } from 'ai';
const { text } = await generateText({
model: 'anthropic/claude-opus-4.6', // 换成 'openai/gpt-5.4' 即切厂商
prompt: 'What is an agent?',
});
这里 model 是个字符串,SDK 默认把它路由到 Vercel AI Gateway(一个统一网关),所以你连 SDK 包都不用装。要直连厂商,就传一个 provider 对象:anthropic('claude-opus-4-6')。两种写法,下游走的是同一套抽象(见 01)。
一句话直觉/类比: 把 AI SDK 当成 LLM 世界的 ODBC / JDBC——数据库各家方言不同,但你对着统一的驱动接口写代码,换数据库只换连接串。这里"数据库驱动"就是 LanguageModelV4,"连接 串"就是那个 model 字符串。
2. 顶层全景(它大概怎么转)
这一节给你看清"大盘":一次 generateText 调用,数据从你的代码流到模型、再流回来,中间经过哪些部件。
2.1 分层结构
AI SDK 是一个 monorepo,核心分三层。理解这三层,就理解了整个库的骨架:
你的应用代码 (generateText / streamText / useChat)
│ 传:统一的 prompt / messages / tools
▼
┌──────────────────────────────────────────────┐
│ 包 ai —— "编排层" (orchestration) │
│ · generateText 多步工具循环 │
│ · streamText 流式 + UI 协议 │
│ · ToolLoopAgent / generateObject │
│ 把"统一格式"翻译成 LanguageModelV4 的调用 │
└──────────────────────────────────────────────┘
│ 调:model.doGenerate(...) / model.doStream(...)
▼
┌──────────────────────────────────────────────┐
│ 包 @ai-sdk/provider —— "规格层" (spec) │
│ 只有类型,没有实现:LanguageModelV4 接口定义 │
│ 规定"一个模型必须长什么样" │
└──────────────────────────────────────────────┘
│ 被实现
▼
┌──────────────────────────────────────────────┐
│ 各厂商包 —— "实现层" (adapters) │
│ @ai-sdk/openai / anthropic / google / gateway… │
│ 各自把统一调用映射成自家 HTTP API │
└──────────────────────────────────────────────┘
怎么读这张图: 上面是你的代码,越往下越靠近厂商。关键分界在中间那条线——@ai-sdk/provider 只定义"模型应该长什么样"(纯类型),不含任何实现;编排层只对着这个接口编程,所以永远不知道底下接的是 OpenAI 还是 Anthropic。这就是"provider 无关"的全部秘密。
2.2 部件一句话职责
| 部件 | 干什么 | 在哪 |
|---|---|---|
LanguageModelV4 | 模型的统一 接口:doGenerate / doStream 两个方法 | packages/provider/src/language-model/v4/language-model-v4.ts |
generateText | 非流式主入口 + 多步工具循环 | packages/ai/src/generate-text/generate-text.ts |
streamText | 流式主入口,产出多种 stream 视图 | packages/ai/src/generate-text/stream-text.ts |
ToolLoopAgent | 把模型/工具/提示打包,薄封装 generateText/streamText | packages/ai/src/agent/tool-loop-agent.ts |
tool() | 定义一个工具(schema + execute) | packages/provider-utils/src/types/tool.ts |
resolveLanguageModel | 把 model 字符串路由到 AI Gateway,或把对象规整为 v4 | packages/ai/src/model/resolve-model.ts |
wrapLanguageModel | 用中间件包住模型(改参数/拦截结果) | packages/ai/src/middleware/wrap-language-model.ts |
| UI Message Stream | 后端→前端的标准流协议 | packages/ai/src/ui-message-stream/ |
AbstractChat / useChat | 前端聊天状态机 | packages/ai/src/ui/chat.ts |
2.3 主线走一遍(高层,不进代码)
以一次"带工具的对话"为例,端到端是这样:
输入: { model, messages, tools }
│
1. resolveLanguageModel —— model 字符串 → 具体 LanguageModelV4
│
2. standardizePrompt —— 把 system/prompt/messages 规整成内部统一格式
│
3. ┌─ 进入 step 循环 ─────────────────────────┐
│ a. convertToLanguageModelPrompt │
│ 内部格式 → LanguageModelV4Prompt │
│ b. model.doGenerate(...) ← 真正打模型 │
│ c. parseToolCall —— 校验/修复模型给的工具调用 │
│ d. executeToolCall —— 跑你的工具 execute │
│ e. 工具结果拼回 messages │
└───── 还有工具要跑 且 没到 stopWhen?→ 回到 a ─┘
│
4. 循环结束 → 解析最终输出(文本或结构化 JSON)
│
输出: GenerateTextResult { text, steps, toolCalls, usage, ... }
这个 step 循环就是整个库的心脏,02 会逐行拆。
3. 阅读地图(按这个顺序读)
本项目较大,拆成 5 章,由浅入深:
-
01 · Provider 抽象 — 先搞懂最底下那条分界线:
LanguageModelV4长什么样、字符串 model 怎么路由到 Gateway、中间件怎么"包"模型。理解了它,后面所有"provider 无关"的说法才落地。 -
02 · generateText 工具循环 — 全库最核心的一段代码。一次 step 内发生什么、
do…while的继续条件、stopWhen怎么终止。读完你能讲清"agent loop"在这里到底是什么。 -
03 · 工具系统 — 工具的四种形态(function / dynamic / provider-executed / provider-defined)、
tool()助手、解析失败时的 repair、以及 tool approval(人审批)机制。 -
04 · 流式与 UI —
streamText的多种流视图、后端→前端的 UI Message Stream 协议、useChat客户端状态机怎么消费它。Web 互操作的精华。 -
05 · 结构化输出 —
generateObject与Output:怎么把自由文本逼成符合 zod schema 的 JSON。
建议路径: 想理解 agent → 读 01 → 02 → 03;做全栈聊天 → 读 02 → 04;只关心抽取结构化数据 → 读 01 → 05。
4. 代码地图(导航索引)
| 主题 | 文件 | 符号 |
|---|---|---|
| 公共 API 出口 | packages/ai/src/index.ts | export * 各目录 |
| 模型接口规格 | packages/provider/src/language-model/v4/language-model-v4.ts | LanguageModelV4 |
| Provider 接口 | packages/provider/src/provider/v4/provider-v4.ts | ProviderV4 |
| 多步循环主入口 | packages/ai/src/generate-text/generate-text.ts | generateText |
| 流式主入口 | packages/ai/src/generate-text/stream-text.ts | streamText |
| Agent | packages/ai/src/agent/tool-loop-agent.ts | ToolLoopAgent |
| 模型路由 | packages/ai/src/model/resolve-model.ts | resolveLanguageModel |
| 中间件 | packages/ai/src/middleware/wrap-language-model.ts | wrapLanguageModel |
| 工具定义 | packages/provider-utils/src/types/tool.ts | tool, Tool |
| 停止条件 | packages/ai/src/generate-text/stop-condition.ts | isStepCount, hasToolCall |
| UI 流协议 | packages/ai/src/ui-message-stream/ui-message-chunks.ts | uiMessageChunkSchema |
| 前端聊天 | packages/ai/src/ui/chat.ts | AbstractChat(具体 Chat 在 packages/react/src/chat.react.ts) |