Vercel AI SDK 入门:从 0 搭一个会流式回复的聊天页

一篇面向新手的 Vercel AI SDK 7 入门教程:先理清 Core、UI 和 Provider,再用 Next.js App Router 跑通一个真正会流式回复的聊天页。

第一次做 AI 应用,最容易卡住的地方通常不是模型本身,而是一堆新名词同时扑过来:Provider、stream、tool calling、message parts、Route Handler、model messages、UI messages。像刚走进后厨,灶台、锅、出餐口全在响,你只是想先煮一碗能吃的面。

Vercel AI SDK 解决的正是这个问题。它把「怎么调模型」和「怎么把模型输出接到前端」这两件事拆开,又用一套比较统一的接口把它们接回去。你不用一开始就手写 SSE,也不用自己维护一堆模型供应商的适配逻辑。先让一个聊天页跑起来,再加工具调用、结构化输出、鉴权和监控,会稳很多。

先记住一句话

Vercel AI SDK 是一套 TypeScript AI 应用工具箱。ai 包负责模型调用和流式输出,@ai-sdk/react 负责前端聊天状态,Provider 包或 Vercel AI Gateway 负责连接具体模型服务。

可以先把它想成三层,后面的代码基本都在这三层之间传递消息:

  • AI SDK Core:模型层。streamTextgenerateTexttool 这些 API 都在这里,负责把 prompt 或 messages 送进模型,再拿回文本、对象或流。
  • AI SDK UI:界面层。useChat 负责输入框、消息列表、发送状态和流式更新,少写很多重复的 React 状态代码。
  • Provider / Gateway:连接层。@ai-sdk/openai@ai-sdk/anthropic 或 Vercel AI Gateway 把统一调用翻译成具体模型服务能听懂的请求。

你会做出什么

这篇不追求一口气讲完整个 AI SDK。我们只做一条最小但完整的链路:用户在页面输入一句话,后端调用模型,前端把回复一点点渲染出来。

能跑通这条链路,比先看十页概念更重要。因为一旦你知道消息怎么从浏览器走到模型、又怎么回来,后面加工具、加 RAG、加日志,都只是沿着这条路继续铺。

项目结构大概是这样:

最小项目结构text
my-ai-app/
├─ app/
│  ├─ page.tsx
│  └─ api/chat/route.ts
├─ .env.local
└─ package.json
文件作用
app/page.tsx前端聊天界面,使用 useChat 管输入、发送和消息渲染
app/api/chat/route.ts后端 Route Handler,使用 streamText 调模型
.env.local放 API Key,不能提交到 Git
package.json安装 ai@ai-sdk/react、Provider 包等依赖
版本提醒

我重新查了一下 npm:现在 ai7.0.18@ai-sdk/react4.0.19@ai-sdk/openai4.0.9。AI SDK 更新很快,复制代码前最好顺手看一眼官方文档。本文按 AI SDK 7 的写法整理。

第一步:创建 Next.js 项目

先准备 Node.js 22+ 和 pnpm。官方 quickstart 也建议用 pnpm,这里就不折腾别的包管理器了。

创建项目bash
pnpm create next-app@latest my-ai-app
cd my-ai-app

创建项目时,建议这样选:

  • App Router:选 yes。本文用的就是 app/api/chat/route.ts
  • TypeScript:选 yes。AI SDK 的类型提示能帮你少踩不少坑。
  • Tailwind CSS:选 yes。不是必须,但写一个舒服点的聊天页会快很多。
  • src 目录:看个人习惯。选不选都不影响 AI SDK。

然后安装 AI SDK 相关依赖:

安装依赖bash
pnpm add ai @ai-sdk/react zod

如果你要直接调用 OpenAI,再装 Provider 包:

使用 OpenAI Providerbash
pnpm add @ai-sdk/openai

这里有两条常见路线:

路线安装环境变量适合谁
Vercel AI Gatewayai @ai-sdk/react zodAI_GATEWAY_API_KEY想用一个入口管理多个模型
直接 Provider再加 @ai-sdk/openaiOPENAI_API_KEY已经确定只接某家模型服务

新手我建议先选一条路。先跑通,再抽象。很多奇怪 bug 都来自「我两个方案都配了一半」。

