🚀 Vercel AI SDK 使用指南:流式协议 (Stream Protocol)

6 阅读4分钟

在构建 AI 应用时,前后端的通信机制是核心。Vercel AI SDK 提供了强大的流式传输能力,但你是否真正了解底层发生了什么?

本文将带你深入解读 Stream Protocol,重点介绍 Vercel AI SDK 默认使用的 Data Stream Protocol。理解这些协议不仅能帮你更好地调试应用,还能让你在非 Next.js 环境(如 Python/Go 后端)中实现兼容的接口。

什么是 Stream Protocol?

Vercel AI SDK 的 UI 组件(如 useChatuseCompletion)支持两种主要的流式协议:

  1. Text Stream Protocol(文本流协议) :最简单的形式,仅传输纯文本块。
  2. Data Stream Protocol(数据流协议) :SDK 的默认且功能最强大的协议。它基于 Server-Sent Events (SSE) 标准,能够传输文本、工具调用(Tool Calls)、推理步骤(Reasoning)、元数据以及自定义数据。

1. Text Stream Protocol (文本流协议)

这是最基础的模式,适用于只需要生成简单文本的场景。

  • 格式:纯文本块(Plain Text Chunks)。
  • 拼接:前端收到每个块后直接拼接到之前的文本后面。
  • 限制:不支持工具调用、不支持复杂的元数据。

后端代码示例 (Next.js App Router):

TypeScript

import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';

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

  const result = streamText({
    model: openai('gpt-4o'),
    messages,
  });

  // 注意这里调用的是 toTextStreamResponse()
  return result.toTextStreamResponse(); 
}

2. Data Stream Protocol (数据流协议) - 核心重点

这是我们今天的主角。当你使用 useChat 时,默认就是走的这个协议。它使用 SSE (Server-Sent Events) 格式,每一行数据都以 data: 开头,后跟一个 JSON 对象。

为什么选择 Data Stream?

  • 标准化:基于 SSE,自带自动重连和缓存处理机制。
  • 多功能:可以在同一个流中混合发送文本、工具调用结果、错误信息和自定义数据。
  • 结构化:每一帧都有明确的类型 (type)。

协议帧详解 (Protocol Parts)

一个典型的数据流由以下几种帧组成:

A. 消息开始与元数据

表示一条新消息的开始。

JSON

data: {"type":"start", "messageId":"msg_123"}

B. 文本生成 (Text Parts)

文本不再是简单的字符串,而是分为“开始”、“增量”和“结束”,并且带有 id,这使得前端可以精确控制文本块。

  • 开始: data: {"type":"text-start", "id":"text_1"}
  • 增量: data: {"type":"text-delta", "id":"text_1", "delta":"你好"}
  • 结束: data: {"type":"text-end", "id":"text_1"}

C. 推理/思考过程 (Reasoning Parts) 🌟

对于像 o1/r1 这样具有思维链的模型,SDK 支持流式传输推理过程。

  • 开始: data: {"type":"reasoning-start", "id":"reason_1"}
  • 增量: data: {"type":"reasoning-delta", "id":"reason_1", "delta":"正在分析用户意图..."}
  • 结束: data: {"type":"reasoning-end", "id":"reason_1"}

D. 工具调用 (Tool Parts)

这是 Data Stream 最强大的地方。它将工具的调用过程拆解得非常细致:

  1. 输入流开始: data: {"type":"tool-input-start", "toolCallId":"call_01", "toolName":"getWeather"}
  2. 输入参数增量: data: {"type":"tool-input-delta", "toolCallId":"call_01", "inputTextDelta":"{"city": "Bei"}
  3. 输入参数完成: data: {"type":"tool-input-available", "toolCallId":"call_01", "toolName":"getWeather", "input":{"city":"Beijing"}}
  4. 工具执行结果: data: {"type":"tool-output-available", "toolCallId":"call_01", "output":{"temp": 25}}

E. 流程控制 (Step & Finish)

当你在后端进行多次 LLM 交互(例如多步工具调用)时,这些帧非常重要。

  • 步骤开始: data: {"type":"start-step"}
  • 步骤结束: data: {"type":"finish-step"} (表示一次 LLM 调用或工具执行的结束)
  • 消息结束: data: {"type":"finish"}
  • 流结束标志: data: [DONE] (SSE 标准结束符)

3. 实战代码示例

后端实现 (Next.js)

在后端,使用 streamText 并调用 toDataStreamResponse() (v6 版本默认行为,或显式调用 toUIMessageStreamResponse 如果需要转换格式)。

TypeScript

// app/api/chat/route.ts
import { streamText, convertToCoreMessages } from 'ai';
import { z } from 'zod';
import { openai } from '@ai-sdk/openai';

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

  const result = streamText({
    model: openai('gpt-4-turbo'),
    messages: convertToCoreMessages(messages),
    tools: {
      getWeather: {
        description: '获取天气信息',
        parameters: z.object({ city: z.string() }),
        execute: async ({ city }) => ({ temperature: 22, condition: 'Sunny' }),
      },
    },
  });

  // 返回 Data Stream 响应
  return result.toDataStreamResponse();
}

前端实现 (React/Next.js)

在前端,useChat 会自动解析上述复杂的 JSON 帧,你只需要关注 UI 渲染。

TypeScript

// app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div className="flex flex-col p-4">
      {messages.map(m => (
        <div key={m.id} className="mb-4">
          <div className="font-bold">{m.role === 'user' ? 'User' : 'AI'}</div>
          
          {/* 渲染消息内容 */}
          {m.parts ? (
             /* v6+ 版本支持 parts 数组渲染,处理多模态和工具调用 UI */
             m.parts.map((part, i) => {
               if (part.type === 'text') return <span key={i}>{part.text}</span>;
               if (part.type === 'tool-invocation') return <pre key={i}>调用工具: {part.toolInvocation.toolName}</pre>;
               return null;
             })
          ) : (
            <div>{m.content}</div>
          )}
        </div>
      ))}

      <form onSubmit={handleSubmit}>
        <input
          className="border p-2 w-full mt-4"
          value={input}
          onChange={handleInputChange}
          placeholder="问问天气..."
        />
      </form>
    </div>
  );
}

总结

理解 Vercel AI SDK 的 Data Stream Protocol 对开发复杂的 AI Agent 至关重要。它通过 SSE 传输结构化的 JSON 数据,完美解决了流式文本、工具调用状态和推理过程的同步问题。

如果你正在尝试用 Python (FastAPI) 或 Go 编写后端来对接 Vercel 的前端 SDK,请务必严格遵守上述 data: {"type": "..."} 的 SSE 格式!


希望这篇教程对你有帮助!记得点赞收藏哦!