Подключите AITUNNEL к популярным инструментам разработки и автоматизации.

DeepSeek HarnessDeepSeek HarnessАгентный харнесс DeepSeek с рабочими пространствами и плагинами — модели через AITUNNEL
Claude CodeClaude CodeТерминальный агент Anthropic — правит код, запускает команды и git-операции через AITUNNEL
Codex CLICodex CLIOpen-source агент OpenAI в терминале — правки кода, ревью и автоматизация задач
CursorCursor IDEAI-редактор на VS Code с автодополнением Tab, агентом Composer и моделями через AITUNNEL
Qwen CodeCLI-агент для Qwen3-Coder — навигация по проекту, сборка фич и правки через AITUNNEL
OpenCodeOpenCodeОткрытый агент для терминала и IDE с поддержкой 75+ LLM-провайдеров
n8nn8nВизуальная автоматизация с AI-агентами, RAG и интеграциями — модели через AITUNNEL
ClineAI-агент для VS Code — правки файлов, команды в терминале и браузер с подтверждением шагов
OpenClawOpenClawAI-агент для Telegram, Discord и Slack — модели через один API-ключ AITUNNEL
Все инструменты

Стриминг

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

Передайте stream: true. Ответ приходит чанками в формате Server-Sent Events: строки data: {…}, в /chat/completions поток заканчивается строкой 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.6-sol",
    "stream": true,
    "messages": [
      { "role": "user", "content": "Как построить самое высокое здание в мире?" }
    ]
  }'

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

Три API — три формата потока

/chat/completions/responses/messages
Строка event:нет, только data:есть, совпадает с type в JSONесть, совпадает с type в JSON
Чем заканчиваетсяdata: [DONE]событие response.completed (или response.incomplete, error)событие message_stop (или error)
data: [DONE]всегдаможет прийти после последнего события, может не прийти — не ждите егоне приходит
Где usageв последнем чанкев response.usage события response.completed / response.incompleteв usage события message_delta
ID запросаid каждого чанка, chat-…response.id, resp-…message.id в message_start, msg-…

ID запроса в потоке тот же, что в заголовке X-Request-Id ответа.

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

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

