Перейти к содержимому

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

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

Открыть .md

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

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."}]}'
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)
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);
}

События

: 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-телом, см. Ошибки. Когда поток уже начался, статус уже 200, поэтому сбой приходит событием:

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

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

Тайм-ауты

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

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

Отмена

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