深度解析

Generative UI 性能优化

如何让 AI 驱动的界面保持高速:流式传输策略、包体积优化和渲染模式。

A
Alex13 分钟阅读

性能悖论

这个悖论很简单:300ms 可能感觉像永恒,而 1.2 秒可能感觉瞬间。在 Generative UI 中这不是理论问题。我有一个生产案例,从内存缓存切换到流式传输骨架屏,感知加载时间降低了 3 倍——同时全组件到达的实际时间增加了 80ms。

LLM 推理对于简单响应是 200–800ms,对于多工具响应是几秒钟。CDN、SSG 和边缘缓存无法消除这个延迟:LLM 决策步骤在每个请求的关键路径上。但界面不必感觉很慢。

这篇文章不是"10 条性能技巧"。它试图区分何时优化值得做,与何时是自我欺骗和工程镀金,以及哪种策略解决哪个具体问题。包含我生产环境的真实数字,而不是博客文章上的基准测试。

何时不要优化

在阅读下面的六个策略之前,先回答三个问题:

  1. 你测量过当前性能吗? 如果没有——关掉这个标签页,先做 TTFC/TTIC 追踪的仪器化。我一半来咨询"一切都很慢"的客户,其实 p50 是 600ms,用户愤怒的原因是布局偏移(CLS),而不是延迟。
  2. 你的 p95 已经在 1.5 秒以内了吗? 那么流式传输骨架屏和乐观 UI 能给你约 20% 的感知提升——代价是一周的工作。把那一周花在功能上。
  3. 你每天活跃用户不到 100? 每分钟两次请求的 Redis 缓存是基础设施货物崇拜。内存 Map 还能撑一年半。

优化不是"总是好的"。以下每个策略都增加了复杂性、失败模式和认知负荷。如果你只有一名工程师,产品还在寻找 PMF——做流式传输骨架屏(策略一),其他先不管。其他的都是过早优化。

权衡取舍表

六个策略,它们的成本,以及各自发挥价值的场景:

策略复杂度TTFC 收益TTIC 收益适用时机
1. 流式传输骨架屏低(几小时)−400…600ms0始终,如果你用 streamUI
2. 并行工具调用低(几小时)0−30…50%generate 中有 ≥2 个独立获取时
3. 响应缓存中(几天)0缓存命中时 −500…800ms查询每用户每天重复 ≥10 次
4. 模型选择低(几小时)0−200…500ms简单工具选择,不需要推理
5. Bundle 优化中(几天)冷加载 −100…300ms0Bundle > 200KB 或移动端用户多
6. 乐观 UI中(几天)−150…250ms0查询可从关键词预测

如果强迫在成熟流量产品上按收益 ÷ 复杂度排序,顺序是:1 → 4 → 2 → 6 → 3 → 5。策略 3 和 5 的回报时间比预期晚,这是我反复遇到的"浪费了一周"的项目。

需要关注的指标

优化之前,明确你要测量什么:

首次组件时间(TTFC): 用户看到任何 AI 生成元素(哪怕是加载状态)需要多久。目标:200ms 以内。通过在推理进行时立即流式传输骨架屏可以实现。

可交互组件时间(TTIC): 第一个真实的、填充了数据的组件出现需要多久。目标:800ms 以内。这是第一次工具调用的 LLM 推理结束点。

流式传输完成时间: 所有生成的组件都加载完成需要多久。随工具调用次数而变。有了流式传输,这比 TTFC 和 TTIC 不那么重要。

布局偏移分数(CLS): 生成的组件在加载时不应该造成页面布局移动。骨架屏必须与最终组件大小匹配。

策略一:立即流式传输骨架屏

影响最大的单一优化是在 LLM 解析第一个参数之前就流式传输加载骨架。Vercel AI SDK 的生成器模式直接支持这种做法:

tools: {
  revenueChart: {
    description: 'Display a revenue chart',
    parameters: z.object({
      period: z.string(),
      data: z.array(z.object({ date: z.string(), value: z.number() })),
    }),
    generate: async function* (params) {
      // 这个 yield 立即执行——在参数解析之前
      // 骨架屏在时间零出现
      yield <ChartSkeleton />;

      // 可选地,在 AI 解析参数时获取真实数据
      // 两者都就绪时组件出现
      return <RevenueChart {...params} />;
    },
  },
}

yield 语句同步执行。用户在与初始请求相同的往返中看到骨架屏。LLM 推理并行进行。这就是为什么 TTFC 可以低于 200ms,即使 TTIC 是 800ms。

关键细节: 骨架屏必须匹配最终组件的尺寸。如果骨架屏是 100px 高,加载完成的组件是 300px,你就有了影响 CLS 的布局偏移,感觉很不自然。

// 不好:通用骨架屏与组件大小不匹配
yield <div className="h-8 animate-pulse bg-muted rounded" />;

// 好:匹配组件的骨架屏
yield (
  <div className="rounded-lg border p-6 h-64">
    <div className="h-4 w-32 animate-pulse bg-muted rounded mb-4" />
    <div className="h-48 w-full animate-pulse bg-muted rounded" />
  </div>
);

策略二:并行工具调用

当 AI 需要调用多个工具时,它们应该并行执行。Vercel AI SDK 会自动处理这个问题——单个响应中的多个工具调用会并发运行其 generate 函数。

但你的组件数据获取不能阻塞:

// 慢:generate 内部顺序数据获取
generate: async function* ({ userId, period }) {
  yield <DashboardSkeleton />;
  const revenue = await fetchRevenue(userId, period);      // 200ms
  const users = await fetchUsers(userId, period);          // 150ms
  const conversions = await fetchConversions(userId);      // 100ms
  // 总计:约 450ms
  return <Dashboard revenue={revenue} users={users} conversions={conversions} />;
},

