---
url: /frontend-ai/ai-sdk.md
description: >-
  面向前端工程师的 Vercel AI SDK 上手科普：讲清裸写 fetch 流式解析要重复造哪些轮子、AI SDK
  抽象掉了哪几层、服务端与客户端怎么分工、最小可读的前后端示例怎么写，以及换模型和代价（依赖、版本漂移、黑盒）的真实体验。
---

# Vercel AI SDK 上手：前端接大模型的标准姿势

这篇解决一个很具体的问题：你已经在项目里调大模型了，但代码是"每个页面自己拉一根 fetch 解 SSE（Server-Sent Events，服务器推送事件）"，对话历史、加载状态、工具调用各写一套，换模型时又要重新适配字段名。Vercel AI SDK（本文核对于 2026-09-27，当时 npm 上 `ai` 最新为 7.0.116，`@ai-sdk/react` 为 4.0.119、`@ai-sdk/vue` 为 4.0.116，要求 Node.js 22+）就是把这堆重复劳动收成一个 TypeScript 库。这个包迭代很快，具体 API 请以官方文档为准。

## 裸写 fetch，你会重复造哪些轮子

第一是编码。`fetch` 的 `response.body` 给你的是 `Uint8Array`（字节数组）流，一个中文占三字节，chunk 边界正好把某个字劈成两半是常态——必须自己用 `TextDecoder` 并传 `{ stream: true }` 兜住半个字符，否则页面蹦乱码。

第二是分帧。SSE 的格式是 `data: ` 前缀加空行结尾，最后还有一条 `[DONE]`；一条 JSON 也可能横跨两个 chunk。你得自己存 buffer、按换行切、跳过注释行。

第三是多模型适配。OpenAI 风格、Anthropic 风格、Gemini 风格的系统消息位置、增量字段、工具调用结构各不相同，一个"支持多家"的产品等于维护若干套解析分支。

第四是工具调用回环：模型返回工具意图，你要执行、把结果回填成一条消息、再发一次请求，直到它不再要工具。循环写错就是死循环或者上下文丢失。

第五是 UI 状态：`submitted`/`streaming`/`ready`/`error` 切得对不对、停止按钮怎么中断、网络断了怎么续、多轮历史的 `key` 怎么给。

## AI SDK 抽象掉了哪几层

核心层是 `ai` 包：`generateText`（一次性生成）和 `streamText`（流式生成）是模型无关的调用面，返回统一的事件流，文本增量、工具调用、结束原因都是固定类型，前端不用再认得厂商字段。

UI 层是两个独立包：React 用 `@ai-sdk/react` 的 `useChat`，Vue 用 `@ai-sdk/vue` 的同名 composable（组合式 API 函数）。它把 SSE 解析、消息列表、状态机收进一个 hook，并且 AI SDK 5 之后 Vue 与 React 已基本对齐功能。

协议层从 5.0 起换成标准 SSE，并明确区分两类消息：`UIMessage` 是客户端的真相（含 `parts`、工具结果），`ModelMessage` 是发给模型的精简格式，中间用 `convertToModelMessages` 转换，持久化存前者。

工具层用 `inputSchema`（5.0 起由 `parameters` 更名，配合 zod 定义）加 `execute`，多步循环由 SDK 自动跑。

## 服务端和客户端怎么分工

服务端持密钥、拼提示词、调 `streamText`，再把流转成 UI 消息流返回；客户端只负责发消息、渲染 `parts`、显示状态。API Key 绝不能进浏览器。

```ts
// app/api/chat/route.ts —— 服务端
import { streamText, UIMessage, convertToModelMessages } from 'ai';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();
  const result = streamText({
    model: openai('gpt-4o'), // 也可直接写模型字符串，走网关
    instructions: '你是前端助手，回答尽量给可运行的代码。',
    messages: await convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}
```

```tsx
// 客户端：useChat 接住流
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';

export default function Chat() {
  const { messages, sendMessage, status } = useChat();
  const [input, setInput] = useState('');

  return (
    <>
      {messages.map(m => (
        <div key={m.id}>
          {m.parts.map((part, i) =>
            part.type === 'text' ? <span key={i}>{part.text}</span> : null,
          )}
        </div>
      ))}
      <form
        onSubmit={e => {
          e.preventDefault();
          sendMessage({ text: input }); // 注意：输入状态归你自己管
          setInput('');
        }}
      >
        <input value={input} onChange={e => setInput(e.target.value)} disabled={status !== 'ready'} />
      </form>
    </>
  );
}
```

Vue 版本换汤不换药：`import { useChat } from '@ai-sdk/vue'`，返回同样结构，注意 Vue 里别解构丢响应式。

## 换模型只是换一行

`model` 既能是 provider 包实例，也能是一个模型字符串（如 `openai/gpt-4o`，通过 AI Gateway 统一入口），切换时通常只动这一行，加装对应 `@ai-sdk/*` 包即可。要留意 `@ai-sdk/*` 的版本必须跟 `ai` 的主版本配套，混着升会直接类型报错。

## 代价：多背一份版本债

第一，多几层依赖，客户端 bundle 也跟着涨；第二，抽象是黑盒，流被截断时你得掀开协议层才知道问题在 SSE 还是在转换；第三，版本漂移真实存在——5.0 重写了 `useChat`（不再托管输入状态），6.0 起 `generateObject`/`streamObject` 标记弃用改用 `output` 参数，升级前最好先读迁移指南并留出改造时间。

::: warning 常见坑 / 什么时候不该套 SDK

1. 密钥别放客户端，`useChat` 只发消息，模型调用必须在服务端路由里。
2. 输入框状态 5.0 以后由你自己维护，别照抄旧教程里的 `input`/`handleInputChange`。
3. 工具定义字段是 `inputSchema`，抄老代码用 `parameters` 会没反应。
4. 渲染优先用 `message.parts`，不要读已不推荐的 `content`。
5. 只调一家模型、只做一次非流式请求，或者要用某个厂商独有的新参数（如特定缓存、思考控制），直接用它官方 SDK 或裸 fetch 弯路更少，别硬套。
   :::
