Почему прерывается потоковый вывод AI API? Полное руководство по отладке SSE (2026)
Практическая диагностика потокового ответа AI API с curl, Node.js и Python: тайм-аут первого байта, буферизация прокси, разрыв соединения и безопасные повторы.

Многие AI API при включённом параметре stream=true возвращают ответ частями через SSE (Server-Sent Events). Если интерфейс бесконечно показывает загрузку, приходит только половина фразы или curl видит данные, а приложение нет, причина обычно в соединении, прокси или обработчике потока.
1. Определите слой, где возникает ошибка
Разделите цепочку на клиентский код, обратный прокси, API-прокси и upstream-модель. Сначала проверьте запрос напрямую из терминала:
curl -N --http1.1 https://адрес-провайдера/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"ID-модели","messages":[{"role":"user","content":"Объясни SSE в трёх предложениях"}],"stream":true}'
Параметр -N отключает буферизацию curl, а --http1.1 помогает исключить проблемы совместимости HTTP/2. Нормальный ответ состоит из событий data:, а в конце обычно приходит data: [DONE].
2. Тайм-аут первого байта и разрыв потока
Тайм-аут первого байта означает, что после отправки запроса не пришло ни одного фрагмента. Причиной могут быть очередь модели, недостаточный баланс или сбой маршрута. Такой запрос допустимо повторить с экспоненциальной задержкой 1, 2 и 4 секунды, не более трёх раз.
Если соединение оборвалось после получения части текста, не повторяйте весь запрос автоматически: пользователь может получить дубликат, а баланс будет списан повторно. Сохраните уже полученный текст и предложите продолжение либо новую генерацию.
3. Чтение SSE в Node.js
Используйте ReadableStream и сохраняйте незавершённый буфер. Сетевой фрагмент не обязан заканчиваться на границе JSON или строки, поэтому нельзя выполнять JSON.parse над каждым результатом read().
const response = await fetch(`${baseUrl}/v1/chat/completions`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model, messages, stream: true }),
signal: AbortSignal.timeout(60000),
})
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`)
const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const events = buffer.split('\n\n')
buffer = events.pop() || ''
for (const event of events) {
const line = event.split('\n').find(item => item.startsWith('data:'))
if (!line) continue
const payload = line.slice(5).trim()
if (payload === '[DONE]') continue
const json = JSON.parse(payload)
process.stdout.write(json.choices?.[0]?.delta?.content || '')
}
}
4. Python 的超时设置
with requests.post(url, headers=headers, json=body, stream=True, timeout=(10, 120)) as response:
response.raise_for_status()
for line in response.iter_lines(decode_unicode=True):
if line and line.startswith('data:') and line[5:].strip() != '[DONE]':
print(line[5:].strip())
连接超时和读取超时应分开设置。模型已经开始输出时,只要连接仍在产生数据,就不应因为两个 token 之间间隔稍长而取消请求。
5. 代理配置与上线检查
常见问题包括 Nginx/CDN 缓冲响应、代理 idle timeout 太短,以及 gzip 或安全网关改写 text/event-stream。SSE 接口应保持该 Content-Type,并关闭不必要的缓存和压缩。
上线前至少验证:curl 能看到连续事件;首 token 延迟可接受;客户端能在断网后释放连接;429/502/504 只在尚未收到内容时重试;日志不记录 API Key 和完整提示词;重试不会造成重复扣费。