Streaming
API поддерживает потоковую передачу (SSE) для любых chat / completions / responses моделей. Это позволяет отображать ответ модели по мере генерации.
Базовое использование
Установите stream: true в теле запроса. Модель будет передавать ответ чанками через Server-Sent Events.
curl https://api.ru-openrouter.ru/v1/chat/completions \
-H "Authorization: Bearer sk_ваш_api_ключ" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o",
"messages": [
{"role": "user", "content": "Как построить самое высокое здание в мире?"}
],
"stream": true
}'
Пример: Python
import requests
import json
question = "Как построить самое высокое здание в мире?"
response = requests.post(
"https://api.ru-openrouter.ru/v1/chat/completions",
headers={
"Authorization": "Bearer sk_ваш_api_ключ",
"Content-Type": "application/json"
},
json={
"model": "openai/gpt-4o",
"messages": [{"role": "user", "content": question}],
"stream": True
},
stream=True
)
buffer = ""
for chunk in response.iter_content(chunk_size=1024, decode_unicode=True):
buffer += chunk
while True:
line_end = buffer.find('\n')
if line_end == -1:
break
line = buffer[:line_end].strip()
buffer = buffer[line_end + 1:]
# Пропускаем SSE-комментарии (строки, начинающиеся с ":")
if line.startswith(':'):
continue
if line.startswith('data: '):
data = line[6:]
if data == '[DONE]':
break
try:
data_obj = json.loads(data)
content = data_obj["choices"][0]["delta"].get("content")
if content:
print(content, end="", flush=True)
except json.JSONDecodeError:
pass
Пример: TypeScript (fetch)
const question = 'Как построить самое высокое здание в мире?';
const response = await fetch('https://api.ru-openrouter.ru/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: 'Bearer sk_ваш_api_ключ',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'openai/gpt-4o',
messages: [{ role: 'user', content: question }],
stream: true,
}),
});
const reader = response.body?.getReader();
if (!reader) throw new Error('Response body is not readable');
const decoder = new TextDecoder();
let buffer = '';
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
while (true) {
const lineEnd = buffer.indexOf('\n');
if (lineEnd === -1) break;
const line = buffer.slice(0, lineEnd).trim();
buffer = buffer.slice(lineEnd + 1);
// Пропускаем SSE-комментарии
if (line.startsWith(':')) continue;
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') break;
try {
const parsed = JSON.parse(data);
const content = parsed.choices[0].delta.content;
if (content) {
console.log(content);
}
} catch (e) {
// Игнорируем невалидный JSON
}
}
}
}
} finally {
reader.cancel();
}
Формат SSE-событий
Поток состоит из событий data: в формате Server-Sent Events. Каждый чанк содержит:
{
"id": "chatcmpl-abc123",
"object": "chat.completion.chunk",
"created": 1709012345,
"model": "openai/gpt-4o",
"choices": [
{
"index": 0,
"delta": {
"content": "Сгенерированный"
},
"finish_reason": null
}
]
}
Финальный чанк содержит usage с данными о стоимости и токенах:
{
"id": "chatcmpl-abc123",
"object": "chat.completion.chunk",
"created": 1709012345,
"model": "openai/gpt-4o",
"choices": [
{
"index": 0,
"delta": {},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 25,
"completion_tokens": 100,
"total_tokens": 125,
"cost": 0.0375
}
}
Примечание: Стоимость в финальном пакете указывается в рублях (RUB).
SSE-комментарии (keep-alive)
Провайдер периодически отправляет комментарии для предотвращения таймаутов соединения:
: RU-OPENROUTER PROCESSING
Такие строки можно безопасно игнорировать — они не являются JSON и начинаются с :.
Эндпоинты с поддержкой streaming
| Эндпоинт | Метод | Описание |
|---|---|---|
/v1/chat/completions |
POST | OpenAI Chat Completions |
/v1/completions |
POST | Legacy Completions |
/v1/responses |
POST | OpenAI Responses API |
Отмена стрима (клиентская)
При разрыве соединения клиентом API продолжает обработку для корректной тарификации. Стоимость рассчитывается по фактически полученным токенам, а не по запрошенным.
import requests
from threading import Event, Thread
def stream_with_cancellation(prompt: str, cancel_event: Event):
with requests.Session() as session:
response = session.post(
"https://api.ru-openrouter.ru/v1/chat/completions",
headers={"Authorization": "Bearer sk_ваш_api_ключ"},
json={
"model": "openai/gpt-4o",
"messages": [{"role": "user", "content": prompt}],
"stream": True
},
stream=True
)
try:
for line in response.iter_lines():
if cancel_event.is_set():
response.close()
return
if line:
print(line.decode(), end="", flush=True)
finally:
response.close()
# Пример использования:
cancel_event = Event()
stream_thread = Thread(target=lambda: stream_with_cancellation("Напиши историю", cancel_event))
stream_thread.start()
# Отмена стрима:
cancel_event.set()
Обработка ошибок
Ошибки до начала стрима
Если ошибка произошла до отправки первого чанка, API возвращает стандартный JSON-ответ с HTTP-статусом:
{
"error": {
"code": 400,
"message": "Model 'unknown/model' not found or inactive"
}
}
Возможные статусы:
| Код | Описание |
|---|---|
| 400 | Неверные параметры запроса |
| 401 | Неверный API-ключ |
| 402 | Недостаточно средств на балансе |
| 404 | Эндпоинт не найден |
| 413 | Тело запроса слишком большое (макс. 10 MB) |
| 415 | Неподдерживаемый Content-Type |
| 429 | Превышен лимит запросов (rate limit) |
| 502 | Ошибка провайдера |
| 503 | Сервис недоступен |
Ошибки во время стрима (mid-stream)
Если ошибка произошла после начала передачи данных, она отправляется как SSE-событие:
data: {"id":"cmpl-abc123","object":"chat.completion.chunk","created":1234567890,"model":"openai/gpt-4o","error":{"code":"server_error","message":"Provider disconnected unexpectedly"},"choices":[{"index":0,"delta":{"content":""},"finish_reason":"error"}]}
Особенности:
- HTTP-статус остаётся
200 OK(заголовки уже отправлены) - Присутствует
choicesсfinish_reason: "error" - После этого события поток завершается
Пример обработки ошибок (Python)
import requests
import json
response = requests.post(
"https://api.ru-openrouter.ru/v1/chat/completions",
headers={
"Authorization": "Bearer sk_ваш_api_ключ",
"Content-Type": "application/json"
},
json={
"model": "openai/gpt-4o",
"messages": [{"role": "user", "content": "Привет!"}],
"stream": True
},
stream=True
)
# Проверка ошибки ДО стрима
if response.status_code != 200:
error_data = response.json()
print(f"Ошибка: {error_data['error']['message']}")
exit()
# Обработка стрима с mid-stream ошибками
for line in response.iter_lines():
if line:
line_text = line.decode('utf-8')
if line_text.startswith('data: '):
data = line_text[6:]
if data == '[DONE]':
break
try:
parsed = json.loads(data)
# Проверка mid-stream ошибки
if 'error' in parsed:
print(f"Stream error: {parsed['error']['message']}")
break
content = parsed['choices'][0]['delta'].get('content')
if content:
print(content, end='', flush=True)
except json.JSONDecodeError:
pass
Streaming для Responses API
Эндпоинт /v1/responses также поддерживает streaming:
curl https://api.ru-openrouter.ru/v1/responses \
-H "Authorization: Bearer sk_ваш_api_ключ" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o",
"input": "Расскажи о себе",
"stream": true
}'