Как мы стримили ответы AI CareerBot и защищали GigaChat от перегруза
В AI CareerBot сделали streaming-ответы через SSE/WebSocket: токены появляются по одному, а GigaChat API не получает весь вирусный трафик сразу.

Тысячи одновременных пользователей — это момент, когда красивый эффект «ответ печатается по токенам» перестаёт быть задачей фронтенда и становится задачей защиты внешнего AI API.
В AI CareerBot мы сделали streaming-ответы: модель генерирует ответ, пользователь видит его не целиком в конце, а постепенно, как в ChatGPT. Для транспорта использовали SSE/WebSocket. На спокойной нагрузке всё выглядело ожидаемо: запрос ушёл, токены приходят, интерфейс дорисовывает текст.
А потом случился вирусный запуск.
Почему поток токенов оказался не главной проблемой
Сам streaming технически довольно прямолинейный: держим соединение открытым, прокидываем чанки ответа, на клиенте добавляем токены в текущий текст. Тут важны аккуратные детали: закрытие соединения, обработка обрыва, состояние «генерация идёт», финальный сигнал, чтобы интерфейс понял, что ответ закончен.
Но слабое место было не в анимации печати. Слабое место — GigaChat API.
Если каждый пользователь напрямую запускает генерацию, при резком росте трафика внешний API получает всплеск запросов. Для пользователя это выглядит как «бот думает», для нас — как риск получить ошибки, таймауты и цепную реакцию повторных запросов.
Одна живая деталь: сначала хотелось считать streaming почти UX-задачей. Обсуждали, какой транспорт удобнее — SSE или WebSocket, как красивее отдавать токены. Но после первых признаков перегруза разговор быстро сместился в сторону очереди: кто имеет право сейчас идти в GigaChat, кто ждёт, а кому честно показываем деградацию.
Что поставили между пользователем и GigaChat
Схема получилась с тремя защитными слоями:
- очередь запросов к AI, чтобы не выпускать весь входящий поток наружу одновременно;
- rate limiting, чтобы ограничивать частоту обращений и не разгонять перегруз;
- graceful degradation, чтобы при пике система не падала молча, а отвечала предсказуемо.
Очередь здесь нужна не для красоты архитектуры. Она превращает резкий всплеск в управляемый поток. Пользовательский запрос попадает внутрь, ждёт своей очереди, а дальше уже запускается генерация и открывается streaming-ответ.
Rate limiting режет ситуации, где один сегмент трафика начинает съедать общий лимит. Это особенно заметно на публичных запусках: часть пользователей обновляет страницу, повторяет запрос, открывает несколько вкладок. Если ничего не ограничивать, повторные действия сами усиливают проблему.
Graceful degradation — самый неприятный, но важный слой. Нужно заранее решить, что происходит, когда очередь уже слишком длинная или внешний API отвечает нестабильно. Не обещать пользователю мгновенный ответ, если система его сейчас не даст. Лучше показать состояние ожидания или отказать аккуратно, чем держать бесконечный «бот печатает…».
Где проходит граница между SSE и WebSocket
В этой задаче оба варианта имеют смысл.
SSE хорошо ложится на сценарий «сервер пишет, клиент читает». Для streaming-ответов AI это естественная модель: браузер держит соединение, сервер отправляет события с токенами. Меньше двусторонней логики, проще рассуждать о потоке.
WebSocket удобен, если кроме токенов нужен активный двусторонний канал: отмена генерации, дополнительные команды, более сложное состояние сессии. Но вместе с этим растёт количество мест, где нужно следить за соединениями, реконнектами и очисткой ресурсов.
Важный вывод для нас был не в выборе одного транспорта навсегда. Важнее, что сам транспорт не защищает AI API. Можно идеально стримить токены и всё равно положить внешний сервис неконтролируемым количеством генераций.
О чём хочется спросить коллег
В таких задачах есть несколько решений, которые приходится принимать почти вслепую, пока не пришла реальная нагрузка:
- какой размер очереди считать нормальным;
- когда начинать деградацию;
- что показывать пользователю до старта генерации;
- как не провоцировать повторные запросы;
- где хранить состояние streaming-сессии при обрыве соединения.
Мы в AI CareerBot закрыли базовый сценарий через очередь, rate limiting и graceful degradation вокруг GigaChat API. Эффект «печатания» остался, но генерации перестали лететь наружу бесконтрольно.
Если вы делали streaming AI-ответов на высокой нагрузке, как вы выбирали пороги для очереди и rate limiting: фиксированные значения, динамическая настройка по ошибкам внешнего API или отдельная модель backpressure?