
## Потоковая передача (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-ответ](./json.md).

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

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

- [Создать чат-комплишен](./completions.md)
- [Лимиты запросов](./rate-limits.md)
- [Гарантированный JSON-ответ](./json.md)
- [AI Router](/docs/ai)
