Долгое время интеграция языковых моделей во фронтенд напоминала попытки собрать корабль в бутылке пинцетом. Пользователь отправляет запрос, сервер обращается к API OpenAI, получает поток текста и транслирует его на клиент. Если требуется интерактивный виджет — например, график акций, карточка товара или форма бронирования — мы заставляем модель генерировать структурированный JSON.
На клиенте процесс превращается в испытание. Мы перехватываем JSON в виде обрывков строк из Server-Sent Events. Пытаемся распарсить его на лету, применяя регулярные выражения для восстановления незакрытых скобок. Складываем получившийся объект в стейт и только потом рендерим React-компонент. Такой подход ненадежен. Одно неверное поле ломает весь интерфейс. Разработчики пишут сотни строк защитного кода просто для того, чтобы безопасно отрендерить кнопку вместо куска текста.
Generative UI меняет подход. Мы перестаем передавать сырые данные между сервером и клиентом. Вместо этого сервер стримит готовые UI-компоненты напрямую в браузер.
Анатомия потокового рендеринга
В основе Generative UI лежат React Server Components (RSC) и протокол React Flight. Используя Vercel AI SDK, мы передаем не HTML-строки, а сериализованное дерево React-компонентов.
Модель не пишет JSX-код. Она по-прежнему предсказывает токены. Но мы даем ей доступ к инструментам (tools). Когда модель решает, что для ответа на запрос «Покажи билеты до Берлина» нужен интерфейс, она прекращает генерировать текст и формирует вызов инструмента searchFlights с нужными аргументами.
Сервер перехватывает вызов, запрашивает данные из базы и возвращает React-компонент . Клиент получает компонент по сети в бинарном формате RSC, и React плавно встраивает его в виртуальный DOM. В окне чата вместо текста мгновенно появляется интерактивный интерфейс.
RouterAPI как отказоустойчивый движок
Генеративный UI предъявляет жесткие требования к LLM. Модель обязана точно поддерживать tool calling и отвечать с минимальной задержкой. Задержка до первого токена (TTFT) критична: пока модель вычисляет нужный инструмент, пользователь смотрит на пустой экран.
Эту задачу решает RouterAPI. Вместо жесткой привязки к одному провайдеру мы настраиваем шлюз. RouterAPI балансирует нагрузку и обеспечивает автоматический фолбэк. Если Claude 3.5 Sonnet возвращает ошибку 529 (Overloaded), RouterAPI прозрачно переключает запрос на GPT-4o. Интерфейс пользователя отрисуется при любых сбоях на стороне конкретного вендора.
Поскольку RouterAPI предоставляет OpenAI-совместимый эндпоинт, интеграция с Vercel AI SDK требует минимальной конфигурации.
import { createOpenAI } from '@ai-sdk/openai';
import { streamUI } from 'ai/rsc';
import { z } from 'zod';
import { FlightSkeleton, FlightList, SearchStatus } from '@/components/flights';
import { db } from '@/lib/db';
const routerApi = createOpenAI({
baseURL: 'https://routerapi.net/v1',
apiKey: process.env.ROUTERAPI_KEY,
});
export async function submitUserMessage(query: string) {
const result = await streamUI({
model: routerApi('anthropic/claude-3.5-sonnet'),
prompt: query,
text: ({ content }) => <p className="text-gray-800">{content}</p>,
tools: {
searchFlights: {
description: 'Искать авиабилеты',
parameters: z.object({
destination: z.string,
date: z.string,
}),
generate: async function* ({ destination, date }) {
yield <FlightSkeleton destination={destination} />;
yield <SearchStatus message={`Ищем билеты в ${destination}..`} />;
const flights = await db.flights.search(destination, date);
return <FlightList flights={flights} />;
},
},
},
});
return result.value;
}
Использование генераторов (async function*) и yield дает важное преимущество. Мы транслируем промежуточные состояния компонента прямо в процессе выполнения серверной логики. Пользователь видит индикатор работы системы, а не зависший интерфейс в ожидании ответа от базы данных.
Управление состоянием и интерактивность
Возникает закономерный вопрос: серверные компоненты не имеют состояния. Разработчик не может использовать useState или назначить обработчик onClick внутри серверного . Как сделать сгенерированный интерфейс интерактивным?
Ответ лежит в правильной композиции. Серверный компонент выполняет роль контейнера. Он получает информацию из базы и передает ее через пропсы в клиентский компонент (Client Component).
'use client';
import { useState } from 'react';
import { bookFlight } from '@/app/actions';
export function FlightList({ flights }) {
const [bookingId, setBookingId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
return (
<div className="flex flex-col gap-4">
{flights.map(flight => (
<div key={flight.id} className="p-4 border rounded-xl">
<h3 className="font-semibold">{flight.airline}</h3>
<span className="font-bold">{flight.price} ₽</span>
<button
disabled={isLoading || bookingId === flight.id}
onClick={async => {
setIsLoading(true);
const id = await bookFlight(flight.id);
setBookingId(id);
setIsLoading(false);
}}
>
{bookingId === flight.id ? 'Забронировано' : 'Забронировать'}
</button>
</div>
))}
</div>
);
}
Модель сгенерировала вызов инструмента. Сервер отрендерил оболочку и передал данные. Клиент получил фрагмент интерфейса, который сразу обладает интерактивностью благодаря директиве 'use client'. Когда пользователь нажимает кнопку, срабатывает Server Action bookFlight, и сервер фиксирует бронь без перезагрузки страницы и дополнительных запросов к LLM.
Архитектурные компромиссы
Внедрение Generative UI требует пересмотра архитектуры и учета новых ограничений.
Во-первых, стоимость токенов. Передача контекста с описанием десятков инструментов раздувает системный промпт. RouterAPI помогает оптимизировать затраты, поддерживая кэширование промптов (Prompt Caching). Если системный промпт остается неизменным, тарификация снижается на порядок, а время обработки запроса сокращается.
Во-вторых, безопасность. Предоставляя модели инструменты, разработчик открывает вектор для атак типа Prompt Injection. Злоумышленник может попытаться заставить модель вызвать инструмент удаления аккаунта. Каждый инструмент обязан проверять авторизацию пользователя внутри функции generate, работая по тем же правилам, что и стандартный REST-контроллер.
В-третьих, сохранение контекста. Чтобы модель понимала, какой интерфейс она сгенерировала, Vercel AI SDK сериализует результат работы инструмента и добавляет его в историю сообщений. При следующем запросе модель анализирует предыдущие действия и корректно отвечает на уточняющие вопросы пользователя.
Новый подход к проектированию
Мы отказываемся от жестко заданных экранов. Фронтенд превращается в набор независимых виджетов, которые оркестрируются языковой моделью в реальном времени. Разработчик больше не пишет императивную логику переходов. Он описывает инструменты и компоненты, а модель самостоятельно выстраивает интерфейс.
Связка Vercel AI SDK и RouterAPI формирует надежный фундамент для этой архитектуры. Разработчик получает скорость потокового рендеринга React и отказоустойчивость специализированного шлюза для LLM. Интерфейс подстраивается под конкретную задачу пользователя в реальном времени, сокращая путь от намерения до результата.