AREXOpen in interactive Feed

AREX Feed Article

ChatGPT 上线次日,一篇逆向分析还原 Intelligent UI 全链路:模型写 DIL,服务器每几百毫秒全量重编译一次

October 12, 2026

OpenAI 在 10 月 7 日的 ChatGPT 发布说明中宣布 GPT-6 上线,同时引入 Intelligent UI,官方公告页将其描述为让 ChatGPT “以完全可交互的用户界面快速作答”的新能力(、)。次日(10 月 8 日)16:49(UTC,北京时间 10 月 9 日凌晨),Rabi Shanker Guha()在 X 上发帖并附上长文(),称团队已经把这套功能完整拆开;帖文开头写道,ChatGPT 终于推出了自己的 Generative UI,并“一夜之间把这个领域改名为 Intelligent UI”。

这篇拆解的核心还原是:ChatGPT 的模型在界面上不写供浏览器执行的原始前端代码,而是输出一门 OpenAI 称为 DIL 的语言;服务器不做增量编译,而是每几百毫秒把模型已写出的全部内容重编译成一个 JavaScript 程序加一份 JSON 文档,存为 model_dil_v2 随消息流式下发;客户端在隐藏的沙箱 iframe 里启动 Web Worker(浏览器后台线程)执行程序,用 ChatGPT 自家设计系统的约 70 个原生组件把界面画出来。实现细节全部出自这篇帖子的单方逆向分析;OpenAI 的官方页面描述了功能本身,帖中没有引述 OpenAI 确认这些实现中的任何一条。

DIL:一门为逐 token 生成而设计的界面语言

帖文用一个“团队方案报价”的小例子,把一句话从模型到屏幕的全程跟了一遍。模型写出的原文是(帖文示例):

## Team plan estimateDrag the slider to see the **monthly price** for your team.{@body const [seats,setSeats] = DIL.useState(8)}{@body const price = seats*29}<box border padding={3} gap={2}>  <slider min={1} max={50} value={seats} onChange={setSeats}/>  <title size="xl">${price}/mo</title></box>

标题和那句话是普通 Markdown;<box>、<slider>、<title> 是 ChatGPT 组件目录里的标签;两行 {@body …} 是 JavaScript,第一行声明一个叫 seats 的状态,第二行从它算出 price。滑块绑定 seats,拖动一下价格就变。

帖文把单设一门语言的原因归到模型的写作方式上:模型是一个 token 一个 token 地写出界面的(token 指模型生成文本的最小单元)。DIL 由模型早已熟悉的记号拼成——Markdown、JSX 式标签(React 生态的组件标记写法)、JavaScript——保证写得可靠;语句各自成行,未闭合的元素可以自动补齐,服务器只要在最后一个完整构造处截断,半成品照样能编译。界面必须从第一个 token 起就“还没写完也能用”。

服务端编译:从半成品到可执行程序

客户端执行的从来不是模型写出的原文。按帖文的还原,服务器会把模型输出编译成一个 JavaScript 程序和一份 JSON 文档,随消息存储为 model_dil_v2。编译把每个客户端本需重复的工作提前做掉:标记变成对 __dil.jsx 的普通函数调用,运行时因此不需要 DIL 解析器;表达式包进 __dilSafe,一个表达式抛错只损失一个元素,不会让整次渲染失败;静态文本抽进 constants(常量)表,流式输出里变长的只是数据,程序本身不变;每份状态带稳定 key(示例里是 { key: "seats" }),值在一次次重编译之间存活;写了一半的语句和标签被丢弃,未闭合的元素被补齐,与目录不符的属性被剔除并记为 diagnostics(编译诊断记录)——作者捕获的一条回复里,编译器剔掉了 icon 组件的 fill 属性(记为 unknown_prop)和 box 组件的 gap="1"(记为 invalid_literal)。JSON 文档里除了文本常量,还放着服务器替回复解析好的数据,例如图片搜索结果。

