Streaming ответов AI через SSE: практическое руководство
SSE показывает ответ по частям, но добавляет состояния, которых нет у обычного JSON: неполный chunk, ошибка после старта, отмена на полуслове. Статья — про чтение потока, которое не врёт.
Chunk не равен событию
Клиент читает response.body как поток байтов, декодирует UTF-8 и копит буфер. Один сетевой chunk может принести несколько событий или половину строки — события делятся пустой строкой, хвост сохраняется до следующего чтения.
Разбирайте data отдельно от служебных маркеров. Нормальное завершение, отмена и обрыв — три разных состояния интерфейса и журнала, а не «поток кончился».
- накапливать неполный хвост
- разделять data и служебные события
- закрывать reader при отмене
Данные отдельно, служебное отдельно
В потоке смешаны содержательные фрагменты и маркеры завершения. Пользователю рендерятся только данные, маркеры обрабатывает код: по ним определяется, ответ завершён, оборван или отменён.
Склейка «сырых» событий в финальный текст без разбора даёт мусор: половина chunk плюс маркер — это не ответ.
Ошибка до и после заголовков
До старта ответа сервер может вернуть обычный JSON с HTTP-ошибкой — это простой случай. После отправки заголовков статус уже не изменить, и ошибка приходит отдельным SSE-событием. Клиент, считающий любой текст успехом, врёт пользователю.
Сервер при этом отдельно финализирует usage и статус. Закрытый браузер после трёх фрагментов не означает, что upstream не доработал, — не делайте выводов по клиентской картине.
Маршрут и fallback
Streaming — свойство конкретного endpoint и модели, а не платформы в целом. Перед включением проверьте каталог и короткий тест; если поток не поддержан — используйте обычный режим, а не бесконечные повторы.
Интеграция обязана иметь непотоковый fallback. Пользователь не должен узнать об отсутствии streaming по зависшему экрану.
Время до первого события
Главная метрика стриминга — пауза до первого фрагмента, а не скорость всего ответа. Долгое молчание перед первым событием обычно означает проблему маршрута, а не «задумчивую модель».
Отмена пользователем — нормальное завершение: закройте reader, зафиксируйте частичный usage и не запускайте повтор. Пользователь уже ушёл, догонять его не надо.
Минимальный тест-план
Перед релизом прогоните шесть сценариев и для каждого запишите, что видит пользователь и какой итог ложится в usage. Непротестированный сценарий — это инцидент с неизвестной датой.
Особое внимание — ошибке после первого события и обрыву: именно там теряются деньги и рвётся UX.
- короткий и длинный ответы
- отмена и обрыв сети
- ошибка после первого события
Открыть эту статью в Markdown·Поделиться в Telegram
Следующий материалКак подключить OpenCode к OpenAI-compatible API