API

Стриминг

AITUNNEL отдаёт стрим от любой модели. Интерфейс обновляется по мере генерации, не дожидаясь полного ответа.

Передайте stream: true. Ответ приходит чанками в формате Server-Sent Events: строки data: {…}, в конце — data: [DONE].

Работает в /chat/completions, /responses и /messages. Голосовой ответ модели стримится в delta.audioаудио.

Как включить

Проще всего читать поток через OpenAI SDK. Сырой HTTP тоже подходит — парсите строки data:.

curl https://api.aitunnel.ru/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -N \
  -d '{
    "model": "gpt-5.4",
    "stream": true,
    "messages": [
      { "role": "user", "content": "Как построить самое высокое здание в мире?" }
    ]
  }'

В финальном чанке (часто с пустым choices) приходит usage — токены, cost_rub и balance. Подробнее о схеме — справочник API.

Разбор SSE вручную

Если SDK нет, читайте тело ответа построчно. Каждое событие — строка data:, затем JSON. data: [DONE] — конец потока.

JavaScript
const response = await fetch('https://api.aitunnel.ru/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: 'Bearer sk-aitunnel-xxx',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'gpt-5.4',
    stream: true,
    messages: [{ role: 'user', content: 'Как построить самое высокое здание в мире?' }],
  }),
});

if (!response.ok) {
  const error = await response.json();
  throw new Error(error.error?.message ?? response.statusText);
}

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

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);

    if (!line.startsWith('data: ')) continue;
    const data = line.slice(6);
    if (data === '[DONE]') return;

    const parsed = JSON.parse(data);
    if (parsed.error) {
      throw new Error(parsed.error.message);
    }
    if (parsed.usage) {
      console.log('Usage:', parsed.usage);
    }
    const content = parsed.choices?.[0]?.delta?.content;
    if (content) process.stdout.write(content);
  }
}

Рекомендуемые клиенты: OpenAI SDK и Vercel AI SDK. Они сами собирают чанки.

Ошибки в стриме

Поведение зависит от того, успели ли уйти токены.

До первого токена

Обычный JSON с HTTP-статусом ошибки:

JSON
{
  "error": {
    "code": 400,
    "message": "Invalid model specified"
  }
}
  • 400 — неверные параметры
  • 401 — ключ
  • 402 — не хватает баланса
  • 429 — лимит запросов
  • 502 — провайдер не ответил

Полный список — ошибки и отладка.

После начала генерации

HTTP уже 200, ошибка приходит событием SSE с полем error и finish_reason: "error":

SSE
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1234567890,"model":"gpt-5.4","error":{"code":500,"message":"Провайдер отключился"},"choices":[{"index":0,"delta":{"content":""},"finish_reason":"error"}]}

Проверяйте error в каждом чанке

После такого события поток заканчивается. Не полагайтесь только на HTTP-статус: он уже 200.

from openai import OpenAI, APIError

client = OpenAI(
    api_key="sk-aitunnel-xxx",
    base_url="https://api.aitunnel.ru/v1/",
)

try:
    stream = client.chat.completions.create(
        model="gpt-5.4",
        messages=[{"role": "user", "content": "Как построить самое высокое здание в мире?"}],
        stream=True,
    )
    for chunk in stream:
        if getattr(chunk, "error", None):
            print("Ошибка в потоке:", chunk.error)
            break
        delta = chunk.choices[0].delta.content if chunk.choices else None
        if delta:
            print(delta, end="", flush=True)
except APIError as e:
    print("Ошибка до потока:", e.message)