第二步:写环境变量

在项目根目录创建 .env.local

创建环境变量文件bash
cp /dev/null .env.local

如果走 Vercel AI Gateway:

.env.localdotenv
AI_GATEWAY_API_KEY=你的_key

如果直接走 OpenAI Provider:

.env.localdotenv
OPENAI_API_KEY=你的_key
别把 Key 放到前端

.env.local 不要提交到 Git。前端组件也不要直接读取模型 Key。浏览器里出现的东西,用户都能看到;把 Key 放进去,基本等于把门禁卡贴在楼下。

第三步:写后端 Route Handler

先看 Gateway 写法。新建 app/api/chat/route.ts

app/api/chat/route.tsts
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai'

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json()

  const result = streamText({
    model: 'xai/grok-build-0.1',
    messages: await convertToModelMessages(messages),
  })

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  })
}

这段代码不长,但里面有几个动作很关键:

1. 接收 UI messages

前端发来的 messages 不只是纯文本。它们还带着角色、分段内容和 UI 侧需要的元数据,类型是 UIMessage[]

2. 转成 model messages

模型不需要前端元数据,所以用 convertToModelMessages 转成模型能处理的消息格式。

3. 调用 streamText

streamText 会发起模型调用,并返回一个可以继续往外传的文本流。聊天场景优先用它,因为用户能更早看到内容开始出现。

4. 返回 UIMessage Stream

createUIMessageStreamResponse 把模型输出包装成 useChat 能读取的响应格式。前端不用关心底层流协议,只管更新消息。

如果你走 OpenAI Provider,Route Handler 可以改成这样:

app/api/chat/route.tsts
import { openai } from '@ai-sdk/openai'
import {
  convertToModelMessages,
  createUIMessageStreamResponse,
  streamText,
  toUIMessageStream,
  type UIMessage,
} from 'ai'

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json()

  const result = streamText({
    model: openai('gpt-5.1'),
    messages: await convertToModelMessages(messages),
  })

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  })
}
为什么不直接 return result?

streamText 更偏模型层,useChat 需要的是 UI 消息流。中间这一层转换有点像后厨把菜做好,再按外卖盒的格式装好:菜是同一道菜,但前端需要按它认识的格式拆开。

实际项目里,模型名最好抽到环境变量里。教程为了少一点干扰,先写死在代码里;等跑通后再改成 MODEL_ID 会更稳。

第四步:写前端聊天页

app/page.tsx 改成下面这样:

这里最值得注意的是两处:

  • messages:当前聊天记录。AI SDK 7 的消息内容通过 message.parts 渲染,不要再假设每条消息只有一个 content 字符串。
  • sendMessage:把用户输入发给 /api/chat。默认路径就是刚才写的 Route Handler。

message.parts 乍看有点绕,但它是合理的。模型以后不一定只返回纯文本,还可能返回 reasoning、tool call、file、source 等片段。parts 就像一排托盘格子,不同类型的输出按顺序放进去,前端再决定每一格怎么展示。

完整链路可以这样读:

流式聊天链路text
用户输入
  ↓
useChat 发送 POST /api/chat
  ↓
Route Handler 读取 messages
  ↓
convertToModelMessages 转成模型消息
  ↓
streamText 调用模型
  ↓
UIMessage Stream 持续返回
  ↓
message.parts 逐段渲染到页面

第五步:跑起来

启动开发服务器:

启动开发环境bash
pnpm dev

打开:

浏览器地址text
http://localhost:3000

如果配置没问题,你输入一句话后,回复会一点点冒出来。这个体验和一次性等完整结果不太一样:总耗时可能差不多,但页面开始动起来以后,人会明显更安心。

如果它没跑起来,先看这几个地方
  1. .env.local 里有没有 Key,变量名是不是写对了。
  2. 改完 .env.local 后有没有重启 pnpm dev
  3. Route Handler 里的模型名是否在当前 Provider 或 Gateway 里可用。
  4. 浏览器 Network 里 /api/chat 是否返回 200。
  5. 终端有没有鉴权、配额、区域网络之类的错误。

第一轮调 AI 应用,很多问题都不神秘:Key 写错、模型名不可用、环境变量没重启。先查这些,别急着怀疑框架。

