生成式界面不是万能
对话界面有个天花板:所有回答都长成一段文字。用户问"帮我看看这个月的支出",聊天框里只能回一段描述;问"对比这三款手机",只能回一张表格的文本版。
生成式界面(Generative UI)想解决的就是这件事——让模型决定"用什么形式回答",而不只是"回答什么"。它可以是参数化的卡片、可以是按 schema 渲染的图表,也可以是一小段组件树。
听起来像下一代交互,但它的边界比想象中窄得多。用错地方,会比纯文本更糟。
1. 三种形态,风险差三个量级
参数化组件(最稳)。 前端预先实现好一组组件(卡片、表格、图表、时间线),模型只返回"用哪个组件 + 什么参数",参数用 JSON Schema 约束。模型不产生结构,只填数据。
组件树 / RSC 流(中等)。 服务端根据模型输出组装组件树,流式发给前端渲染。灵活度高,但需要严格的白名单——能渲染哪些组件、能传哪些 props,必须是枚举。
自由 HTML / 脚本(基本不要用)。 让模型直接产出 HTML 或 JS。它打开了 XSS、钓鱼、任意跳转、任意请求的全部口子,而且没有可靠的沙箱——iframe + sandbox 也挡不住社工(页面本身可以伪装成你的产品)。
2. 它真正解决的问题
不是"让界面更花哨",而是固定卡片装不下长尾意图。
举个具体的:一个数据分析助手,用户可能问"上个月华东区的退款率"(一张指标卡就够)、"对比三个渠道的转化漏斗"(需要漏斗图)、"哪些商品的退款率异常"(需要表格 + 排序)。如果只有一种"文本 + 表格"的输出,前两个问题的体验都很差。
生成式界面把"选形式"这件事交给模型,前端只需要准备一组表达能力足够的组件。这是它的价值,也是它唯一的价值——如果你的场景里输出形式本来就只有一两种,不需要它。
3. 安全:三条硬边界
组件白名单。 模型返回的 component 必须是一个枚举值,映射到前端注册表。不能出现"按名字动态 import"这种设计——那等于把模块加载权交给了模型。
props 用 schema 约束。 每个组件有自己的 props schema,服务端校验通过才下发。特别注意 URL 类字段:必须走域名白名单,不能直接渲染模型给的链接(这是钓鱼最省事的一条路)。
永不执行模型产出的代码。 不 eval、不 new Function、不动态插 <script>、不用 dangerouslySetInnerHTML 渲染模型输出。Markdown 里的 HTML 也要过滤。
再补一条容易被忽略的:图表的数据要能被追溯。生成式界面里出现一个数字,用户会当成事实。它必须能点回数据来源,否则就是把幻觉包装得更可信了(见《答案要能追溯到出处》)。
4. 交互:生成的组件怎么把事件传回来
这是工程上最容易做残的一块。组件是模型选的,但点击之后要发生什么,必须是前端预定义的:
用户点击「查看明细」
→ 组件触发 action: { type: 'drill_down', payload: { metric: 'refund_rate', dim: 'channel' } }
→ 前端把 action 转成一条用户消息(或一次工具调用)
→ 进入下一轮
关键约束:action 的类型是枚举,payload 走 schema 校验。不能让模型生成"点击后跳转到某个 URL"这种自由行为——那等于把导航权交出去。
另一个设计点是状态归属。生成的组件如果是受控的(比如一个筛选器),它的状态应该存在会话层,而不是组件内部——否则流式重渲染一次,用户选的东西就没了。
5. 三个容易被忽略的工程面
流式下的组件渲染。 模型和参数是一边生成一边到的,组件不能等全部收完才出现,但也不能每来一个字就重建一次。可行的做法是分两个阶段:先收到组件类型就渲染骨架,参数到位后填充。这里有个和《半截 Markdown 怎么渲染》一样的取舍——缓冲粒度太细会疯狂重排,太粗又会有一段空白。另外,参数未到达时组件必须能自己渲染成加载态,否则会出现"卡片闪一下变成另一种卡片"。
无障碍。 生成式界面最容易在这一项上失分:动态插入的组件没有焦点管理、没有语义标签、屏幕阅读器读不出来"这里刚出现了一张图"。三条最低要求:动态区域要有合适的 live region 策略(但别把所有内容都设成激进播报,会把用户淹没)、组件本身要有可访问的语义结构、键盘能到达所有可交互元素。这一项在纯文本时代是加分项,在生成式界面里是及格线——因为用户无法预判界面会变成什么样。
降级与兜底。 至少要兜住三种失败:组件不在注册表里、props 校验不通过、前端版本落后于服务端。这三者的兜底都是同一套——退回纯文本渲染,并且把失败记下来。降级静默发生是最糟的情况:用户以为看到的是产品设计的样子,实际是降级结果,而团队看板上一切正常。
渲染安全这一层不在这里展开,统一见《模型输出不能直接塞进 innerHTML》。
6. 三个会翻车的地方
可测性下降。 纯文本可以用字符串断言,生成式界面要断言"渲染了哪个组件、props 对不对"。测试要落在 schema 层,而不是快照层——快照会因为一个字号变化全红。
版本漂移。 模型今天选 BarChart,明天可能选 LineChart。要么在提示里固定可选集,要么接受"同类组件可互换",并在评测里按"信息是否表达清楚"打分,而不是按组件名匹配。
降级路径。 组件渲染失败、schema 不匹配、前端版本落后时,必须有纯文本兜底。没有兜底的生成式界面,一次发布不同步就是白屏。
7. 什么时候不该用
- 关键业务流程(支付、签约、审批):形式必须稳定,不能由模型决定
- 需要审计的场景:监管要看的是"当时展示了什么",动态组件让留证变复杂
- 表单:让模型生成表单,等于让它决定收集哪些字段——这是产品决策,不是生成任务
- 输出形式本来就单一:客服问答、文档摘要,文本 + 引用足够了
判断标准可以很朴素:这个界面的形式,是不是产品经理已经能定下来的? 能定,就别交给模型;定不下来(因为长尾太多),才考虑生成式。
最后一句:生成式界面把"模型的不确定性"从文本层扩散到了界面层。文本错了用户还能看出来,界面错了用户会以为那是产品设计。门槛应该比纯文本更高,而不是更低。