ChatGPT наконец-то запустил Generative UI и за одну ночь переименовал это направление в Intelligent UI. Нам просто необходимо было разобраться, как они это реализовали.
https://x.com/OpenAI/status/2107894997538525580
Эта статья — подробный разбор того, как OpenAI реализовала свой флагманский Intelligent UI: слои, форматы и нативный рендеринг на Web и Mobile.
Строительные блоки
В реализации ChatGPT работа разделена между моделью, бэкенд-сервером и клиентом:
- Формат инференса: модель описывает интерфейс на языке DIL, который объединяет Markdown, JSX-подобные теги и JavaScript.
- Компиляция на сервере: сервер преобразует каждый фрагмент ответа в программу на JavaScript и JSON-документ с текстом и данными.
- Клиентский рантайм: изолированная среда (sandbox) выполняет программу и генерирует UI-операции.
- Рендеринг: ChatGPT применяет эти операции к собственным нативным компонентам.
- Дизайн-система и каталог: компоненты, свойства и дизайн-токены, доступные модели.

Схема архитектуры Intelligent UI в ChatGPT
Формат инференса
Именно это выводит модель. В ChatGPT используется язык, который OpenAI называет DIL: Markdown для текста, JSX-подобные теги для компонентов и JavaScript для состояния и логики. Проследим путь одного небольшого ответа через все слои:
1## Оценка стоимости тарифа Team2Перетащите ползунок, чтобы увидеть **месячную стоимость** для вашей команды.3{@body const [seats,setSeats] = DIL.useState(8)}4{@body const price = seats*29}5<box border padding={3} gap={2}>6 <slider min={1} max={50} value={seats} onChange={setSeats}/>7 <title size="xl">${price}/мес</title>8</box>
Заголовок и абзац — обычный Markdown. Теги — компоненты из каталога ChatGPT. Две строки {@body …} — это JavaScript: первая объявляет состояние seats, вторая вычисляет на его основе price. Ползунок привязан к seats, поэтому при его перемещении цена обновляется.
Отдельный формат необходим, потому что модель генерирует интерфейс токен за токеном.
- Его должно быть легко писать без ошибок, поэтому он построен на нотациях, которые модель уже хорошо знает.
- Он должен оставаться рабочим даже в недописанном виде. Операторы располагаются на отдельных строках, а любой незакрытый элемент может быть закрыт автоматически. Это позволяет серверу обрезать частичный ответ по последнему завершённому блоку и всё равно его скомпилировать.
Компиляция на сервере
Клиент никогда не выполняет вывод модели «как есть». Сервер OpenAI компилирует его в программу на JavaScript и JSON-документ, которые сохраняются вместе с сообщением (как model_dil_v2). Вот во что компилируется приведённый выше ответ (отформатировано для удобства чтения):
1function __dilSafe(evaluate, failureValue) {2 try { return evaluate(); } catch { return failureValue; }3}45DIL.render(__dil.jsx(() => {6 const __dilConstants = DIL.useConstants();7 const __dilModelDataBindings = DIL.useAppData((appData) => appData.opGenui?.modelDataBindings ?? {});8 const [seats, setSeats] = DIL.useState(8, { key: "seats" });9 const price = __dilSafe(() => seats * 29, undefined);10 return __dil.jsx(__dil.Fragment, null,11 __dil.jsx("title", { size: "lg" }, __dilConstants["0"]),12 __dil.jsx("text", null, __dilConstants["1"], __dil.jsx("bold", null, __dilConstants["2"]), __dilConstants["3"]),13 __dil.jsx("box", { border: true, padding: 3, gap: 2 },14 __dilSafe(() => __dil.jsx("slider", { min: 1, max: 50, value: seats, onChange: setSeats }), null),15 __dil.jsx("title", { size: "xl" }, __dilConstants["4"], __dilSafe(() => price, null), __dilConstants["5"])));16}, { key: "body:2" }));
1{2 "constants": {3 "0": "Оценка стоимости тарифа Team",4 "1": "Перетащите ползунок, чтобы увидеть ",5 "2": "месячную стоимость",6 "3": " для вашей команды.",7 "4": "$",8 "5": "/мес"9 },10 "appData": { "opGenui": { "componentResults": {}, "modelDataBindings": {} } }11}
Markdown компилируется в то же дерево, что и компоненты. Заголовок становится title, абзац — text с bold внутри, а их содержимое переносится в таблицу констант.
Компиляция берёт на себя работу, которую иначе пришлось бы повторять каждому клиенту:
- Обычные вызовы функций. Разметка превращается в вызовы __dil.jsx, благодаря чему JavaScript-рантайм может выполнить программу без парсера DIL.
- Изоляция ошибок. Выражения оборачиваются в __dilSafe, поэтому ошибка в одном выражении удаляет только один элемент, а не прерывает весь рендеринг.
- Текст в отдельной таблице. Статический текст переносится в таблицу констант, поэтому при стриминге ответа растущий текст меняет данные, а не саму программу.
- Стабильные ключи состояний. Каждое состояние получает ключ ({ key: "seats" }), благодаря чему его значение сохраняется при каждой перекомпиляции.
- Восстановление и валидация. Незавершённые операторы и теги отбрасываются, незакрытые элементы закрываются, а свойства, не прошедшие валидацию по каталогу, удаляются и записываются в диагностические сообщения.
JSON-документ содержит текстовые константы и любые данные, которые сервер подготавливает для ответа, например результаты поиска изображений (см. раздел «Данные»).
Клиентский рантайм
Клиент получает скомпилированную программу и JSON-документ. Его работа разделена между рантаймом, который выполняет программу, и рендерером, который отрисовывает результат.
Программа написана моделью, поэтому она не выполняется прямо на странице ChatGPT. Вместо этого ChatGPT загружает скрытый iframe (runner.html), изолированный с помощью allow-scripts и Content Security Policy default-src 'none', который запускает Web Worker.
- Lockdown (блокировка). Перед выполнением программы воркер удаляет из глобальной области видимости доступ к сети, таймеры, обмен сообщениями и динамическое выполнение кода, а оставшиеся глобальные объекты замораживает.
- Выполнение. Затем программа выполняется через new Function. Объекты рантайма (DIL, __dil, GenUI) и составные компоненты из каталога передаются в неё как параметры.
- Watchdog (сторожевой таймер). Если программа не отвечает в течение заданного времени, она помещается в карантин, а воркер перезапускается.
Рантайм представляет собой небольшой reconciler в стиле React. Он рендерит компонент и хранит состояния хуков в слотах с ключами. Затем сравнивает полученное дерево с предыдущим и кодирует различия в виде списка операций. Сам он ничего не отрисовывает.
Следующий иллюстративный пример показывает операции первого рендера — по одной строке на узел. Записи с перечислением имён свойств каждого элемента опущены:
1CREATE #1 title SET size = "lg" PLACE under root at 02CREATE #2 text "Оценка стоимости тарифа Team" PLACE under #1 at 03CREATE #3 text PLACE under root at 14CREATE #4 text "Перетащите ползунок, чтобы увидеть " PLACE under #3 at 05CREATE #5 bold PLACE under #3 at 16CREATE #6 text "месячную стоимость" PLACE under #5 at 07CREATE #7 text " для вашей команды." PLACE under #3 at 28CREATE #8 box SET border = true, padding = 3, gap = 2 PLACE under root at 29CREATE #9 slider SET min = 1, max = 50, value = 8, onChange = fn#1 PLACE under #8 at 010CREATE #10 title SET size = "xl" PLACE under #8 at 111CREATE #11 text "$" PLACE under #10 at 012CREATE #12 text "232" PLACE under #10 at 113CREATE #13 text "/мес" PLACE under #10 at 2
Функции никогда не покидают воркер: обработчик ползунка передаётся только в виде идентификатора (fn#1). При передаче по сети операции кодируются как бинарная последовательность целых чисел, а строки хранятся в отдельной таблице.
Рендеринг
Страница ChatGPT применяет операции к собственному дереву компонентов. Каждый CREATE создаёт экземпляр нативного компонента из дизайн-системы ChatGPT, а страница анимирует изменения по мере их поступления. Страница принимает операции только для известных типов компонентов, поэтому вывод модели не может добавить произвольную разметку или стили. Исключения — сырые CSS-значения, которые принимают некоторые свойства (см. раздел «Дизайн-система и каталог»), и приложения AppBlock, работающие в iframe (см. раздел «Аварийный люк»).
Взаимодействие работает в обратном направлении. Когда пользователь перетаскивает ползунок на отметку 9, страница отправляет воркеру идентификатор обработчика и аргументы. Воркер вызывает setSeats(9), заново рендерит интерфейс и возвращает операции обновления. Никаких обращений к модели при этом не происходит.
Дизайн-система и каталог
Каталог определяет, что именно может запросить модель. Он нужен потому, что модель не собирает интерфейс из базовых правил вёрстки и стилей. Она выбирает из компонентов, которые ChatGPT уже умеет отрисовывать, и стилизует их с помощью дизайн-токенов вроде padding={3}. Для некоторых свойств допускаются сырые CSS-значения (например, ширина в пикселях или цвета в HEX), но приоритет отдаётся дизайн-токенам. В результате:
- Сгенерированные интерфейсы выглядят органично на любой платформе, в едином стиле ChatGPT.
- У компилятора есть схема для проверки вывода. Свойство, которого нет у компонента, или литерал неверного типа удаляется при компиляции и фиксируется в диагностике.
В одном из перехваченных нами ответов компилятор удалил два свойства: fill у иконки (диагностика unknown_prop) и gap="1" у блока box (диагностика invalid_literal).
Каталог состоит из трёх частей:
- Нативные компоненты. Около 70 компонентов определены в реестре компонентов в клиентском коде ChatGPT; 39 из них встретились в перехваченных нами ответах.
- Дизайн-токены для отступов, скруглений, цветов и размеров.
- Составные компоненты, написанные OpenAI на DIL и передаваемые в sandbox в готовом виде, например компоненты image и product. В наших логах модель использовала эти компоненты, но ни разу не определяла собственные.
Стриминг
Стриминг текста устроен просто: каждый новый токен добавляется к тому, что уже отображается на экране. Со стримингом интерфейса всё сложнее по трём причинам:
- Вывод обычно ещё нельзя запустить. В большинстве моментов это неполная программа с незакрытым тегом или выражением, и её невозможно выполнить «как есть».
- Интерфейс должен работать, пока он формируется. Компоненты, с которыми пользователь уже взаимодействовал, должны сохранять своё состояние.
- Часть контента приходит отдельно. Данные вроде изображений поступают с сервера, а не из текста.
Стриминг на стороне сервера
Обычный поток добавляемых токенов с этим не справится. Поэтому ChatGPT стримит патчи для структурированного сообщения, в котором сырой текст, скомпилированная программа и её данные хранятся рядом.
Ответ поступает в браузер через поток server-sent events (POST /backend-api/f/conversation). Каждое событие — это обновление формируемого сообщения в стиле JSON-Patch. Обычно одно событие одновременно обновляет сырой текст DIL и его скомпилированную версию. Вот одно из сокращённых обновлений перехваченного ответа:
1{"o": "patch", "v": [2 {"p": "/message/content/parts/0", "o": "append", "v": " Воскресное жаркое из баранины с друзьями — щедрая еда, …"},3 {"p": "/message/metadata/model_dil_v2/code", "o": "replace", "v": "DIL.render(__dil.jsx(()=>{…"},4 {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"0": "Вот план идеального воскресного жаркого из баранины с друзьями — …"}},5 {"p": "/message/metadata/model_dil_v2/constants", "o": "append", "v": {"1": "Поскольку ты"}},6 {"p": "/message/metadata/model_dil_v2/fallbackMarkdown", "o": "append", "v": " Воскресное жаркое из баранины с друзьями — …"}7]}
Сервер не компилирует код инкрементально. Каждые несколько сотен миллисекунд — скорее всего, с каждым новым чанком вывода модели — он перекомпилирует всё, что модель успела сгенерировать, и отправляет результат. Компиляция начинается с первого токена, ещё до появления любого тега.
Компилятору приходится решать следующие задачи:
- Компиляция недописанного ответа
- Обновление текста
- Обновление UI
Таймлайн выглядит примерно так:

