Vercel AI SDK 使用指南:生成式用户界面 (Generative UI)

作者:互联网

2026-04-17

⼤语⾔模型脚本

在构建 AI Chatbot 时,我们通常局限于纯文本的交互。用户询问天气,AI 返回一段文字:"旧金山今天是晴天,气温 20 度"。

但在现代应用中,我们希望 AI 能做得更多——直接渲染一个精美的天气卡片,或者一个互动的股市走势图。这就是 Generative UI (生成式用户界面) 的核心理念:让大模型不仅能生成文本,还能"生成"界面。

本文将带你深入理解 Vercel AI SDK 的 Generative UI 机制,并手把手教你实现一个能动态渲染组件的聊天应用。

什么是 Generative UI?

简单来说,Generative UI 是将 Tool Calling (工具调用) 的结果直接连接到 React 组件 的过程。

工作流程如下:

  1. 用户发送消息(例如:"查询旧金山的天气")。
  2. 模型根据上下文决定调用一个工具(例如:getWeather)。
  3. 工具在服务端执行,返回结构化数据(JSON)。
  4. 客户端接收到数据,不是显示 JSON,而是根据数据渲染一个预定义的 React 组件(例如:)。

️ 实战开发

我们将构建一个简单的聊天应用,当用户询问天气时,它会显示一个可视化的天气组件。

第一步:定义工具 (Server-Side)

首先,我们需要在服务端定义一个工具。工具本质上是一个带有 Zod 参数校验的函数。

创建 ai/tools.ts

TypeScript

import { tool as createTool } from 'ai';
import { z } from 'zod';

// 定义一个天气工具
export const weatherTool = createTool({
  description: '显示指定地点的天气信息',
  inputSchema: z.object({
    location: z.string().describe('需要获取天气的地点'),
  }),
  // 模拟异步请求,实际项目中这里会调用第三方 API
  execute: async function ({ location }) {
    await new Promise(resolve => setTimeout(resolve, 2000));
    return { weather: 'Sunny', temperature: 24, location };
  },
});

// 导出工具集合,键名 'displayWeather' 很重要,后续前端会用到
export const tools = {
  displayWeather: weatherTool,
};

第二步:创建 API 路由 (Server-Side)

接下来,创建处理聊天请求的 API 路由。我们需要将定义好的 tools 传递给 streamText 函数。

创建 app/api/chat/route.ts

TypeScript

import { streamText, convertToModelMessages, UIMessage, stepCountIs } from 'ai';
import { tools } from '@/ai/tools'; // 引入刚才定义的工具

// 这里以 Claude 为例,你也可以替换为 'gpt-4o' 等其他模型
export async function POST(request: Request) {
  const { messages }: { messages: UIMessage[] } = await request.json();

  const result = streamText({
    model: "anthropic/claude-sonnet-4.5", 
    system: '你是一个友好的助手!',
    messages: await convertToModelMessages(messages),
    stopWhen: stepCountIs(5), // 防止模型陷入过深的循环
    tools, // 注册工具
  });

  return result.toUIMessageStreamResponse();
}

第三步:创建 UI 组件 (Client-Side)

这是 Generative UI 的可视部分。我们需要一个纯 React 组件来展示工具返回的数据。

创建 components/weather.tsx

TypeScript

type WeatherProps = {
  temperature: number;
  weather: string;
  location: string;
};

export const Weather = ({ temperature, weather, location }: WeatherProps) => {
  return (
    <div className="border p-4 rounded-lg bg-blue-50 my-2">
      <h2 className="font-bold text-lg">{location} 当前天气h2>
      <p>状况: {weather}p>
      <p>温度: {temperature}°Cp>
    div>
  );
};

第四步:整合前端逻辑 (Client-Side)

这是最关键的一步。我们需要在 page.tsx 中根据 message.parts 的类型来动态渲染内容。

注意: 在 AI SDK 5.0+ 中,工具调用的 part 类型会有特定的命名格式:tool-[工具名]

创建 app/page.tsx

TypeScript

'use client';

import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
import { Weather } from '@/components/weather';

export default function Page() {
  const [input, setInput] = useState('');
  
  // 使用 useChat 钩子管理聊天状态
  const { messages, sendMessage } = useChat();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    sendMessage({ text: input });
    setInput('');
  };

  return (
    
className="max-w-xl mx-auto p-4"> {messages.map(message => (
key={message.id} className="mb-4">
className="font-semibold text-sm text-gray-500"> {message.role === 'user' ? 'User: ' : 'AI: '}
{/* 遍历消息的各个部分 (parts) */} {message.parts.map((part, index) => { // 1. 处理普通文本 if (part.type === 'text') { return key={index}>{part.text}; } // 2. 处理特定工具调用:'displayWeather' // 注意这里的类型匹配字符串:tool-[工具名] if (part.type === 'tool-displayWeather') { // 根据工具调用的不同状态渲染不同 UI switch (part.state) { case 'input-available': return
key={index} className="text-gray-400">正在查询天气...
; case 'output-available': // 工具执行成功,渲染 Weather 组件 // part.output 包含 execute 函数返回的数据 return (
key={index}>
); case 'output-error': return
key={index} className="text-red-500">Error: {part.errorText}
; default: return null; } } return null; })}
))}
onSubmit={handleSubmit} className="fixed bottom-0 left-0 w-full p-4 bg-white border-t">
className="max-w-xl mx-auto flex gap-2"> className="flex-1 p-2 border rounded" value={input} onChange={e => setInput(e.target.value)} placeholder="输入 '查询旧金山天气'..." />
); }

核心要点总结

  1. 强类型工具名:在客户端判断渲染逻辑时,使用 part.type === 'tool-[key]',这里的 [key] 必须对应 ai/tools.ts 中导出对象的键名(即本例中的 displayWeather)。

  2. 状态管理:工具调用有三个主要状态,你可以分别为它们定制 UI:

    • input-available: 工具已被模型调用,正在等待结果(适合展示 Loading 骨架屏)。
    • output-available: 工具执行完成,数据已就绪(展示最终组件)。
    • output-error: 执行出错。
  3. 分离关注点:将数据获取(Server Tool)与数据展示(Client Component)完全解耦,让代码更易维护。

通过这种方式,你可以轻松扩展出查询股票、预订机票、生成图表等丰富的交互功能,让你的 AI 应用从"聊天框"进化为"智能终端"。