Skip to content
КОНЦЕПТЫ
3 мин. чтенияЧитать на английском

Generative UI в агентном чате

Пользователь пишет агенту «сделай мне постер». Тот спрашивает: «в каком стиле?». Ответ: «ну, современно, но не слишком, может, как тот фильм Вонг Карвая». Агент парсит это в реальный prompt для генерации — плохо. Пользователь перепечатывает. Итерация медленная, потому что каждый шаг — это free-text вход и free-text выход, а агент постоянно угадывает, что имел в виду пользователь. Idea-card с тремя конкретными превью закрыл бы тот же вопрос одним кликом.

Ниже — паттерн: позволить агенту испускать не просто текст, а типизированные UI-компоненты, которые рендерятся прямо в потоке. Следующее «сообщение» пользователя становится кликом по одному из них, а не предложением.

Это generative UI: ситуативные, испускаемые агентом UI-элементы, перемежающиеся с репликами ассистента. Чат-агент может делать больше, чем испускать текст — он может испускать типизированные компоненты, которые рендерятся как карточки, опросники, previews, формы. Следующее сообщение пользователя становится кликом внутри одного из них, а не свободным текстом, и агент получает структурированное событие вместо абзаца, который он должен парсить.

Когда применять

  • В разговоре есть точки ветвления, которые лучше выражены кнопками/формами, чем «введите A, B или C».
  • У агента есть частичный результат, который стоит показать сейчас (карточка идеи, черновой промпт, ранняя картинка) — чтобы пользователь мог вмешаться до следующей стадии.
  • LLM в runtime не поддерживает нативный tool / function calling, но структурные промежуточные состояния всё равно нужны.

Механизм

  1. Словарь компонентов. Закрытый набор типизированных UI-элементов: IdeaList, Questionnaire, PromptPreview, ArtworkCard, PublishForm, DoneCard. У каждого — JSON-форма и рендерер.
  2. JSON-эмулированный function calling. LLM испускает ограждённый JSON-блок (например, ```tool\n{"name":"show_ideas","args":{...}}\n```). Сервер парсит такие блоки из потока и превращает в UI-события. Нативный tool-use API не требуется.
  3. Streamed SSE. Ответ агента — Server-Sent-Events-поток смешанных text-delta и tool-call-объектов. Клиент дописывает текст в текущий bubble до tool-события, затем рендерит компонент и стартует новый bubble.
  4. Click-as-message. Каждый интерактивный элемент в отрендеренном компоненте отправляет структурированное сообщение в эндпоинт агента (например, {"action":"pick_idea","idea_id":"…"}). Сервер трактует это как следующий ход пользователя, но со известной схемой, так что LLM не парсит intent из свободного текста.
  5. Перенос состояния. Стор агента (по четырёхуровневой иерархии источников) хранит артефакты, произведённые предыдущими компонентами (выбранная идея, черновой промпт, URL сгенерированной картинки), чтобы последующие tool-вызовы получали их как структурированные аргументы, а не как обрывки истории чата.

Почему JSON-эмуляция, а не нативный tool-use

  • Работает с любой LLM, включая модели, проксируемые через third-party-хосты или развёрнутые локально, где нативный tool-use API не выставлен.
  • Легче дебажить — сырой поток человекочитаемый.
  • Тот же fenced-формат работает для стриминга частичного JSON: text-delta появляются внутри ограждения, парсер терпит незавершённые объекты, пока не придёт закрывающее ограждение.

Разобранный словарь компонентов

IdeaList → PromptQuestionnaire → PromptPreview → ArtworkCard → PublishForm → DoneCard. Агент комментирует поток на естественном языке и испускает один из этих компонентов в каждой точке решения. Пользователь выбирает идею → chosenIdea в сторе обновляется → рендерится questionnaire → инструмент craft_prompt стреляет с приложенным полным four-tier-контекстом.

Разговор читается как чат, но форма данных под ним — структурированная.

Pitfalls

  • Scroll-jank на hover-тултипах. Всё, что приякорено к chat-bubble, должно переизмеряться на скролле. Capture-phase scroll-listener'ы, обнуляющие hover-state на каждом wheel-tick, саморазрушаются под auto-scroll. Портально в document.body и трекать через rAF + getBoundingClientRect.
  • Утечка состояния компонента. Когда один и тот же тип компонента рендерится дважды за сессию (пользователь возвращается и выбирает другую идею), каждому экземпляру нужен стабильный key, привязанный к ID хода агента, а не только к типу компонента.
  • Fallback-текст. Каждое tool-событие нуждается в текстовом эквиваленте для screen-reader'ов и для replay'а потока, где UI не отрендерился.

Идёт в паре с

  • four-tier-prompt-source-hierarchy — что агент переносит между ходами, управляемыми компонентами.
  • template-dispatch-prompts — серверная сторона: какой шаблон промпта использовать для каждого click-message.
  • two-stage-architect-pattern — generative UI — естественная поверхность для шага «Креативного директора» (карточки / опции) перед шагом «Технического архитектора» (коммит).