GIF
Стриминг на стороне клиента
Страница передаёт каждое новое обновление в изолированный воркер. Воркер выполняет его, заново рендерит интерфейс с сохранением существующего состояния и отправляет странице операции обновления. Состояние сохраняет значения между перекомпиляциями благодаря ключам, добавленным на этапе компиляции. Если новая программа не выполняется или не рендерится, воркер оставляет последнюю рабочую версию.
Затем страница анимирует каждое изменение:
- текст плавно появляется за 0,7 с;
- новые строки и элементы сетки выезжают за 0,42 с;
- графики отрисовываются за 1,8 с;
- высота контейнеров меняется плавно, а не скачком.
Аварийный люк: AppBlock — приложение в iframe

Встроенное приложение, сгенерированное ChatGPT
Некоторые запросы требуют того, на что нативные компоненты не рассчитаны, — например, драм-машину, синтезирующую звук через Web Audio. Для таких случаев модель может создать AppBlock: полноценное веб-приложение на HTML, CSS и JavaScript, встроенное прямо в ответ. Вот сокращённое начало одного из них:
1<AppBlock title="Drum Lab" icon="app-chatgpt" variant="inline" app_block_id="drum-lab-01">2<div id="dl" class="w-full min-w-0 space-y-4 text-base">3 <style>4 #dl{color:var(--viz-text)}#dl button{touch-action:manipulation}#dl .panel{background:var(--viz-panel);border:1px solid var(--viz-border);border-radius:15px}…5 </style>6 …7 <button id="dl-play" class="btn" style="background:var(--viz-text);color:var(--viz-card);min-width:100px">▶ Play</button>8 …9</div>10<script>11(function(){12const root=document.getElementById('dl');if(root.dataset.init)return;root.dataset.init="yes";13…14function audioInit(){if(!audio){const C=window.AudioContext||window.webkitAudioContext; if(!C)return false;audio=new C();…15…16})();17</script>18</AppBlock>
AppBlock рендерится иначе, чем компоненты Intelligent UI.
Как всё работает вместе
Вы вводите промпт. Модель начинает писать интерфейс, сервер превращает его в то, что способен выполнить ChatGPT, а страница собирает всё по частям по мере стриминга ответа. Как только интерфейс готов, перемещение ползунка или установка галочки обновляет его локально, без повторного обращения к модели.
Специализированный язык для модели, этап серверной компиляции, нативные рендереры и продуманная дизайн-система складываются в единую картину. Каждая часть решает критически важную задачу, а вместе они обеспечивают миллиарды пользователей по всему миру интерфейсами нового поколения, созданными специально для ИИ. Какое время мы живём!

Скриншоты Intelligent UI в ChatGPT
Методология
Все наблюдения получены из наших собственных аккаунтов ChatGPT, из трафика, генерируемого веб-приложением ChatGPT, и из JavaScript-кода, который chatgpt.com предоставляет публично. Исследование проводилось в октябре 2026 года с использованием GPT-6 и GPT-6 Thinking.
Анализ выполнен с помощью Codex и Claude. Текст написан с помощью Codex, визуализации подготовлены в Claude.
(Более подробная версия доступна по адресу https://www.openui.com/blog/how-chatgpt-intelligent-ui-works





