生成式 UI 上线前:用 JSON Schema 限制组件、属性和事件

发布时间:2026/8/16 9:41:58
生成式 UI 上线前:用 JSON Schema 限制组件、属性和事件 生成式 UI 上线前用 JSON Schema 限制组件、属性和事件生成式 UI 返回的 Schema 就是不可信输入。组件、属性、事件和数据绑定都要过白名单与 JSON Schema流式片段没闭合前不要急着渲染。为什么生成式 UI 在生产环境容易崩盘在传统的低代码平台中UI 描述文件Schema是由拖拽编译器生成的数据结构高度可控。但到了 Generative UI 场景Schema 是由概率模型吐出来的。这会带来三类工程风险。第一幻觉字段导致运行时崩溃。LLM 会随机篡改属性名比如把props写成properties或者把数字写成带有单位的字符串。第二流式片段不适合作为完整 Schema 逐次渲染。应在边界完整、校验通过后提交 UI或使用明确的增量协议批量更新。第三动态组件注入带来的 XSS 安全风险。生成式 UI 如果允许直接渲染自定义 HTML 或执行内联脚本一旦 Prompt 被恶意注入攻击者就能轻松窃取 Cookie。解决方案不能靠在 Prompt 里写“请务必返回合法 JSON”而应在前端接入与部署拓扑中构建硬隔离规则。Ajv 强校验与带防抖的 Vue3 动态 UI 渲染器我们在上线前重构了渲染管道所有 LLM 返回的数据应先经过 AjvJSON Schema 校验器只有符合 Schema 定义的节点才能提交给 UI 渲染线程。同时引入防抖合并机制避免流式响应造成的频繁重渲染。下面是完整且可运行的 Vue3 动态生成式 UI 渲染器实现。import { defineComponent, h, ref, watch, onUnmounted, type PropType, type VNode } from vue; import Ajv, { type JSONSchemaType } from ajv; // 定义标准的生成式组件 Schema 结构 export interface DynamicComponentNode { type: string; id: string; props?: Recordstring, unknown; children?: DynamicComponentNode[]; } const ajv new Ajv({ coerceTypes: true, useDefaults: true }); // 严格的 JSON Schema 定义 const componentSchema: JSONSchemaTypeDynamicComponentNode { type: object, properties: { type: { type: string }, id: { type: string }, props: { type: object, nullable: true, required: [] }, children: { type: array, items: { $ref: # }, nullable: true, }, }, required: [type, id], additionalProperties: false, }; const validateSchema ajv.compile(componentSchema); export const GenerativeUIRenderer defineComponent({ name: GenerativeUIRenderer, props: { rawStreamSchema: { type: String, required: true, }, componentRegistry: { type: Object as PropTypeRecordstring, ReturnTypetypeof defineComponent | string, required: true, }, debounceMs: { type: Number, default: 60, }, }, setup(props) { const validatedAST refDynamicComponentNode | null(null); const parseError refstring | null(null); let debounceTimer: ReturnTypetypeof setTimeout | null null; /** * 确定性校验与安全清洗引擎 */ const parseAndSanitize (jsonText: string) { try { const parsed JSON.parse(jsonText); const valid validateSchema(parsed); if (!valid) { console.warn([Schema Security Alert] JSON 包含合法性缺陷:, validateSchema.errors); parseError.value 组件 Schema 校验失败无法渲染未知结构; return; } parseError.value null; validatedAST.value parsed as DynamicComponentNode; } catch { // 捕获流式传输中尚未闭合的 JSON 语法异常不做处理等待后续 Token } }; watch( () props.rawStreamSchema, (newVal) { if (debounceTimer) clearTimeout(debounceTimer); debounceTimer setTimeout(() { parseAndSanitize(newVal); }, props.debounceMs); }, { immediate: true } ); onUnmounted(() { if (debounceTimer) clearTimeout(debounceTimer); }); /** * 递归将 Schema 映射为 Vue VNode 节点树 */ const renderNode (node: DynamicComponentNode): VNode { const TargetComponent props.componentRegistry[node.type]; if (!TargetComponent) { return h(div, { class: ui-fallback-error }, 不支持的组件类型${node.type}); } const childrenVNodes node.children Array.isArray(node.children) ? node.children.map((child) renderNode(child)) : []; // 防范 XSS 注入清除潜在的 dangerouslySetInnerHTML 属性 const safeProps { ...(node.props || {}) }; delete safeProps.innerHTML; delete safeProps.dangerouslySetInnerHTML; safeProps.key node.id; return h(TargetComponent, safeProps, () childrenVNodes); }; return () { if (parseError.value) { return h(div, { class: ui-fallback-error }, parseError.value); } if (!validatedAST.value) { return h(div, { class: ui-loading }, UI 构建中...); } return renderNode(validatedAST.value); }; }, });生产环境部署拓扑与静态资源收口把 Generative UI 打包发布上线时环境配置治理同样容易踩坑。静态资源需要按内容区分缓存策略。带内容 hash 的 JS、CSS 可以配置较长缓存HTML 和会变化的配置则应使用可重新验证的缓存策略。具体时长需与发布和回滚流程一起验证。模型返回的动态配置通常不应被共享缓存若其中含有用户数据应使用合适的私有缓存或no-store策略。入口 HTML 通常使用no-cache以便在发布后重新验证。前端构建产物不能包含模型 API Key。模型请求应由服务端代理并结合身份、配额、限流和审计设计访问控制限额应根据业务流量设定不宜照搬固定数值。无论界面是否由模型生成结构校验、组件白名单和服务端权限边界都应保留。