Пока модель читает длинный контекст или думает, данных может не быть минутами. Чтобы прокси и сетевые фильтры не закрыли простаивающее соединение, раз в 10 секунд приходит SSE-комментарий : AITUNNEL PROCESSING — во всех трёх API. По спецификации SSE строки с : — комментарии: SDK их пропускают сами, а в ручном разборе пропускайте всё, что не начинается с data: (в /responses и /messages — ещё и event: ). Не передавайте такую строку в JSON.parse.

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.6-sol',
    stream: true,
    messages: [{ role: 'user', content: 'Как построить самое высокое здание в мире?' }],
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message} (${error.request_id})`);
}

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) {
      // Сбой после начала генерации: HTTP уже 200, причина — в событии.
      throw new Error(`${parsed.error.type}: ${parsed.error.message} (${parsed.error.request_id})`);
    }
    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. Они сами собирают чанки.

Остановка потока

Закройте соединение (AbortController, stream.close(), Ctrl+C в curl) — мы сразу отменяем генерацию у провайдера.

  • Списывается то, что провайдер успел обработать до остановки: вход запроса и уже сгенерированные токены. Остановленный поток не бесплатен.
  • Точную сумму мы запрашиваем у провайдера уже после обрыва, поэтому запрос появляется в истории расходов и в GET /aitunnel/requests/{id} не сразу — обычно в течение минуты.
  • Если провайдер так и не сообщил стоимость, списывается оценка по тому, что успело прийти в потоке.

Если денег хватает не на весь ответ

Когда баланса или бюджета ключа не хватает на полный max_tokens (или на максимум модели, если лимит не задан), запрос не отклоняется — вывод ограничивается тем, на что хватает денег. В ответе приходит заголовок X-AITunnel-Output-Capped с этим числом токенов. Подробнее — лимит расходов.

Если модель дошла до этого предела или деньги закончились по ходу потока (их потратили параллельные запросы), поток завершается как при обычном лимите токенов:

SSE
# Chat Completions
data: {"id":"chat-034pVqy5IBvKq1Ef5KcGE5","object":"chat.completion.chunk","created":1234567890,"model":"gpt-5.6-sol","choices":[{"index":0,"delta":{},"finish_reason":"length"}]}

data: [DONE]

# Responses
event: response.incomplete
data: {"type":"response.incomplete","response":{"id":"resp-034pVqy5IBvKq1Ef5KcGE5","object":"response","created_at":1234567890,"status":"incomplete","model":"gpt-5.6-sol","incomplete_details":{"reason":"max_output_tokens"},"error":null,"output":[],"usage":null}}

# Messages
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"max_tokens","stop_sequence":null},"usage":{"output_tokens":0}}

event: message_stop
data: {"type":"message_stop"}

То есть finish_reason: "length" в Chat Completions, response.incomplete с incomplete_details.reason: "max_output_tokens" в Responses и stop_reason: "max_tokens" в Messages. Когда поток остановили мы, а не провайдер, события с usage в нём нет: стоимость считается после, как при остановке потока.

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

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

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

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

JSON
{
  "error": {
    "code": 400,
    "type": "model_not_found",
    "message": "Указанная модель (gpt-5.6-sool) не найдена, либо сейчас недоступна. Список доступных моделей на нашем сайте https://aitunnel.ru/models",
    "request_id": "chat-034pVqy5IBvKq1Ef5KcGE5"
  }
}
  • 400 — неверные параметры
  • 401 — ключ
  • 402 — не хватает баланса
  • 429 — лимит запросов
  • 502 — провайдер не ответил

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

Сбой провайдера в первые секунды, до первого токена, мы обрабатываем сами: повторяем запрос или переключаемся на запасную модель. В поток такая ошибка не попадает.

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

HTTP уже 200, ошибка приходит событием SSE. В нём тот же объект error, что в обычной ошибке: code, type, message, request_id. После этого события поток заканчивается.

Chat Completions — последний чанк с finish_reason: "error" и полем error, за ним data: [DONE]:

SSE
data: {"id":"chat-034pVqy5IBvKq1Ef5KcGE5","object":"chat.completion.chunk","created":1234567890,"model":"gpt-5.6-sol","choices":[{"index":0,"delta":{},"finish_reason":"error"}],"error":{"code":502,"type":"provider_unavailable","message":"Соединение с провайдером модели прервалось. Повторите запрос.","request_id":"chat-034pVqy5IBvKq1Ef5KcGE5"}}

data: [DONE]

Responses — событие error. Поля code и message на верхнем уровне — как в событии error OpenAI, наш объект — в error. После него ничего не приходит, response.completed не будет:

SSE
event: error
data: {"type":"error","code":"provider_unavailable","message":"Соединение с провайдером модели прервалось. Повторите запрос.","param":null,"error":{"code":502,"type":"provider_unavailable","message":"Соединение с провайдером модели прервалось. Повторите запрос.","request_id":"resp-034pVqy5IBvKq1Ef5KcGE5"}}

Messages — событие error в формате Anthropic: error.type из словаря Anthropic, наш тип в error.aitunnel_type, request_id на верхнем уровне. message_stop после него не приходит:

SSE
event: error
data: {"type":"error","error":{"type":"overloaded_error","message":"Overloaded","code":503,"aitunnel_type":"provider_unavailable"},"request_id":"msg-034pVqy5IBvKq1Ef5KcGE5"}

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

from openai import OpenAI, APIError, APIStatusError

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

try:
    stream = client.chat.completions.create(
        model="gpt-5.6-sol",
        messages=[{"role": "user", "content": "Как построить самое высокое здание в мире?"}],
        stream=True,
    )
    for chunk in stream:
        delta = chunk.choices[0].delta.content if chunk.choices else None
        if delta:
            print(delta, end="", flush=True)
except APIStatusError as e:
    # До потока: обычный HTTP-ответ с ошибкой
    err = e.response.json().get("error") or {}
    print("Запрос отклонён:", e.status_code, err.get("type"), err.get("message"))
    print("ID запроса:", err.get("request_id"))
except APIError as e:
    # После начала генерации: SDK сам поднимает исключение на событии с error
    print("Поток оборвался:", e.message)
    print("Детали:", e.body)  # {"code", "type", "message", "request_id"}