RussiaAPI

Техническое руководство

Частичный streaming-ответ AI API: UX и отмена

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

Опубликовано 24 сентября 2026 · 10 минут чтения · Ключевой запрос: OpenAI API обработка частичного streaming ответа

Сначала определите состояния, а не анимацию

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

Частичный текст особенно опасен в сценариях с заявкой, публикацией, оплатой, поддержкой или медицинскими, юридическими и финансовыми темами. Интерфейс должен маркировать его как черновой, пока ваш backend не завершил сборку и прикладную проверку. Не запускайте действие по первому фрагменту. Если продукт использует инструменты, поиск или запись в систему, предложенное моделью действие должно пройти отдельную серверную авторизацию и явное подтверждение пользователя.

Собирайте поток на сервере и ограничивайте вход

Клиенту не нужен ключ gateway и не нужен произвольный URL поставщика. Браузер отправляет запрос вашему защищённому endpoint, а backend получает tenant и роль из сессии, ограничивает размер текста, разрешённый model alias и длительность. Затем он создаёт контролируемый upstream-вызов и передаёт клиенту только нормализованные события. Так вы можете остановить работу при logout, отзыве прав или достижении локального лимита, не раскрывая Authorization в DevTools или мобильном приложении.

Не предполагайте, что каждый фрагмент является JSON целиком или что поле content приходит в каждом событии. Буферизуйте данные, ограничивайте общий объём и отдельно обрабатывайте пустой фрагмент, невалидный JSON, обрыв соединения и финальное событие. В обычный лог пишите внутренний request ID, код, длительность, размер и причину отмены. Не записывайте полный prompt, сырой поток, cookie, токен либо тело исключения без отдельной политики данных.

Отмена и повтор должны быть предсказуемыми

Кнопка отмены должна завершать ваш клиентский запрос, а backend — прекращать дальнейшую выдачу и освобождать локальные ресурсы. Возможно, удалённая операция уже успела начаться; не утверждайте обратное без проверяемого статуса по актуальному контракту. В UI покажите «Отменено» и предложите новый запуск только с явным действием пользователя. Не склеивайте остаток старого ответа с результатом следующего запроса: храните локальный stream ID и отбрасывайте события от закрытой операции.

Повторять запрос можно только после классификации причины сбоя. Для чата без side effect повтор может быть допустимым, но для публикации, записи или оплаты нужен локальный idempotency key и отдельное подтверждение. Перед rollout выполните синтетический тест: нормальный поток, отмена до первого фрагмента, отмена после нескольких фрагментов, timeout и невалидное событие. Зафиксируйте проверенную дату, маршрут и форму ответа; модели, лимиты и доступность не следует обещать в интерфейсе.

Контрольный список перед релизом

До изменения production сохраните версию адаптера, владельца решения, дату проверки и безопасный способ отключения. Прогоните положительный сценарий на синтетическом входе и отдельные отрицательные случаи: пустое поле, неверный tenant, недоступный alias, timeout и повтор того же запроса. Измеряйте только технические признаки, достаточные для поддержки, а не содержимое пользователя.

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

Server-side пример

Этот минимальный пример рассчитан на Node.js 18+ и защищённый server-side запуск. Он не содержит реального ключа и не утверждает наличие недокументированной функции; перед интеграцией подтвердите текущий маршрут, alias модели и схему payload.

export async function streamAnswer({ session, text, onChunk, fetchImpl = fetch }) {
  if (!session?.tenantId || typeof text !== 'string' || text.length > 6_000) throw new Error('invalid_input');
  if (!process.env.RUSSIAAPI_API_KEY || !process.env.RUSSIAAPI_TEXT_MODEL) throw new Error('server_config_missing');
  const controller = new AbortController();
  const response = await fetchImpl('https://russiaapi.com/v1/chat/completions', {
    method: 'POST', signal: controller.signal,
    headers: { authorization: `Bearer ${process.env.RUSSIAAPI_API_KEY}`, 'content-type': 'application/json' },
    body: JSON.stringify({ model: process.env.RUSSIAAPI_TEXT_MODEL, stream: true, messages: [{ role: 'user', content: text }] })
  });
  if (!response.ok || !response.body) return { ok: false, status: response.status, cancel: () => controller.abort() };
  const reader = response.body.getReader(); let bytes = 0; let output = '';
  for (;;) { const { value, done } = await reader.read(); if (done) break; bytes += value.byteLength; if (bytes > 1_000_000) throw new Error('stream_too_large'); const chunk = new TextDecoder().decode(value); output += chunk; onChunk(chunk); }
  return { ok: true, output, cancel: () => controller.abort() };
}
// Validate the current streaming contract; this example intentionally does not parse provider-specific events.

Проверьте синтаксис командой node --check, добавьте собственную аутентификацию, контролируемые лимиты и тесты отрицательных сценариев. Не добавляйте в журнал тело запроса, ответ целиком или авторизационные заголовки.

Проверьте сценарий в RussiaAPI

Создайте собственный тестовый ключ в консоли, сверьте текущий каталог моделей и выполните обезличенный server-side smoke test. Расширяйте нагрузку только после измеримой проверки.

Открыть консоль RussiaAPI · Документы · Каталог моделей

FAQ

Можно ли сразу показывать пользователю каждый фрагмент?

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

Означает ли stream: true одинаковые события у всех моделей?

Нет. Формат, поля и конечный сигнал зависят от фактического маршрута и текущего контракта. Проверяйте их синтетическим тестом, обрабатывайте неизвестное поле безопасно и не привязывайте UI к недокументированному фрагменту.

Нужно ли передавать ключ API в браузер для streaming?

Нет. Ключ остаётся на сервере. Ваш backend применяет аутентификацию, tenant-ограничения, лимиты и нормализацию ошибок, а клиент получает только разрешённые события своего сеанса.

Читайте также