暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

TokUI 的 DSL 语法设计哲学

溯朢 2026-06-25
30

每一个做 AI 对话产品的团队,最终都会遇到同一个问题:用什么格式描述 AI 输出的 UI?有人选 Markdown——简单但无法表达交互组件。有人选 JSON——结构化但太重,大模型生成容易括号不匹配。有人选 HTML——标签冗长且 XSS 风险高。

TokUI 选择了一条不同的路:设计一门极简的 DSL,专为 AI 流式生成而优化。这篇文章拆解这门语言的设计取舍。

一、方括号语法:为什么不是尖括号

HTML 用尖括号 <tag> 有历史包袱——SGML 时代的遗产。但 AI 生成尖括号标签时有三个问题:标签名冗长(<button>[btn] 多耗 Token)、闭合标签冗余(</button> 太长)、属性引号规则不一致(有时可省有时不行)。

TokUI 的 DSL 用方括号加极短标签名:

[btn tx:确认 v:primary clk:onSubmit]
[h1 产品概览]
[card tt:用户信息] 内容 [/card]

设计原则是一个字符能省就省tx 代替 textclk 代替 onclickv 既是 value 又是 variant,ph 代替 placeholder。这不是随意缩写,而是在大模型 Tokenizer 的词表中反复验证后的选择——每个缩写都确保在主流模型(GPT、GLM、Claude)中是一个完整 Token。

从 JBoltAI 的实践经验来看,DSL 描述同一段 UI 的 Token 消耗约为 HTML 的 40%,约为 JSON 的 55%。在 AI 对话的流式场景下,省 Token 就是省延迟、省成本。

二、属性系统:冒号分隔与布尔简写

DSL 的属性格式是 key:value,空格分隔多个属性。两种特殊处理体现了工程精度。

布尔属性只写 key。 很多组件属性是开关式的——req(必填)、dis(禁用)、stripe(斑马纹)。写 req:true 纯属浪费 Token,直接写 req 即可生效:

[input l:用户名 ph:请输入 req]
[btn tx:提交 v:primary dis]
[table stripe]

解析器内部有一个 BOOLEAN_ATTRS 集合维护所有布尔属性名,遇到这些 key 不需要 value 就设为 true。

引号内值可含空格和特殊字符。 普通属性值以空格为分隔符,但用双引号包裹的值可以包含空格、逗号甚至方括号:

[btn tx:"含空格的按钮文本" v:"primary,pill"]
[stat tt:总营收 v:"¥1.2亿" trend:up]

从 JBoltAI 踩过的坑来看,最容易出错的是属性值里含冒号的情况——链接地址中的 :// 会被误解析为嵌套属性。解决方式是引号包裹整个值。

三、容器与自闭合:对标 HTML 但更宽容

组件分两类:自闭合和容器。自闭合组件是一个原子标签,不包含子节点。容器组件必须开闭配对,子节点放在中间。

[h1 这是自闭合标题]
[card tt:卡片标题]
  [p 这是容器的子节点]
  [btn tx:操作]
[/card]

关键设计决策是容错。AI 写 DSL 时经常带 HTML 肌肉记忆——不写闭合标签、容器跨行漏写 ]。TokUI 的解析器不是零容忍报错,而是隐式补全:

[list]
  [item 第一项]
  [item 第二项]
  [item 第三项]
[/list]

上面三个 [item] 都没有 [/item],但解析器在新 item 开标签时会自动关闭前一个。这是对标 HTML <li> 的裸标签语义。从 JBoltAI 踩过的坑来看,早期严格校验时约 15% 的 AI 输出因标签不闭合而渲染失败,容错机制把这个数字降到接近零。

四、变体系统:白名单而非自由拼接

传统前端组件库的 class 是开放的——开发者可以随意拼接 className。但在 AI 生成内容的场景下,自由 class 意味着 XSS 注入风险。

TokUI 用 VARIANTS 白名单约束变体。每个组件预定义允许的变体名:

[btn v:primary 主按钮]
[btn v:"danger,pill" 危险圆角]
[btn v:"success,sm" 成功小尺寸]
[h1 v:underline 带下划线标题]
[p v:muted 弱化文本]

v:primary 渲染为 CSS 类 tokui-btn--primary。如果 AI 输出了白名单中不存在的变体名,解析器静默丢弃,不报错也不注入。这既保证了视觉一致性,也堵死了 class 注入攻击面。

五、原始内容模式:代码块内的方括号不解析

代码块、Markdown、diff、terminal 这些容器内部的内容是原始文本,里面的 [ 不应该被解析为标签。否则一段含 arr[0] 的代码会让解析器崩溃。

[code js]
const arr = [1, 2, 3];
arr.forEach((item, i) => {
  console.log(`arr[${i}] = ${item}`);
});
[/code]

进入原始内容模式后,解析器只认对应的闭标签 [/code],中间所有 [ 都视为字面文本。这个设计看似简单,但在流式场景下极其复杂——闭标签可能被 SSE 分块劈成 [/code],解析器必须回持半截闭标签不发,等下一块到齐再判断。



六、动态更新协议:upd 的精准定位

传统前端更新 UI 的方式是重新渲染整棵 DOM 树。TokUI 的 upd 组件只更新目标元素的值和状态,不触碰其他 DOM:

[progress id:cpu v:35 l:CPU使用率]
[upd id:cpu v:67]
[upd id:cpu v:89 status:error]

upd 通过 id 精准定位已渲染的元素,单次更新耗时在毫秒级。以 JBoltAI 的实践来看,这个机制在实时监控看板场景下表现极好——每秒推送几十次 upd 更新,页面流畅无卡顿。



设计取舍总结

设计决策选择替代方案理由
标签分隔符[...]<...> 或 JSON短、省 Token、AI 友好
属性格式key:valueJSON key-value无逗号无引号开销
布尔属性只写 keykey:true省 6 个字符
容错策略隐式闭合严格报错AI 输出 15% 失败率→0
变体约束白名单自由 class防 XSS 注入
原始内容模式切换转义处理流式逐字渲染的基石

以 JBoltAI 的工程经验来看,DSL 设计的核心矛盾是"表达能力 vs 生成可靠性"。JSON 表达力强但 AI 生成易出错,Markdown 可靠但无法表达交互。TokUI DSL 的选择是:用最小的语法集覆盖最多的组件形态,同时容忍 AI 输出的所有毛刺。 这不是理论上的最优解,是在真实 AI 对话产品中打磨出来的工程最优解。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论