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
  }'