# Потоковый ответ

Server-sent events потокового ответа, чанк с usage, ошибки в потоке, тайм-ауты и стоимость прерванного потока.

С `"stream": true` ответ приходит как `text/event-stream`: текст поступает частями (чанками) по мере генерации.

```bash
curl -N https://router.nodul.ru/api/v1/chat/completions \
  -H "Authorization: Bearer $NODUL_ROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "google/gemma-3-4b-it", "stream": true, "messages": [{"role": "user", "content": "Count to five."}]}'
```

```python
import os
from openai import OpenAI

client = OpenAI(base_url="https://router.nodul.ru/api/v1", api_key=os.environ["NODUL_ROUTER_API_KEY"])
stream = client.chat.completions.create(
    model="google/gemma-3-4b-it",
    messages=[{"role": "user", "content": "Count to five."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    if chunk.usage:
        print("\ncost:", chunk.usage.cost)
```

```js
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://router.nodul.ru/api/v1", apiKey: process.env.NODUL_ROUTER_API_KEY });
const stream = await client.chat.completions.create({
  model: "google/gemma-3-4b-it",
  messages: [{ role: "user", content: "Count to five." }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
  if (chunk.usage) console.log("\ncost:", chunk.usage.cost);
}
```

## События

```text
: keepalive

data: {"id":"gen-…","object":"chat.completion.chunk","model":"google/gemma-3-4b-it","choices":[{"index":0,"delta":{"role":"assistant","content":"One"}}]}

data: {"id":"gen-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":", two"},"finish_reason":null}]}

data: {"id":"gen-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"gen-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":9,"total_tokens":21,"cost":0.0000081}}

data: [DONE]
```

- Строки, начинающиеся с `:`, — комментарии, которые поддерживают соединение. SSE-клиенты их игнорируют; самописный парсер должен их пропускать.
- В каждом чанке есть `id` генерации; он же приходит в заголовке ответа `X-Generation-Id`.
- `delta.content` содержит текст, `delta.tool_calls` — вызовы инструментов по частям, а модели с рассуждениями могут присылать `delta.reasoning` или `delta.reasoning_content`.
- Последний чанк перед `data: [DONE]` содержит `usage` с `cost` в PnP. `stream_options` не нужен: роутер всегда запрашивает usage сам.

## Ошибки в потоке

Ошибки до первого чанка приходят как обычная HTTP-ошибка с JSON-телом, см. [Ошибки](/docs/ru/errors). Когда поток уже начался, статус уже `200`, поэтому сбой приходит событием:

```text
data: {"error":{"code":504,"message":"the upstream stream timed out (idle)"},"choices":[{"index":0,"delta":{},"finish_reason":"error"}]}
```

После первого чанка роутер никогда не переключается на резервную модель: начатый ответ не генерируется заново в другом месте. Если соединение закрылось без `[DONE]`, считайте ответ неполным.

## Тайм-ауты

| Лимит                              | Значение   |
| ---------------------------------- | ---------- |
| Самый долгий поток                 | 15 минут   |
| Самая долгая пауза между событиями | 120 секунд |
| Ожидание начала ответа провайдера  | 120 секунд |

Используйте потоковый ответ для длинных ответов и моделей с рассуждениями: запрос без потока тоже должен начаться в течение 120 секунд и завершиться за 10 минут.

## Отмена

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