Потоковый ответ
Server-sent events потокового ответа, чанк с usage, ошибки в потоке, тайм-ауты и стоимость прерванного потока.
С "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 минут.
Отмена
Закройте соединение, чтобы остановить генерацию. Запрос оплачивается за то, что было сгенерировано к этому моменту, — по отчету провайдера об использовании, а если его нет, по оценке.