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] — конец потока.
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-статусом ошибки:
{
"error": {
"code": 400,
"message": "Invalid model specified"
}
}- 400 — неверные параметры
- 401 — ключ
- 402 — не хватает баланса
- 429 — лимит запросов
- 502 — провайдер не ответил
Полный список — ошибки и отладка.
После начала генерации
HTTP уже 200, ошибка приходит событием SSE с полем error и finish_reason: "error":
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)