RussiaAPI

Интеграция API

SSE streaming в совместимом API: тайм-ауты и отмена

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

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

Граница сервиса. RussiaAPI — независимый сторонний API gateway, не официальный сервис OpenAI, Anthropic, Google или производителя модели. «OpenAI-совместимый» относится к части контракта API и не гарантирует одинаковые streaming-события, модели, стоимость или доступность. Используйте только собственный ключ RussiaAPI и соблюдайте правила сервисов и применимые требования.

Короткий ответ

Не помещайте API Key в браузер. Браузер подключается к вашему серверному маршруту, который проверяет сессию, лимиты и входные данные, а затем открывает поток к разрешённому endpoint собственным ключом. Сервер пересылает только ожидаемые данные, связывает отмену клиента с AbortController, ограничивает общий дедлайн и сохраняет результат лишь после корректного завершения.

Как устроен SSE-поток

Server-Sent Events — это длительный HTTP-ответ с последовательностью текстовых событий. В совместимых чат-интерфейсах вы можете встретить фрагменты с добавлением текста и отдельный сигнал завершения. Но конкретный формат нельзя угадывать по названию SDK: заранее проверьте, поддерживает ли выбранная модель поток, какие поля приходят, как выглядит ошибка и требуется ли специальный параметр запроса.

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

Почему серверный маршрут обязателен

Ключ в JavaScript страницы можно увидеть в инструментах разработчика, расширениях, дампе сети или ошибке. Его нельзя «спрятать» обфускацией. Серверный endpoint позволяет аутентифицировать пользователя, задать лимит длины prompt, запретить нежелательные параметры, записать безопасный request ID и выбрать модель из утверждённого каталога. Он также позволяет отделить внутренний ключ RussiaAPI от ключей пользователей: не просите и не принимайте ключи других поставщиков.

Такой маршрут не должен слепо пересылать все поля клиента. Сформируйте разрешённую схему: сообщения, допустимая температура, ограничение токенов и флаг streaming. Это защищает бюджет и делает обновления предсказуемыми. Модель и base URL задайте переменными окружения, а не значениями из формы. Перед запуском проверьте их в актуальном каталоге и документации.

Минимальный серверный пример

Ниже иллюстративный маршрут для Node.js 20+ с fetch. Он показывает управление отменой и пересылку тела. Формат событий внешнего API может отличаться; протестируйте его отдельно. Не используйте этот пример в браузере и не подставляйте реальный ключ в код.

import express from 'express';
const app = express();
app.use(express.json({ limit: '32kb' }));

app.post('/api/chat/stream', async (req, res) => {
  const controller = new AbortController();
  req.on('close', () => controller.abort());
  const upstream = await fetch('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_MODEL,
      stream: true, messages: sanitizeMessages(req.body.messages) })
  });
  if (!upstream.ok || !upstream.body) return res.status(upstream.status).end();
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  for await (const chunk of upstream.body) {
    if (!res.writableEnded) res.write(chunk);
  }
  res.end();
});

В рабочем коде добавьте try/catch для AbortError, лимит времени, проверку аутентификации, идентификатор операции и контролируемый формат ошибок. Если вы разбираете события на сервере, не отправляйте в браузер диагностические поля, внутренние URL и технические сообщения от поставщика без фильтрации. Для более долгих задач не держите HTTP-поток бесконечно: верните состояние и предложите получение финального результата отдельным запросом.

Тайм-аут, backpressure и отмена

У потока есть несколько часов: тайм-аут браузера, reverse proxy, вашего сервера и внешнего API. Выберите предсказуемый общий deadline для конкретного UX и покажите пользователю, что происходит при его превышении. Не переподключайтесь мгновенно бесконечным циклом. При временных ошибках действует ограниченный retry с экспоненциальной паузой и jitter; при 401, 403, невалидном JSON или неподдерживаемой модели сначала исправляют конфигурацию.

Backpressure возникает, когда клиент читает медленнее, чем upstream выдаёт данные. Не копите весь поток в памяти без лимита. Либо применяйте потоковый pipe с контролем записи, либо ограничивайте размер буфера и прекращайте запрос безопасно. Закрытие вкладки — важный сигнал: отмените передачу на сервере, сохраните статус cancelled_by_client или unknown согласно факту и не отмечайте частичный текст как завершённый.

Результат, повторы и логирование

Сохранять каждый фрагмент не всегда нужно. Для простого чата можно показать фрагменты только в интерфейсе, а финальный ответ сохранить после завершающего события и валидации. Для задач, где важен аудит, записывайте operation ID, время начала и окончания, длину, конечный статус и безопасный хеш входа. Полные prompts, заголовки Authorization, cookie и персональные данные не должны оказываться в обычных application-логах.

Перед новым запросом с тем же действием проверьте ID операции. Если предыдущая работа ещё идёт, подключите пользователя к её статусу или предложите дождаться; не запускайте скрытый дубль. Руководство о повторных запросах без дублей описывает уникальные индексы и неопределённые статусы после обрыва сети. При 429 используйте очереди и умеренный backoff из статьи об ограничении частоты.

Тестовый набор перед релизом

Не проверяйте streaming одним успешным prompt. Прогоните короткий и длинный ответ, русский текст, отмену вкладки, медленное соединение, ошибку модели, достижение лимита и повтор того же действия. Убедитесь, что ключ нигде не попал в DevTools, страницу, клиентский bundle или логи. Сравните время первого фрагмента, долю успешных завершений и число отмен с не-потоковым режимом.

Совместимый endpoint может возвращать иной набор моделей и параметров, чем знакомый клиент. Поэтому миграцию проводят постепенно: отдельная конфигурация, небольшой тестовый трафик, пороги p95 и ошибок, затем rollback при деградации. Подробный план есть в материале о миграции с OpenAI SDK. Это не способ получить доступ к закрытому сервису или обойти его территориальные, правовые либо договорные ограничения.

Чек-лист

Проверьте streaming безопасно

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

Открыть консоль RussiaAPI

FAQ

Можно ли вызывать streaming API прямо из браузера?

Не следует, если это требует API Key: секрет попадёт в клиентскую среду. Создайте серверный маршрут, который аутентифицирует пользователя, применяет лимиты и сам обращается к API собственным ключом.

Что делать, когда пользователь закрыл вкладку?

Свяжите закрытие соединения с AbortController на сервере, если endpoint поддерживает отмену передачи. Зафиксируйте отмену и не сохраняйте неполный результат как готовый; внешняя задача могла уже быть принята.

Означает ли OpenAI-совместимость одинаковый streaming?

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

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