Потоковая передача
Как читать SSE-ответы и потоковые обновления
API NeuroAPI позволяет получать потоковые ответы от любой модели. Это полезно для создания чат-интерфейсов или других приложений, где пользовательский интерфейс должен обновляться по мере генерации ответа моделью.
Чтобы включить потоковую передачу, установите параметр stream в true в вашем запросе. Модель будет передавать ответ клиенту по частям (чанками), а не возвращать весь ответ сразу.
Пример обработки потока
Python
import requests
import json
response = requests.post(
url="https://neuroapi.host/v1/chat/completions",
headers={
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json"
},
json={
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Напиши длинную историю о космосе"}],
"stream": True
},
stream=True
)
buffer = ""
for chunk in response.iter_content(chunk_size=None, decode_unicode=True):
buffer += chunk
while '\n\n' in buffer:
line, buffer = buffer.split('\n\n', 1)
if line.startswith('data: '):
data = line[6:]
if data == '[DONE]':
print("\n\n--- Стрим завершён ---")
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:
# Игнорируем некорректный JSON, это может быть ping-сообщение
passTypeScript
interface ChatMessage {
role: string;
content: string;
}
interface ChatChoice {
delta: {
content?: string;
};
}
interface ChatCompletionResponse {
choices: ChatChoice[];
}
async function streamExample(): Promise<void> {
try {
const response = await fetch("https://neuroapi.host/v1/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: "Напиши длинную историю о космосе" }],
stream: true,
}),
});
if (!response.ok) {
throw new Error('HTTP error! status: ' + response.status);
}
if (!response.body) {
throw new Error("Response body is null");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
console.log("Начинаем получение ответа...\n");
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let boundary = buffer.indexOf("\n\n");
while (boundary !== -1) {
const chunk = buffer.substring(0, boundary);
buffer = buffer.substring(boundary + 2);
if (chunk.startsWith("data: ")) {
const data = chunk.substring(6);
if (data === "[DONE]") {
console.log("\n\n--- Стрим завершён ---");
return;
}
try {
const parsed: ChatCompletionResponse = JSON.parse(data);
const content = parsed.choices?.[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
} catch (e) {
// Игнорируем ошибки парсинга, это могут быть ping-сообщения
}
}
boundary = buffer.indexOf("\n\n");
}
}
} catch (error) {
console.error("Ошибка при выполнении запроса:", error);
throw error;
}
}Почему ответ может начинаться долго
У моделей семейств GPT-5.6 и Claude рассуждение (reasoning) скрыто на стороне провайдера: процесс «размышления» модели не передаётся в поток. Стриминг начинается только тогда, когда рассуждение закончилось и модель начала генерировать сам ответ. Поэтому время до первого токена может быть долгим — у тяжёлых моделей (например, Claude Opus или GPT-5.6 Sol) оно честно достигает нескольких минут, особенно на сложных задачах.
Это не зависание: соединение остаётся живым, а keep-alive сообщения : PING продолжают приходить, пока модель «думает». Мы автоматически ждём такие ответы с увеличенным бюджетом и сами переключаемся на резервный маршрут, только если провайдер действительно завис.
Рекомендация: не ставьте в своём клиенте короткий таймаут на первый токен для моделей с рассуждением. Ориентируйтесь на 120 секунд для лёгких моделей и до 600 секунд для тяжёлых (Claude Opus, GPT-5.6 Sol и других с глубоким рассуждением) — иначе вы разорвёте соединение раньше, чем модель успеет ответить.
Дополнительная информация
Keep-Alive сообщения: Для поддержания соединения API периодически отправляет keep-alive сообщения в формате
: PING. Эти сообщения не начинаются сdata:и должны игнорироваться вашим парсером событий (SSE). Наши примеры кода уже учитывают это.
Отмена потока
Потоковые запросы можно отменить, прервав соединение. Для поддерживаемых провайдеров это немедленно остановит обработку модели и биллинг.
Для этого в JavaScript используется AbortController, а в Python можно использовать более сложные механизмы, такие как threading.Event для сигнализации об отмене.
TypeScript
const controller = new AbortController();
async function cancelableStream() {
try {
const response = await fetch(
'https://neuroapi.host/v1/chat/completions',
{
method: 'POST',
headers: {
Authorization: 'Bearer <YOUR_API_KEY>',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Напиши очень длинную историю' }],
stream: true,
}),
signal: controller.signal, // Передаём AbortSignal
},
);
// ... обработка стрима
} catch (error) {
if (error.name === 'AbortError') {
console.log('Поток был отменён');
} else {
console.error('Произошла ошибка:', error);
}
}
}
// Запускаем стрим
cancelableStream();
// Отменяем стрим через 1 секунду
setTimeout(() => {
controller.abort();
}, 1000);Python
import requests
import threading
import time
# Event для сигнализации об отмене
cancel_event = threading.Event()
def stream_with_cancellation():
try:
with requests.post(
"https://neuroapi.host/v1/chat/completions",
headers={"Authorization": "Bearer <YOUR_API_KEY>"},
json={"model": "gpt-4o", "messages": [{"role": "user", "content": "Напиши очень длинную историю"}], "stream": True},
stream=True,
timeout=10 # Таймаут на установку соединения
) as response:
response.raise_for_status()
for chunk in response.iter_content(chunk_size=8192):
if cancel_event.is_set():
print("\n--- Поток отменён ---")
break
print(chunk.decode('utf-8'), end="")
except requests.exceptions.RequestException as e:
if cancel_event.is_set():
print("\n--- Запрос отменён успешно ---")
else:
print(f"Произошла ошибка: {e}")
# Запускаем стрим в отдельном потоке
stream_thread = threading.Thread(target=stream_with_cancellation)
stream_thread.start()
# Ждём 1 секунду и отменяем
time.sleep(1)
print("\n--- Отправка сигнала отмены ---")
cancel_event.set()
stream_thread.join()Важно: Отмена работает только для потоковых запросов к поддерживающим отмену провайдерам. Для не-потоковых запросов или неподдерживаемых провайдеров модель продолжит обработку, и вам будет выставлен счет за полный ответ.