// 快:并行数据获取
generate: async function* ({ userId, period }) {
  yield <DashboardSkeleton />;
  const [revenue, users, conversions] = await Promise.all([
    fetchRevenue(userId, period),
    fetchUsers(userId, period),
    fetchConversions(userId),
  ]);
  // 总计:约 200ms(最慢的那个)
  return <Dashboard revenue={revenue} users={users} conversions={conversions} />;
},

策略三:响应缓存

对于重复查询,缓存可以完全消除推理延迟。但缓存有代价:陈旧性、内存压力和缓存失效的复杂性。

对于 Generative UI,在实现缓存之前先回答:你的查询真的会重复吗?内部工具通常会("显示本周的营收"每天上午 9 点都会被查询)。消费者聊天通常不会(每个用户的问题都是独特的)。

在你有数据之前不要实现响应缓存。"缓存可能有帮助"不是实现它的理由——"我的日志显示 X 查询每天被 Y 用户查询 Z 次"才是。

策略四:模型选择

对于简单的工具选择(5 个以内的工具,明确的描述),较小的模型通常和较大的模型一样好——而且快 200–500ms,成本低一个数量级。

粗略的经验法则:

  • gpt-4o-mini / Claude Haiku:适合 ≤10 个工具,描述清晰,无需多步推理
  • gpt-4o / Claude Sonnet:适合复杂工具集,需要跨多次工具调用进行推理
  • Opus / GPT-4 完整版:几乎不需要用于工具选择;为分析或长上下文任务保留

在你的真实提示词和工具集上测试两个模型。关注工具选择质量(正确工具调用百分比),而不仅仅是延迟。迁移到更便宜的模型,直到质量下降,然后退回去。

策略五:Bundle 优化

Generative UI 可能加重 bundle,原因有两个:组件库本身,以及客户端渲染运行时(用于 Thesys / CopilotKit 等方式)。

对于使用 Vercel AI SDK 的 Next.js 项目,大多数 bundle 优化是标准的 Next.js 实践。但有一些 GenUI 特有的陷阱:

不要急切导入所有组件。 如果你的注册表有 20 个组件但大多数查询只使用其中 3 个,对其他 17 个进行懒加载:

// 每个组件懒加载
const LazyRevenueChart = dynamic(() => import('@/components/RevenueChart'), {
  loading: () => <ChartSkeleton />,
});

测量,再优化。 在 bundle 优化上花任何工程时间之前,运行 next build && next-bundle-analyzer(或 @next/bundle-analyzer 包),确认组件库实际上是问题所在。我很多次都发现罪魁祸首不是组件,而是一个未预期的大型依赖。

策略六:乐观 UI

乐观 UI 是在 AI 完成推理之前预渲染"最可能的"组件。当查询可以从关键词预测时效果最好:

function predictComponent(query: string): ToolName | null {
  if (/revenue|sales|income/i.test(query)) return 'revenueChart';
  if (/user|customer|people/i.test(query)) return 'userTable';
  if (/alert|warning|error/i.test(query)) return 'alertBanner';
  return null;
}

如果预测正确,用户看到组件骨架屏立即出现,然后在推理完成时无缝切换到数据填充版本。如果预测错误,回退到标准的流式传输骨架屏。

在正确预测率低于约 70% 之前,这个策略会适得其反(用户看到骨架屏闪烁为不同的组件)。先测量预测准确性,再部署这个优化。

实际情况:我实际数字的经验

这些来自真实的生产部署,不是基准测试环境:

优化TTFC 前后TTIC 前后实施成本
流式传输骨架屏(策略一)1,200ms → 80ms不变(1,200ms)4 小时
并行工具调用(策略二)不变450ms → 210ms2 小时
模型降级(策略四)不变680ms → 340ms1 小时(测试时间)
骨架屏大小匹配(策略一变体)不变不变CLS 从 0.18 降到 0.02(8 小时)

策略一中 TTFC 从 1,200ms 降到 80ms 不是错误——那是将 LLM 推理从关键路径上完全移除(对于感知速度而言)。用户感觉界面是即时的,即使完整组件需要 1.2 秒才能到达。

生产性能清单

在发布之前:

  • TTFC 和 TTIC 监控已就位(告警阈值:TTFC > 500ms,TTIC > 2s)
  • 每个工具都有匹配最终组件大小的骨架屏
  • CLS 分数在真实使用中低于 0.1
  • 如果有 ≥2 个独立数据获取,使用 Promise.all
  • 在你的工具集上测试过 mini 模型
  • 使用 bundle 分析器运行过 next build(如果使用 Next.js)
  • 至少在 100 次真实查询上记录了工具选择分布,然后再优化

最后一个项目是最经常被跳过的。在你知道用户实际查询什么之前,你不知道哪个工具需要最快的骨架屏,哪个路径会从缓存中最受益,哪个组件负责大部分 bundle 重量。先测量。


需要帮助让你的 GenUI 应用性能更快?让我们讨论你的具体设置——我见过的大多数性能问题都在仪器化数据可用后的一小时内得到解决。

分享TwitterLinkedIn邮件
performanceoptimizationstreaminggenerative-ui
A

Alex

Generative UI Engineer & Consultant

专注于 AI 界面与 Generative UI 系统的资深工程师。帮助产品团队用正确的 GenUI 技术栈更快交付。

掌握 Generative UI 前沿动态

每周文章、框架更新与实用实现指南——直达你的邮箱。

我们尊重你的隐私。随时退订。

需要帮助实现你刚读到的内容?

预约免费咨询