跳到主要内容

生成式 UI 作为一种生产规程:当模型渲染屏幕时

· 阅读需 14 分钟
Tian Pan
Software Engineer

上周二发布给用户的按钮标签从未经过文案人员之手,从未在 Figma 中评审过,从未进行过 QA,甚至在推理阶段(inference time)之前都不存在。它是由一个模型生成的,该模型在对话中途决定,收集送货地址的正确方式是渲染一个包含六个字段的内联表单,而不是再进行三轮文字交流。表单生效了,标签也没问题。团队中没有人能告诉你究竟是哪次模型运行生成了它,因为追踪记录(trace)已经从热存储中移出,而评估套件测试的是文本输出,而非组件图。

这就是生产环境中的生成式 UI(Generative UI):模型不再仅仅是一个偶尔调用工具的文本生成器。它是一个输出为组件树的 UI 编译器,而设计系统现在是模型必须遵守的契约,而不仅仅是人类松散遵循的指南。这种转变打破了一整套假设——针对静态规范的 QA、固定布局的无障碍审计、最终字符串的文案审查、构建时的设计系统一致性检查——而大多数团队在替换掉这些旧流程之前,就已经发布了功能。

这种模式现在已经悄无声息地无处不在。智能体产品渲染动态表单来收集槽值(slot values),而不是通过对话询问。对话式仪表盘根据模型针对问题选择的基础库(图表、表格、KPI 卡片、过滤器标签)为每个会话进行组合。新手引导流程(Onboarding flows)完全跳过了静态规范:智能体根据用户设定的目标,决定询问哪些字段、跳过哪些字段以及如何布局。像 A2UI 这样的开放标准定义了一种声明式格式,智能体输出一份扁平的类型化组件列表,而客户端根据受信任的目录对其进行渲染。像 Vercel 的 json-render 和 AI SDK 的 RSC 流式传输等框架,已经让底层的逻辑连接变得非常简单。逻辑连接并不是难点,围绕逻辑连接的规范才是难点,这也是缺乏计划的团队积累隐蔽缺陷速度超过其发现速度的地方。

设计系统成为一种类型系统

首先需要明确的是,你的组件词汇表不再是建议,而是模型被迫遵循并输出的 Schema。任何在生产环境中发布过此类产品的团队都不会允许模型输出自由的 HTML 或任意的 React 代码。其爆炸半径(blast radius)太大:自由形式的输出通道意味着提示词注入可以渲染任意控件,无障碍性退化变得无法控制,设计评审也会变成一场针对模型每次请求都可能重生成的输出空间的、西西弗斯式的差异对比。

行之有效的模式是一个受限的组件目录——Card、Button、TextField、Select、List、Row、Column,带有明确的 props 和明确允许的子组件——以 JSON Schema 或 Zod 定义的形式暴露出来,模型据此产生结构化输出。A2UI 将其规范化为类型化组件的邻接表加上客户端定义的目录,智能体无法逃脱这个范围。Vercel 的 json-render 对组件目录和按钮允许调用的操作都使用 Zod Schema。心智模型是一样的:模型从有限的词汇中进行选择,验证器拒绝任何超出范围的内容,渲染器是一个从验证过的树到 DOM 的纯函数。

这种规范带来了三件让初次尝试的团队感到惊讶的事情:

  • Schema 验证是运行时门控,而非构建时检查。 模型在任何请求中都可能生成无法渲染的组件组合——一个子项不是列表项的 List,一个没有选项的 Select,一个有标签但未绑定的 TextField。验证器在每次输出时运行,而不只是在 CI 中运行,回退路径(fallback path)是第一级的产品表面,而不是一个错误页面。
  • 目录必须足够小,以便模型在工作记忆中容纳。 拥有 200 个组件的设计系统过于庞大;模型会选择次优组件或产生 props 幻觉。生产环境的组件目录通常收敛于 20–40 个基础组件(primitives)加上少数组合模式,设计系统的其余部分只能通过组合来实现。
  • Props 是契约的一部分,而非事后补救。 “按钮可以有一个 onClick”不是契约;契约是“按钮有一个 action prop,它指定了来自封闭枚举(closed enum)的已注册处理程序。”如果模型可以输出任意字符串作为点击目标,你就在以一种新的形式重新引入 unsafe-eval 问题。

无障碍性不是模型能自动搞定的事

对 AI 生成的前端代码的审计总能发现同样的问题:当允许模型输出原始标记(raw markup)时,它会生成 <div onClick> 而不是 <button>,遗漏 ARIA 状态属性,以及没有键盘处理的自定义控件。训练数据是罪魁祸首——React 的公共语料库被 <div> 模式占据——无论多少提示词工程(prompt engineering)都无法可靠地修复它。CSS 可以让 <div> 看起来像个按钮,但只有 HTML 语义才能让它真正成为一个按钮。

在生成式 UI 中,这不再仅仅是一个前端代码整洁(hygiene)问题,而是一个架构问题。你目录中的组件必须在构建时就是无障碍的,因为不能指望模型去应用正确的角色(roles)、聚焦顺序和标签。做得好的团队会选择一个基础组件库——Radix、React Aria、Headless UI——它们自带内置语义,然后只向模型暴露这些组件。模型选择渲染哪个控件以及如何标注它;基础组件保证渲染出的控件对屏幕阅读器是可用的、可以用键盘导航的,并且能正确播报状态变化。

这转移了无障碍审计发生的环节。你不再审计一个固定的页面——因为已经没有固定的页面了。你审计的是组件目录。每个基础组件都要经过一次性的、高标准的无障碍认证,模型的自由度被限制在这些认证范围内。然后,评估套件会验证模型在上下文中是否选择了语义正确的组件(例如,“提交”功能被渲染为 Button 而不是带有 onClick 的 Card),但主要的无障碍性工作量在组件库中,而不是在运行时检查中。

UI 作为输出的评估覆盖 (Eval Coverage on UI-as-Output)

文本输出的评估通常侧重于事实准确性、语气和拒绝行为。而 UI 输出除了这些,还需要四个文本评估无法涵盖的额外维度:

  • 功能正确性 (Functional correctness) —— 渲染出的组件树是否真的能让用户完成任务?一个表单如果以错误的顺序请求正确的字段,那就是错误的。
  • 设计系统遵循度 (Design-system adherence) —— 输出是否使用了经过授权的组件、属性 (props) 和间距标记 (spacing tokens)?一个看起来不错但超出了组件库范围的界面,是一种会随着时间推移破坏设计系统稳定性的慢性回归。
  • 布局完整性 (Layout integrity) —— 在不同的断点 (breakpoints)、语言环境和从右到左 (RTL) 的脚本下,输出是否渲染正确?一个在训练分布中从未见过 RTL 流量的模型,会非常自信地生成在阿拉伯语或希伯来语渲染下崩溃的布局。
加载中…
References:Let's stay in touch and Follow me for more thoughts and updates