流式下发走 server-sent events(服务器单向推送的事件流),接口是 POST /backend-api/f/conversation。每个事件是对正在成型的消息的一次 JSON-Patch(以补丁描述 JSON 变更的格式)式更新,一条事件通常同时改动原始 DIL 文本和编译产物。作者截到的其中一条更新如下(有删节;被捕获的回复本身是一份“周日和朋友烤羊”的计划):

json
{"o": "patch", "v": [  {"p": "/message/content/parts/0", "o": "append", "v": " Sunday lamb roast with friends — generous food, …"},  {"p": "/message/metadata/model_dil_v2/code", "o": "replace", "v": "DIL.render(__dil.jsx(()=>{…"},  {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"0": "Here's a plan for a proper Sunday lamb roast with friends — …"}},  {"p": "/message/metadata/model_dil_v2/fallbackMarkdown", "o": "append", "v": " Sunday lamb roast with friends — …"}]}

帖文强调,这套编译不是增量的:每隔几百毫秒——多半是每收到一段新的模型输出——模型到目前为止写出的全部内容都会被重新编译一遍再下发;编译从第一个 token 就开始,那时一个标签都还没出现。

沙箱 Worker:函数不出 iframe,拖一次滑块不回模型

程序是模型写的代码,所以它不跑在 ChatGPT 页面里。页面加载一个隐藏 iframe(runner.html),以 allow-scripts 沙箱化、内容安全策略设为 default-src 'none',由它启动一个 Web Worker。帖文还原的加锁过程分三步:求值前,worker 把网络访问、定时器、消息通道和动态代码求值从全局作用域里剥掉,再冻结剩下的全局对象;然后用 new Function 求值程序,DIL、__dil、GenUI 这些运行时对象和目录里的复合组件作为参数传入;最后是 watchdog(看门狗计时器),超时无响应的程序被隔离,worker 随之重启。

运行时是一个 React 风格的小型 reconciler:渲染组件、把 hook 状态(React 组件的内部状态)放进带 key 的槽位,然后把新树与上一棵树比较,把差异编码成一串操作,它自己不画任何东西。帖文那个小例子的首帧被编码成 13 条 CREATE 操作;在线路上,这些操作编码为二进制整数序列,字符串单独存表。函数从不离开 worker——滑块的 onChange 传到页面时只剩一个标识符 fn#1。

页面这端,每条 CREATE 实例化一个 ChatGPT 设计系统里的原生组件,变化到达时带动画呈现;页面只接受已知组件类型的操作,模型输出因此进不了任意标记或样式。交互走反方向:用户把滑块拖到 9,页面把处理器标识符和参数发回 worker,worker 调用 setSeats(9)、重渲染、返回更新操作,全程没有模型调用。

客户端一侧的流式规则,帖文也有交代:每条新更新递给 worker 求值、带着既有状态重渲染,状态靠编译时加的 key 在一次次重编译之间保值;新程序求值或渲染失败时,worker 沿用上一个能跑的程序。帖文还给出动画参数:文本 0.7 秒淡入,新行与新网格项 0.42 秒滑入,图表 1.8 秒画完,容器高度走过渡而不是跳变。

一条被捕获的回复:6,341 字符、84 次更新、约 25 秒流关闭

帖文把流式界面的难点归为三条:多数时刻,输出还是一个跑不起来的半成品程序;用户碰过的组件必须保住状态;图片这类数据不走文本,由服务器另行送达。

作者把一条捕获的回复做成了一张逐次更新的时间线(下图来自该帖):

这条回复共 6,341 个字符、84 次更新,文本流式传输 18.7 秒。帖文的统计列出:52 次更新改变了界面结构,14 次只是文本变长,10 次发生在一个尚未写完的标签内部;图片搜索结果落地 5 次,每次耗时 1.4~1.7 秒。图例另把时间线上的更新分成全量重编译(整个程序重新下发)、只补 constants、无界面变化、图片搜索结果落地四类标记。最后一个 token 出现在 21 秒上下,整条流在约 25 秒处关闭。

