前言:范式革命——当UI成为AI思维的涌现
在使用本指南前,请接受一个颠覆性的观念:我们不再是为AI生成的数据寻找匹配的UI容器。相反,我们正在构建一个环境,让合适的UI成为AI解决问题过程中自然“涌现”的产物。UI不再是预设的终点,而是AI思维过程的可视化、可交互的中间步骤。
本指南将带您掌握这种思想,并将其转化为稳定、可扩展、安全的生产级应用。
第一部分:核心哲学与四大基石
在深入案例前,必须理解支撑 Vercel AI SDK 3.0 生成式UI的四大核心理念。它们是您后续所有架构决策的理论依据。
基石一:UI是AI状态的纯函数 (
UI = f(AIState)
)- 理念: 整个用户界面,在任何时刻,都应是当前AI完整状态(包括对话历史、上下文、内部数据)的确定性渲染结果。
- 实践: 我们使用
createAI
来管理这个核心的、持久化的AIState
。这意味着UI的变更不是通过命令式地“添加”或“移除”组件,而是通过更新AIState
,然后让React根据新状态重新渲染出正确的UI。
基石二:工具是LLM的声明式API (
Tools as a Declarative API for LLMs
)- 理念: 您的React组件不再仅仅是UI片段,它们是提供给LLM的一套功能强大的、类型安全的“API”。您通过Zod Schema和描述(description)来定义这个API的“文档”。
- 实践: LLM的职责是根据用户意图,选择调用哪个“API”(工具),并提供符合“文档”(Schema)的参数。SDK负责执行这个调用并将结果渲染出来。您的工作从“命令AI做什么”转变为“教会AI能用什么”。
基石三:服务器是安全可靠的业务编排器 (
The Secure Orchestrator
)- 理念: 所有核心业务逻辑、数据获取、外部API调用和状态变更,都必须在服务器端的
render
函数或独立的Server Action中执行。 - 实践: 客户端只接收渲染好的UI和无害的数据。API密钥、数据库连接、复杂的业务规则永远不会暴露。这天然地形成了一个安全的后端边界。
- 理念: 所有核心业务逻辑、数据获取、外部API调用和状态变更,都必须在服务器端的
基石四:流式传输是默认的用户体验 (
Streaming as the Default UX
)- 理念: 用户等待是糟糕的体验。系统必须在思考和处理的同时,持续地提供反馈。
- 实践: 优先使用
streamUI
和异步生成器 (async function*
)。这允许您流式传输加载状态、中间思考过程的文本、甚至是部分渲染的UI,从而创造一种“实时协作”而非“请求-响应”的体验。
第二部分:分级案例深潜——从基础构件到复杂工作流
我们将通过三个不同业务领域的案例,逐步展示四大基石如何在实践中组合,以解决日益复杂的问题。
Level 1: 基础构件——让AI掌握“说话”和“展示”
目标:掌握让AI返回单个、有状态的UI组件的核心模式。
案例1.1:智能金融仪表盘小部件 (领域:金融科技)
场景: 用户是金融分析师,他需要快速获取某支股票的关键指标。
实现:
- 工具定义 (
/components/ai/stock-quote.tsx
):import { z } from 'zod'; // Schema是给LLM的“API文档” export const stockQuoteSchema = z.object({ symbol: z.string().describe("The stock ticker symbol, e.g., 'NVDA'"), price: z.number().describe("The latest closing price."), change: z.number().describe("The dollar change from the previous day."), changePercent: z.number().describe("The percentage change."), }); // 组件是纯粹的UI表现 export function StockQuote({ price, change, changePercent, symbol }: z.infer<typeof stockQuoteSchema>) { const isPositive = change >= 0; return ( <div className={`p-4 border-l-4 ${isPositive ? 'border-green-500 bg-green-50' : 'border-red-500 bg-red-50'}`}> <p className="font-bold text-xl">{symbol}</p> <p className="text-2xl">${price.toFixed(2)}</p> <p className={`text-sm ${isPositive ? 'text-green-700' : 'text-red-700'}`}> {isPositive ? '+' : ''}{change.toFixed(2)} ({changePercent.toFixed(2)}%) </p> </div> ); }
- 编排逻辑 (
/app/actions.tsx
):// 在tools对象中 getStockQuote: { description: 'Get and display a real-time quote for a specific stock symbol.', parameters: stockQuoteSchema, render: async function* (props) { // 基石三:服务器端编排。 props已经由SDK根据Schema验证过 // 在真实应用中,我们会在这里调用金融数据API // const quoteData = await fetchQuoteFromFMP(props.symbol); // 这里为了演示,我们直接使用LLM可能幻化出的数据 return <StockQuote {...props} />; } }
架构洞察:
- 这是一个最纯粹的**“展示型”**工具。AI负责理解用户意图(“给我看下英伟达股价”)并从非结构化文本中提取结构化数据(
symbol
),然后调用工具。 render
函数是业务逻辑的封装点。即使LLM幻化出价格数据,render
也可以选择忽略它们,而去调用真实的API,确保数据的准确性。
Level 2: 流式交互——构建有反馈、有过程的体验
目标:掌握使用异步生成器提供加载状态和中间反馈,创造流畅的用户体验。
案例2.1:AI旅行规划助手 (领域:旅游)
场景: 用户计划去巴黎旅行,AI需要分步查询天气、推荐景点,并在每一步都给出反馈。
实现:
- 多阶段UI组件 (
/components/ai/trip-planner.tsx
):// 骨架屏组件 export function TripPlannerSkeleton() { /* ...一个加载中的UI布局... */ } // 最终展示组件 export function TripPlanDisplay({ city, weather, attractions }) { return ( <div> <h3>Trip Plan for {city}</h3> <p>Weather: {weather}</p> <h4>Top Attractions:</h4> <ul>{attractions.map(a => <li key={a}>{a}</li>)}</ul> </div> ); }
- 分步流式编排 (
/app/actions.tsx
):planTrip: { description: 'Plan a trip by fetching weather and top attractions for a city.', parameters: z.object({ city: z.string() }), render: async function* ({ city }) { // 基石四:立即提供反馈 yield <TripPlannerSkeleton />; // 模拟第一个长时任务:获取天气 const weatherPromise = fetchWeatherAPI(city); // 在等待时,可以流式传输文本反馈 yield <p>Checking the weather in {city}...</p>; const weather = await weatherPromise; // 模拟第二个长时任务:获取景点 const attractionsPromise = fetchAttractionsAPI(city); yield <p>Okay, the weather is {weather}. Now finding top attractions...</p>; const attractions = await attractionsPromise; // 返回最终的完整UI return <TripPlanDisplay city={city} weather={weather} attractions={attractions} />; } }
架构洞察:
async function*
是实现过程式UI的关键。yield
关键字将render
函数变成了一个可以暂停和恢复的“状态机”。- 每一个
yield
都向客户端推送一个新的UI状态,客户端无缝地用新UI替换旧UI。这让用户感觉AI是“活”的,正在与他们同步工作。 - 这种模式完美适用于任何需要多个顺序异步调用的场景,如多步表单验证、复杂数据报告生成等。
Level 3: 闭环交互——让生成的UI能够“回话”
目标: 掌握如何让AI生成的UI包含可交互元素(如按钮),这些元素能再次调用Server Actions,形成一个完整的“AI提议 -> 用户决策 -> 系统执行”的闭环。
案例3.1:IT运维机器人 (领域:企业软件/DevOps)
场景: 系统检测到某个服务器CPU使用率过高。AI机器人不仅要报告问题,还要提供可行的解决方案(如“重启服务”),并让授权用户一键执行。
实现:
- 带Action的UI组件 (
/components/ai/alert-card.tsx
):'use client'; // 这个组件需要客户端交互性 import { useActions } from 'ai/rsc'; import { restartServerAction } from '@/app/actions'; // 导入一个独立的Server Action export function HighCpuAlert({ serverId, serviceName }) { const [isSubmitting, setIsSubmitting] = useState(false); const [result, setResult] = useState(''); const handleRestart = async () => { setIsSubmitting(true); const actionResult = await restartServerAction(serverId, serviceName); setResult(actionResult.message); setIsSubmitting(false); }; return ( <div> <p><strong>Alert:</strong> High CPU on {serverId}.</p> <p>Service "{serviceName}" might be the cause.</p> <button onClick={handleRestart} disabled={isSubmitting}> {isSubmitting ? 'Restarting...' : `Restart ${serviceName}`} </button> {result && <p>{result}</p>} </div> ); }
- 独立的执行Action (
/app/actions.tsx
):// 这是一个独立的、可被任何客户端组件调用的Server Action export async function restartServerAction(serverId: string, serviceName: string) { 'use server'; // 基石三:在此执行安全的操作 const session = await getAuth(); // 1. 权限校验 if (!session?.user.isAdmin) { return { success: false, message: 'Error: Unauthorized.' }; } // 2. 执行真正的运维操作 const result = await sshIntoServerAndRestart(serverId, serviceName); // 3. 返回结果 return { success: result.success, message: result.log }; }
- AI的提议工具 (
/app/actions.tsx
中的tools对象):proposeRestart: { description: 'When a server issue is detected, propose a restart action to the user.', parameters: z.object({ serverId: z.string(), serviceName: z.string() }), render: async function* (props) { return <HighCpuAlert {...props} />; } }
架构洞察:
- 我们清晰地分离了**“提议”和“执行”**。AI的职责是使用
proposeRestart
工具来提议,而真正的危险操作被封装在restartServerAction
中。 HighCpuAlert
组件是连接这两者的桥梁。它由AI生成,但它内部的onClick
处理器调用的是一个标准的、权限严格的Server Action。- 这是生成式UI最强大的模式之一。它将LLM的推理能力(诊断问题)与传统软件的严谨性(权限控制、确定性操作)完美结合。
第三部分:生产化与架构升华
目标:将前述模式整合,并考虑在真实生产环境中的健壮性、可扩展性和安全性。
1. 架构模式:AI作为状态机协调器 (AI as a State Machine Coordinator
)
将您的整个应用视为一个大型状态机。用户的输入是触发状态转换的事件。AI的职责是决定下一个状态是什么。
- 浏览商品 -> (用户点击商品) -> 查看详情 -> (AI提议加入购物车) -> 购物车预览
- AI的工具(
showProductDetail
,proposeAddToCart
)就是状态转换的动作。AIState
记录了当前处于哪个宏观状态。
2. 安全策略:纵深防御
- Schema验证是第一道门:
zod
能防止格式错误的输入。 render
函数是业务逻辑的围墙:在这里净化和验证从LLM收到的内容。例如,如果LLM为一个商品生成了不切实际的低价,你的render
函数应该从数据库中拉取真实价格来覆盖它。- 独立Action是最终的堡垒:对于任何会修改数据的操作(如
addToCart
,restartServerAction
),必须在Action内部,而不是在render
函数里,进行最严格的用户身份和权限验证。永远不要相信仅仅因为render
被调用,用户就有权执行后续操作。
3. 高级状态管理:AIState
vs UIState
createAI
允许你定义两种状态:
AIState
: 这是“给AI看”的真相之源。它应该只包含干净、结构化的对话历史和核心数据(如购物车ID和数量)。这是你发送给LLM的messages
数组的来源。UIState
: 这是“给用户看”的临时状态。它可以包含加载指示器、临时的客户端交互状态、AI返回的UI组件等。
实践: 当一个操作只是为了临时更新UI(如显示一个toast通知),你可以只更新UIState
。当一个操作改变了对话的核心事实(如用户确认了一个订单),你必须更新AIState
。这种分离能防止用临时的UI状态“污染”给LLM的上下文,从而节省token并提高准确性。
4. 测试与可观测性
- 工具单元测试: 独立测试你的每个UI组件和其对应的
render
函数。你可以mock掉外部API调用,断言在给定props时,render
函数能否yield
或return
正确的UI组件。 - 端到端测试 (E2E): 使用Playwright或Cypress编写测试脚本,模拟完整的用户对话。例如:“断言当用户输入’查找X’时,页面上最终会出现一个带有特定标题的
ProductCard
组件”。 - 日志与追踪: 在每个
render
函数的入口和出口、以及每个独立Action中,添加详细的日志。记录LLM返回了哪个工具调用、参数是什么、你的业务逻辑是否覆盖了它、最终渲染了哪个UI。这将是你调试和优化应用最重要的遥测数据。
结论
通过本指南的深度实践,您掌握的已远超一个SDK的用法。您学会了一种全新的应用设计哲学,能够将LLM的强大推理能力,安全、高效、且富有创造性地融入到用户界面的每一个像素中。您现在有能力构建的,是能够与用户深度协作、共同完成复杂任务的下一代智能软件。