Для AI-агентов: markdown этой страницы — /docs-content/ai/chat/streaming.md индекс документации — /llms.txt

Потоковая передача (Server-Sent Events)

При stream: true ответ модели приходит потоком событий по мере генерации токенов, а не одним куском в конце. Первый байт доходит до клиента за секунды независимо от полной длины ответа.

Заголовки ответа: Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive. Каждое событие — строка data: {JSON}\n\n, завершающее событие — data: [DONE]\n\n.

Формат событий

data: {"id":"chatcmpl-a9f6128818355f17","object":"chat.completion.chunk","model":"bitrix/bitrixgpt-5.5","choices":[{"index":0,"delta":{"role":"assistant","content":"CRM"}}]}

data: {"id":"chatcmpl-a9f6128818355f17","object":"chat.completion.chunk","model":"bitrix/bitrixgpt-5.5","choices":[{"index":0,"delta":{"content":" — это"}}]}

data: {"id":"chatcmpl-a9f6128818355f17","object":"chat.completion.chunk","model":"bitrix/bitrixgpt-5.5","choices":[{"index":0,"finish_reason":"stop","delta":{}}],"usage":{"prompt_tokens":10,"completion_tokens":15,"total_tokens":25}}

data: [DONE]

usage приходит в последнем событии перед [DONE]. Накапливайте delta.content из всех событий для получения полного текста ответа.

Пример обработки потока

javascript
const response = await fetch('https://vibecode.bitrix24.tech/v1/chat/completions', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'bitrix/bitrixgpt-5.5',
    messages: [{ role: 'user', content: 'Расскажи про CRM' }],
    stream: true,
  }),
})

const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break
  buffer += decoder.decode(value, { stream: true })
  const lines = buffer.split('\n')
  buffer = lines.pop() ?? ''

  for (const line of lines) {
    if (!line.startsWith('data: ')) continue
    const payload = line.slice(6)
    if (payload === '[DONE]') return
    const chunk = JSON.parse(payload)
    const delta = chunk.choices?.[0]?.delta?.content
    if (delta) process.stdout.write(delta)
  }
}

Выбор режима под нагрузкой

В синхронном режиме (stream: false) полный ответ модели собирается на сервере и уходит клиенту одним ответом — первый байт приходит вместе с последним. Для модели с длинной фазой рассуждения bitrix/bitrixgpt-5.5-thinking на длинном контексте и с развёрнутым ответом генерация занимает десятки секунд. Если клиентский таймаут короче полного времени генерации, соединение закрывается без единого полученного байта — например, cURL error 28: Operation timed out, 0 bytes received. На коротких запросах генерация укладывается в таймаут, и эффект не проявляется.

В потоковом режиме (stream: true) сервер отправляет статус 200 и заголовки сразу после начала обработки, а события идут по мере генерации токенов, включая фазу рассуждения. Для объёмных запросов и моделей с рассуждением используйте stream: true — клиентский таймаут по приёму первого байта тогда не срабатывает.

Ориентиры по клиентскому таймауту:

  • Потоковый режим (stream: true) — задавайте таймаут на приём первого события с запасом на обдумывание модели. Модель с рассуждением тратит несколько секунд до первого токена, значение от 30 секунд покрывает эту фазу с запасом.
  • Синхронный режим (stream: false) — таймаут по всему запросу должен покрывать полное время генерации, а не только сетевой обмен. Для объёмного запроса к модели с рассуждением это десятки секунд. Кроме того, при ошибке или таймауте провайдера синхронный запрос повторяется автоматически, и тогда финальный ответ или ошибка приходят через несколько минут. Поэтому значений 9-15 секунд недостаточно: задавайте таймаут по всему запросу с запасом на такие повторы — несколько минут — либо переходите на потоковый режим, где таймаут по приёму первого байта снимает проблему.

Известные особенности

Резервной модели в потоке нет. При сбое провайдера синхронный запрос повторяется с моделью по умолчанию, а в потоковом режиме клиент получает ошибку в последнем событии перед data: [DONE].

Ошибки приходят внутри потока. Статус 200 уже отправлен, когда платформа узнаёт о перегрузке или обрыве генерации, поэтому служебное событие вида data: { "error": { "code": "pool_exhausted", "retryAfter": 5 } } приходит перед data: [DONE]. Читайте поток до конца и проверяйте поле error.

Незавершённый структурированный ответ тоже приходит событием ошибки. Если в запросе был response_format, а полного JSON модель не выдала, перед data: [DONE] приходит data: { "error": { "code": "structured_output_truncated", "message": "...", "type": "invalid_request_error" } }. В этом событии только три поля — code, message и type. Полей finishReason, param и suggestedMaxTokens, которые несёт синхронный ответ 422, здесь нет. Текст message различается по причине завершения: генерация оборвана по finish_reason: "length" — предлагается повторить запрос с увеличенным max_tokens, поток закончился без причины завершения — ответ может быть неполным, модель завершилась сама с другой причиной — разбираемого JSON нет, рекомендуется модель без рассуждения. Подробнее — гарантированный JSON-ответ.

Зависший ответ обрывается по тайм-ауту простоя. Если модель отдала заголовки, но затем замолчала в середине ответа и не присылает новых данных дольше окна ожидания, платформа прерывает вызов и присылает data: { "error": { "code": "stream_idle_timeout", "type": "server_error", "retryable": true, "retryAfter": <секунды> } } перед data: [DONE]. Ошибка повторяемая — повторите запрос с учётом retryAfter. Непрерывный поток токенов (в том числе токенов рассуждений у «думающих» моделей) сбрасывает таймер простоя и под тайм-аут не попадает.

Смотрите также