组件目录与 AppBlock 逃生口

目录决定模型能要什么。按帖文的描述,模型不从原始布局和样式规则搭界面,而是从 ChatGPT 已经会画的组件里挑,再用 padding={3} 这类 design token(预先定义的设计变量)定样式;个别属性接受像素宽度、hex 色值这样的原始 CSS 值,但 token 优先。这样生成的界面在任何平台上都长得像 ChatGPT 自己。

目录有三部分:ChatGPT 客户端代码组件注册表里定义的约 70 个原生组件,作者捕获的回复里出现了其中 39 个;间距、圆角、颜色、尺寸的 design token;以及由 OpenAI 用 DIL 写好、预先送进沙箱的复合组件,例如 image 和 product——在捕获中,模型用过这些复合组件,但从未自己定义过组件。

有些请求超出原生组件的设计范围,帖文举的例子是一台要用 Web Audio 合成声音的鼓机。这时模型可以写一个 AppBlock:一段自包含的 HTML、CSS 和 JavaScript 直接嵌进回复,在 iframe 里运行,渲染路径与 Intelligent UI 组件不同。作者在回复里补充说,模型能用组件集里的原语组合出大量东西,而作为逃生口,它也拥有直接写原始代码的能力();至于什么请求会触发原始代码路径,他“没法完全确定”,猜测是需要大量 JavaScript——不只是事件处理器和变量——的请求,何时启用由模型自行决定,没有固定规则()。

谁做的拆解,哪些问题还没有答案

帖子的方法论部分写明:所有观察来自作者自己的 ChatGPT 账号、ChatGPT 网页应用产生的流量,以及 chatgpt.com 公开下发的 JavaScript;观察在 2026 年 10 月完成,使用 GPT-6 和 GPT-6 Thinking;分析借助 Codex 与 Claude,文中可视化也由 Claude 生成。被问到具体手段时,作者回帖确认走的是检查 DOM 和网络请求的路子()。长文文末另给了一个更详细的版本,挂在 openui.com 上()。

Guha 并非旁观者。他的 X 资料自述正开发 @openuidev、任 @thesysdev 的 CEO,此前创办的公司被 Google 收购;在回复“这对 Open UI 意味着什么”时,他说 OpenAI 尚未为这个功能(至少目前)发布 API 和 SDK,所以对要给 agent 做界面的开发者来说,“OpenUI 是你目前最好的选择,而且优势巨大”()。

截至 10 月 12 日查询,这条推文获得 2,547 次点赞、约 40.2 万次浏览和 4,498 次收藏。回复里获赞最高的一条(27 个赞)来自用户 Sujal:“在服务端编译一门小语言感觉是对的——把原始生成的 JSX 直接送进生产环境会让我害怕。”()

作者自己也承认拆解没有全部想透——有的地方“看起来非常眼熟”,有的地方“我们到现在还想不明白他们为什么要这么做”;他预告下一篇帖子会把 ChatGPT 的这套实现与自家 OpenUI 做对比()。

参考链接

## Team plan estimateDrag the slider to see the **monthly price** for your team.{@body const [seats,setSeats] = DIL.useState(8)}{@body const price = seats*29}<box border padding={3} gap={2}>  <slider min={1} max={50} value={seats} onChange={setSeats}/>  <title size="xl">${price}/mo</title></box>
{"o": "patch", "v": [  {"p": "/message/content/parts/0", "o": "append", "v": " Sunday lamb roast with friends — generous food, …"},  {"p": "/message/metadata/model_dil_v2/code", "o": "replace", "v": "DIL.render(__dil.jsx(()=>{…"},  {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"0": "Here's a plan for a proper Sunday lamb roast with friends — …"}},  {"p": "/message/metadata/model_dil_v2/fallbackMarkdown", "o": "append", "v": " Sunday lamb roast with friends — …"}]}