Потоковый режим OpenAI API (параметр stream) возвращает ответ по частям, и приложение показывает первые слова раньше, чем модель закончит генерацию. Пользователь видит движение и меньше ждёт, а разработчик получает три новые заботы: сборку текста из фрагментов, отмену и обработку ошибки посреди ответа. Включать ли поток, решают длина ответа и то, смотрит ли на него человек в реальном времени.

Зачем нужен поток

TL;DR

По документации OpenAI, поток включается параметром stream в запросе к Responses API и приходит событиями по протоколу server-sent events. Текст поступает приращениями, а завершение и ошибки сообщаются отдельными событиями.

Внутренний помощник отвечает сотрудникам на вопросы по регламентам, и ответ генерируется заметное время. Без потока человек смотрит на пустой экран и начинает перезагружать страницу, что порождает лишние запросы. С потоком первые слова появляются почти сразу, пользователь читает по мере генерации, и счётчик повторных обращений падает. Ради такого эффекта поток и подключают.

Без потока приложение ждёт весь ответ целиком, а длинный текст генерируется долго, и интерфейс выглядит зависшим. Поток оставляет генерацию такой же долгой, зато сокращает время до первого видимого слова, и от этого зависит ощущение скорости. Для фоновых задач вроде классификации писем поток бесполезен: результат нужен целиком.

Эта статья посвящена приложению, вопросы доступа остаются за рамками. Как получить ключ и настроить проект, мы писали в материале OpenAI API: как выдать ключ и управлять доступом, а вопросы размещения данных за рубежом разбирал текст про серверы OpenAI и закон о персональных данных.

Доступ к сервису вендора идёт напрямую, а российские банковские карты для оплаты там недоступны, поэтому вопросы оплаты решаются до разработки. Подробности по интерфейсу потока смотрите в руководстве OpenAI по потоковым ответам: список событий расширяется.

События потока

Поток состоит из типизированных событий. Приложение подписывается на нужные и игнорирует остальные, а текст собирает из приращений в порядке прихода.

СобытиеЧто означаетЧто делает интерфейс
Создание ответаЗапрос принят, генерация началасьПоказывает индикатор, фиксирует время старта
Приращение текстаПришёл очередной фрагмент ответаДописывает текст на экран и в буфер
Завершение ответаГенерация закончена штатноУбирает индикатор, сохраняет полный текст и данные об использовании
ОшибкаСбой на стороне сервиса или сетиОставляет полученный фрагмент, предлагает повторить или продолжить

Подумайте и о доступности интерфейса. Текст, который постоянно дописывается, мешает программам экранного чтения и сбивает пользователя с позиции. Для таких случаев делайте обновление областью с вежливым оповещением, которая сообщает о новом тексте после завершения абзаца, вместо озвучивания каждого слова. Такой подход полезен всем пользователям, включая людей с ограничениями.

Фрагменты приходят в порядке генерации, но их размер непредсказуем: иногда слово, иногда часть слова. Поэтому интерфейс обновляют с небольшой задержкой, группируя события, иначе страница дёргается и тормозит. Полный текст храните в буфере и сохраняйте только после события завершения.

Для структурированных ответов и вызова функций поток устроен иначе: частичные данные нельзя разбирать как готовый JSON. Разбирать такой ответ можно после завершения, а до этого показывайте пользователю статус. Подробности описаны в разделах документации про потоковые вызовы функций.

Отмена и обрыв

Пользователь передумал, закрыл вкладку или нажал кнопку остановки. Приложение должно прекратить генерацию, иначе вы платите за текст, оставшийся без читателя.

  • Кнопка остановки: клиент закрывает соединение с потоком, а сервер перестаёт слать события. Проверьте на практике, что расход прекращается.
  • Закрытие страницы: на сервере отслеживайте разрыв клиентского соединения и прекращайте запрос к модели.
  • Тайм-аут молчания: если событий нет дольше разумного срока, соединение считается оборванным, и запрос прекращают или повторяют.
  • Состояние после отмены: сохраните уже полученный текст с пометкой, что ответ прерван, чтобы пользователь мог продолжить.
  • Защита от двойного запуска: пока поток идёт, повторное нажатие блокируйте, иначе параллельные запросы удвоят расход.

Отдельная ловушка — шлюзы и балансировщики между приложением и пользователем: некоторые накапливают ответ и отдают его целиком, превращая поток в обычный запрос. Проверьте цепочку от сервера до браузера и отключите буферизацию для потоковых маршрутов.

Ошибка посреди ответа

Обычный запрос либо успешен, либо нет. Поток может оборваться на середине, и тогда пользователь уже прочитал половину ответа. Для такого случая нужна заранее продуманная реакция.

  1. Сохраняйте полученный фрагмент и отметку о прерывании: пользователь должен видеть, что ответ неполный.
  2. Определите тип сбоя по событию ошибки и по состоянию соединения: сбой сети, ограничение скорости, ошибка сервиса.
  3. Для сетевых сбоев предложите повтор с паузой, для ограничения скорости подождите и повторите, а для ошибок запроса исправляйте запрос, повторять его бесполезно.
  4. Склейка старого фрагмента с новым ответом без проверки опасна: повторная генерация выдаёт другой текст, и результат выйдет бессвязным.
  5. Записывайте каждый сбой в журнал с идентификатором запроса, временем и числом уже полученных фрагментов.

Отдельно решите вопрос проверки содержимого. В документации сказано, что модерация частичных ответов затруднена: оценка приходит после полного вывода. Если приложение показывает ответ публично, добавьте фильтры на своей стороне и подумайте, показывать ли текст до проверки.

● Discovery · 1 час · бесплатно

Где в вашем приложении нужен ответ по частям?

Прийти на Discovery →

Замер задержки

// два числа

Измеряйте время до первого видимого фрагмента и время полного ответа. Первое определяет ощущение скорости, второе влияет на стоимость и на занятость соединений.

Фиксируйте метки времени в приложении: отправка запроса, первое приращение, завершение. Средние значения скрывают проблемы, поэтому смотрите распределение: медиану и хвост, то есть худшие запросы. Пользователь запоминает самый медленный ответ.

Расход зависит от длины запроса и ответа, а режим доставки влияет на него косвенно: отмена экономит токены, а повторные запросы из-за зависшего экрана тратят их впустую. Следите за этим по журналу, а точные тарифы смотрите на странице цен вендора.

Ведите счётчики по маршрутам: число потоков в работе, доля прерванных, доля завершённых с ошибкой. Рост прерванных запросов сигнализирует о проблеме интерфейса или медленных ответах, рост ошибок — о сбоях на стороне сервиса или сети. Эти три числа в одном отчёте дают команде достаточно информации для разговора о качестве потока.

Сравнивайте режимы честно. Включение потока меняет восприятие, а общее время генерации остаётся тем же, и в отчёте следует показывать оба показателя отдельно. Если первое слово приходит слишком поздно, причина часто в длинном запросе, лишних инструкциях или нагрузке на сети, и приложению стоит измерить каждый участок пути отдельно.

Один короткий вопрос при тихой сети даёт слишком красивый результат, поэтому проверяйте типичные длинные запросы, мобильную сеть и несколько одновременных пользователей. Если поток служит частью продукта с интеграциями и контролем качества, загляните на страницу про чат-боты для бизнеса.

Частые вопросы

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