Generative-UI工程
核对日期:2026-08-26。
1. 定义与边界
Generative UI 是指 Agent 不只输出文字,还输出可渲染的界面单元:卡片、表格、图表、可编辑产物(Artifact / Canvas)。工程上它是「结构化结果 → 白名单组件」的渲染问题,不是「模型会写 HTML」。
本文件给出生产可用的模式选择、组件注册表和注入防护。流式传输见 流式协议与中断.md;工具状态见 流式Tool-Use与前端状态.md。
明确不做:
- 把模型输出的 HTML / JSX 字符串直接插入 DOM。
- 用
v-html、dangerouslySetInnerHTML、小程序rich-text渲染未消毒的模型内容。 - 把 Claude Artifacts / ChatGPT Canvas 的产品形态当成可以无沙箱复制的能力。
2. 为什么重要
文字答案信息密度低,业务用户要的是订单卡、退款预览、图表。但不加白名单的 Generative UI 会把 XSS、供应链和越权展示一起打开:
- 模型可以在「漂亮的卡片」里塞进钓鱼链接或伪系统提示。
- HTML 字符串一旦进 DOM,审批卡片和聊天记录都变成攻击面。
- 每个用户一套随机布局会破坏设计系统和无障碍。
所以生产默认应是:模型只填 JSON,组件是你写的。
3. 核心机制
UI part 是一种特殊工具结果,不是自由文本:
interface UiPart {
type: 'ui_part';
runId: string;
component: string;
props: Record<string, unknown>;
}
component 必须能在服务端注册表里查到。查不到就降级,不要动态 import(userString)。
4. 架构模式
| 模式 | 做法 | 何时用 | 何时不用 |
|---|---|---|---|
| A. Markdown | 模型出 Markdown,客户端安全渲染 | 解释、结论、引用 | 要强交互控件 |
| B. 结构化数据 + 模板 | 工具 / UI part 出 JSON,固定组件填 props | 订单、天气、商品、审批摘要 | 每次都要全新布局 |
| C. 组件 DSL | 有限组件树 JSON(type + children) | 仪表盘拼装 | DSL 比业务还复杂 |
| D. 沙箱 HTML / JSX | iframe sandbox 或独立运行时 | 代码演示、内部设计稿预览 | 主聊天、主业务路径 |
| E. 服务端组件流 | 如 AI SDK RSC / streamUI | Next.js 且组件全在服务端 | Vue / 小程序 / 多端 |
对本仓库读者(Vue 3、小程序、H5):默认选 B。E 绑定 React Server Components,不能当跨端方案。D 只放在隔离窗口,且禁止同域 cookie。
Vercel AI SDK 可以把工具结果映射成 React 组件并流式到客户端。这是 B/E 的一种实现,不是「模型写 JSX 就安全」。
5. 工程实现
5.1 组件注册表
import type { Component, DefineComponent } from 'vue';
interface OrderCardProps {
orderId: string;
status: 'created' | 'paid' | 'shipped' | 'delivered' | 'cancelled';
amount: string;
}
type UiComponentName = 'order_card' | 'refund_preview' | 'citation_list';
const UI_REGISTRY: Record<UiComponentName, Component> = {
order_card: OrderCard,
refund_preview: RefundPreview,
citation_list: CitationList,
};
function isUiComponentName(value: string): value is UiComponentName {
return value in UI_REGISTRY;
}
function resolveUiPart(part: { component: string; props: unknown }): DefineComponent | Component {
if (!isUiComponentName(part.component)) {
return SafeUnknownPart;
}
return UI_REGISTRY[part.component];
}
props 必须再用运行时校验(Zod 等)收窄。不要 as OrderCardProps 直接传入。
5.2 Vue 3 渲染
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
component: string;
rawProps: unknown;
}>();
const resolved = computed(() => {
if (!isUiComponentName(props.component)) {
return { comp: SafeUnknownPart, data: { label: props.component } };
}
const parsed = parseProps(props.component, props.rawProps);
if (parsed === undefined) {
return { comp: SafeUnknownPart, data: { label: 'invalid_props' } };
}
return { comp: UI_REGISTRY[props.component], data: parsed };
});
</script>
<template>
<component :is="resolved.comp" v-bind="resolved.data" />
</template>
SafeUnknownPart 只渲染纯文本「暂不支持该卡片」,不回显原始 JSON 里的 HTML。
5.3 产物面板(Artifact)
长代码、长文档、图表应离开气泡,进入右侧 / 下层产物面板:
- 气泡保留任务叙事和工具过程。
- 产物有独立版本、复制、下载、再编辑入口。
- 再编辑走「选中片段 + 新指令」,不要把整份产物反复塞回上下文。上下文预算见 ../03-模型与推理能力/上下文窗口管理.md。
小程序没有宽右侧栏时,用全屏页承载产物,而不是在消息列表里塞 WebView。
5.4 禁止项与例外
H5 / Web:禁止用 dangerouslySetInnerHTML 或 Vue v-html 渲染模型输出。若预览用户自己的 Markdown,使用消毒后的解析器,并关掉原始 HTML。
例外(必须同时满足):
- 独立
sandboxiframe,sandbox不含allow-same-origin与allow-top-navigation。 - 内容来自内部只读预览,不带用户 Cookie。
- 有 CSP 和体积上限。
小程序:不要把模型 HTML 丢进 rich-text。rich-text 的节点白名单因端而异,且不是消毒方案。
6. 生产实践
| 实践 | 说明 |
|---|---|
| 组件版本 | order_card@2 进 schema,旧 run 回放仍能渲染 |
| 空态 | 工具成功但字段缺失时出空态,不编造数字 |
| 引用 | 知识卡必须带可点击的文档 id,见 ../06-RAG与知识系统/引用与可追溯性.md |
| 流式骨架 | ui_part 未就绪时用组件骨架,而不是闪烁整页 |
| 设计系统 | 卡片复用业务组件,避免 Agent 专用一套视觉 |
| 降级文案 | 未知 component 时保留可复制的纯文本摘要 |
7. 常见反模式
| 反模式 | 表现 | 后果 | 修正 |
|---|---|---|---|
| 模型写 HTML | 把 HTML 当消息 | XSS、钓鱼 | 模式 B |
v-html 渲染回复 | 图省事 | 注入即 DOM | Markdown 安全子集或纯文本 |
| 动态组件名 | import(part.component) | 任意代码路径 | 静态注册表 |
| 主路径上沙箱网页 | 聊天里嵌未隔离 iframe | cookie / 跳转风险 | 产物进隔离窗口 |
| 每问一新布局 | 没有组件库 | 不可测、不可访问 | 先收口 5–10 个业务卡 |
| 把 RSC 示例搬到小程序 | 复制 Next.js streamUI | 无法运行 | 跨端只用 JSON + 本地组件 |
8. 评测方法
| 指标 | 说明 |
|---|---|
| Unknown Component Rate | 未知 component 占比,高说明 schema 失控 |
| Prop Validation Fail Rate | props 未过校验 |
| XSS Fixture Pass | 红队样本(javascript:、事件处理属性、伪系统横幅)均被纯文本化 |
| Business Card Coverage | 高频任务是否有对应卡片 |
| Artifact Isolation | 产物预览是否同域可读 cookie,目标为否 |
补一组 UI 红队样本到安全评测,见 ../10-Agent评测体系/安全评测.md。
9. 安全与治理
- UI part 的
props与工具返回一样不可信。 - 卡片里的链接只允许 https,并走业务域名白名单。
- 禁止组件根据模型字段直接调用写工具;写操作仍走 HITL。
- 产物下载要鉴权,
run_id不可枚举。 - 日志里不要存完整 HTML 产物,存组件名、props 摘要、版本。
10. 权威资料
- Vercel AI SDK Generative UI / chatbot tools: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage (核对日期:2026-08-26)
- Vercel AI SDK UI overview: https://ai-sdk.dev/docs/ai-sdk-ui/overview (核对日期:2026-08-26)
- OWASP XSS prevention: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html (核对日期:2026-08-26)
- OWASP Top 10 for LLM Applications: https://owasp.org/www-project-top-10-for-large-language-model-applications/ (核对日期:2026-08-26)
- Vue
v-html文档: https://vuejs.org/api/built-in-directives.html#v-html (核对日期:2026-08-26)