generateText 和 streamText 怎么选

AI SDK Core 里最常见的是 generateTextstreamText。名字很直白,但使用场景不一样。

函数适合场景返回方式
generateText标题、摘要、分类、短文本改写等模型完整生成后一次性返回
streamText聊天、长文本、需要实时显示的输出边生成边返回

比如你只是要生成一个标题:

generate-title.tsts
import { generateText } from 'ai'

const { text } = await generateText({
  model: 'xai/grok-build-0.1',
  prompt: '给一篇 Vercel AI SDK 入门教程起一个中文标题',
})

console.log(text)

聊天页更适合 streamText。用户发出消息后,系统马上开始吐字,比盯着空白区域等最终答案舒服很多。流式输出不一定让模型更快,但会让等待更可控。

加一点系统提示词

现在模型还在自由发挥。你可以在 streamText 里加 system,让它更像你的产品,而不是一个没有边界的万能聊天框:

app/api/chat/route.tsts
const result = streamText({
  model: 'xai/grok-build-0.1',
  system: '你是一个简洁、耐心的中文编程助手。回答要短,但关键步骤不能省。',
  messages: await convertToModelMessages(messages),
})

system 不要写成一份公司制度。太长的提示词会互相打架,模型也未必抓得住重点。先写三五句,跑一段真实对话,再根据输出慢慢修。

Prompt 不是权限系统

你可以在 system 里写「不要泄露密钥」,但真正的安全要靠代码:Key 不进前端,工具不随便读 .env.local,用户输入不直接拼进危险命令。Prompt 是方向盘,不是保险柜。

下一步可以加什么

跑通聊天只是第一关。AI SDK 后面能接的东西很多,但别一口气把工具、RAG、图片、语音、agent 全倒进去。先让主链路稳定,再一层一层加。

第一阶段:纯聊天

先掌握 useChatstreamTextmessagesmessage.parts。这一步看似基础,其实决定了后面所有功能怎么接。

第二阶段:结构化输出

用 schema 让模型返回固定结构,比如分类、表单字段、JSON 数据。适合做后台任务和半自动流程。

第三阶段:工具调用

让模型在需要时调用函数,比如查数据库、读文档、创建工单。这里开始要认真处理权限、日志和失败重试。

第四阶段:产品化

补上鉴权、限流、成本统计、错误提示、模型切换和评估。能 demo 不等于能上线,这一步最容易被低估。

新手可以先做几个小练习:

练习清单md
1. 把 system prompt 改成「苏格拉底式老师」,让它少直接给答案,多追问。
2. 给页面加一个「清空对话」按钮。
3. 把模型名抽到环境变量里,比如 MODEL_ID=xai/grok-build-0.1。
4. 在 Route Handler 里捕获错误,给前端返回更友好的提示。

常见坑

症状处理
忘了 'use client'useChat 报错或页面无法交互app/page.tsx 顶部加上
Key 写错/api/chat 返回 401/403检查 .env.local 和 Provider 文档
改 env 不重启明明改了 Key,还是报旧错误停掉 pnpm dev 重新启动
消息渲染空白后端有返回,页面没显示改成遍历 message.parts
API 路径不一致前端发不到后端默认用 /api/chat,自定义时配置 useChat transport
模型不支持Provider 报 model not found换可用模型,或确认 Gateway 权限

这张表可以先留着。第一次接 AI SDK,很多调试时间都花在这些小地方;它们不高级,但足够烦人。

总结

Vercel AI SDK 入门的重点不是记住所有 API,而是先把主链路跑通。

浏览器里,useChat 管输入、发送和消息渲染;Next.js Route Handler 里,streamText 调模型;Provider 或 Gateway 把请求送到真正的模型服务;UIMessage Stream 再把结果一点点送回页面。

这条路通了,你再加工具调用、结构化输出、RAG 或 agent 编排,心里会有一张地图。否则一开始就把所有能力堆上去,调试时很容易分不清到底是前端状态、后端格式、模型权限,还是流式协议出了问题。

先做一条能稳定来回的消息链路。它看起来朴素,但这是所有 AI 应用的地基。

新故事即将发生
Deep Research Agent:让 AI 带着小本本去查资料

评论区

评论加载中...