Потоковый режим OpenAI API (параметр stream) возвращает ответ по частям, и приложение показывает первые слова раньше, чем модель закончит генерацию. Пользователь видит движение и меньше ждёт, а разработчик получает три новые заботы: сборку текста из фрагментов, отмену и обработку ошибки посреди ответа. Включать ли поток, решают длина ответа и то, смотрит ли на него человек в реальном времени.
Зачем нужен поток
По документации OpenAI, поток включается параметром stream в запросе к Responses API и приходит событиями по протоколу server-sent events. Текст поступает приращениями, а завершение и ошибки сообщаются отдельными событиями.
Внутренний помощник отвечает сотрудникам на вопросы по регламентам, и ответ генерируется заметное время. Без потока человек смотрит на пустой экран и начинает перезагружать страницу, что порождает лишние запросы. С потоком первые слова появляются почти сразу, пользователь читает по мере генерации, и счётчик повторных обращений падает. Ради такого эффекта поток и подключают.
Без потока приложение ждёт весь ответ целиком, а длинный текст генерируется долго, и интерфейс выглядит зависшим. Поток оставляет генерацию такой же долгой, зато сокращает время до первого видимого слова, и от этого зависит ощущение скорости. Для фоновых задач вроде классификации писем поток бесполезен: результат нужен целиком.
Эта статья посвящена приложению, вопросы доступа остаются за рамками. Как получить ключ и настроить проект, мы писали в материале OpenAI API: как выдать ключ и управлять доступом, а вопросы размещения данных за рубежом разбирал текст про серверы OpenAI и закон о персональных данных.
Доступ к сервису вендора идёт напрямую, а российские банковские карты для оплаты там недоступны, поэтому вопросы оплаты решаются до разработки. Подробности по интерфейсу потока смотрите в руководстве OpenAI по потоковым ответам: список событий расширяется.
События потока
Поток состоит из типизированных событий. Приложение подписывается на нужные и игнорирует остальные, а текст собирает из приращений в порядке прихода.
| Событие | Что означает | Что делает интерфейс |
|---|---|---|
| Создание ответа | Запрос принят, генерация началась | Показывает индикатор, фиксирует время старта |
| Приращение текста | Пришёл очередной фрагмент ответа | Дописывает текст на экран и в буфер |
| Завершение ответа | Генерация закончена штатно | Убирает индикатор, сохраняет полный текст и данные об использовании |
| Ошибка | Сбой на стороне сервиса или сети | Оставляет полученный фрагмент, предлагает повторить или продолжить |
Подумайте и о доступности интерфейса. Текст, который постоянно дописывается, мешает программам экранного чтения и сбивает пользователя с позиции. Для таких случаев делайте обновление областью с вежливым оповещением, которая сообщает о новом тексте после завершения абзаца, вместо озвучивания каждого слова. Такой подход полезен всем пользователям, включая людей с ограничениями.
Фрагменты приходят в порядке генерации, но их размер непредсказуем: иногда слово, иногда часть слова. Поэтому интерфейс обновляют с небольшой задержкой, группируя события, иначе страница дёргается и тормозит. Полный текст храните в буфере и сохраняйте только после события завершения.
Для структурированных ответов и вызова функций поток устроен иначе: частичные данные нельзя разбирать как готовый JSON. Разбирать такой ответ можно после завершения, а до этого показывайте пользователю статус. Подробности описаны в разделах документации про потоковые вызовы функций.
Отмена и обрыв
Пользователь передумал, закрыл вкладку или нажал кнопку остановки. Приложение должно прекратить генерацию, иначе вы платите за текст, оставшийся без читателя.
- Кнопка остановки: клиент закрывает соединение с потоком, а сервер перестаёт слать события. Проверьте на практике, что расход прекращается.
- Закрытие страницы: на сервере отслеживайте разрыв клиентского соединения и прекращайте запрос к модели.
- Тайм-аут молчания: если событий нет дольше разумного срока, соединение считается оборванным, и запрос прекращают или повторяют.
- Состояние после отмены: сохраните уже полученный текст с пометкой, что ответ прерван, чтобы пользователь мог продолжить.
- Защита от двойного запуска: пока поток идёт, повторное нажатие блокируйте, иначе параллельные запросы удвоят расход.
Отдельная ловушка — шлюзы и балансировщики между приложением и пользователем: некоторые накапливают ответ и отдают его целиком, превращая поток в обычный запрос. Проверьте цепочку от сервера до браузера и отключите буферизацию для потоковых маршрутов.
Ошибка посреди ответа
Обычный запрос либо успешен, либо нет. Поток может оборваться на середине, и тогда пользователь уже прочитал половину ответа. Для такого случая нужна заранее продуманная реакция.
- Сохраняйте полученный фрагмент и отметку о прерывании: пользователь должен видеть, что ответ неполный.
- Определите тип сбоя по событию ошибки и по состоянию соединения: сбой сети, ограничение скорости, ошибка сервиса.
- Для сетевых сбоев предложите повтор с паузой, для ограничения скорости подождите и повторите, а для ошибок запроса исправляйте запрос, повторять его бесполезно.
- Склейка старого фрагмента с новым ответом без проверки опасна: повторная генерация выдаёт другой текст, и результат выйдет бессвязным.
- Записывайте каждый сбой в журнал с идентификатором запроса, временем и числом уже полученных фрагментов.
Отдельно решите вопрос проверки содержимого. В документации сказано, что модерация частичных ответов затруднена: оценка приходит после полного вывода. Если приложение показывает ответ публично, добавьте фильтры на своей стороне и подумайте, показывать ли текст до проверки.
Где в вашем приложении нужен ответ по частям?
Замер задержки
Измеряйте время до первого видимого фрагмента и время полного ответа. Первое определяет ощущение скорости, второе влияет на стоимость и на занятость соединений.
Фиксируйте метки времени в приложении: отправка запроса, первое приращение, завершение. Средние значения скрывают проблемы, поэтому смотрите распределение: медиану и хвост, то есть худшие запросы. Пользователь запоминает самый медленный ответ.
Расход зависит от длины запроса и ответа, а режим доставки влияет на него косвенно: отмена экономит токены, а повторные запросы из-за зависшего экрана тратят их впустую. Следите за этим по журналу, а точные тарифы смотрите на странице цен вендора.
Ведите счётчики по маршрутам: число потоков в работе, доля прерванных, доля завершённых с ошибкой. Рост прерванных запросов сигнализирует о проблеме интерфейса или медленных ответах, рост ошибок — о сбоях на стороне сервиса или сети. Эти три числа в одном отчёте дают команде достаточно информации для разговора о качестве потока.
Сравнивайте режимы честно. Включение потока меняет восприятие, а общее время генерации остаётся тем же, и в отчёте следует показывать оба показателя отдельно. Если первое слово приходит слишком поздно, причина часто в длинном запросе, лишних инструкциях или нагрузке на сети, и приложению стоит измерить каждый участок пути отдельно.
Один короткий вопрос при тихой сети даёт слишком красивый результат, поэтому проверяйте типичные длинные запросы, мобильную сеть и несколько одновременных пользователей. Если поток служит частью продукта с интеграциями и контролем качества, загляните на страницу про чат-боты для бизнеса.