Потоковые ответы
Server-Sent Events в стандартном формате chat-completion-chunk, таймауты и остановка при простое.
Передайте "stream": true в теле запроса chat completion, чтобы получать Server-Sent Events в стандартном формате chunks. Шлюз прозрачно переводит поток upstream: независимо от исходного формата или Anthropic envelopes event: …, клиент видит одинаковые chunks.
Запрос
-N (--no-buffer) отключает буферизацию curl, поэтому chunks появляются сразу после получения.
Формат ответа
Каждое событие — строка data: с JSON, после которой идёт пустая строка. Поток завершается data: [DONE].
Первый chunk с ролью
Шлюз всегда отправляет первый chunk с delta.role = "assistant" и пустым content, даже если upstream этого не делает. Некоторые строгие CLI-клиенты отбрасывают сообщения, если первый delta не задаёт роль, поэтому шлюз добавляет его до передачи upstream-данных.
Usage содержит последние полученные значения
Когда upstream сообщает usage, шлюз сохраняет последние значения из стандартного потокового поля usage либо Anthropic-событий stop / message_delta. В нормальном потоке usage может прийти в заключительном chunk, но клиент не должен считать, что каждый поток содержит окончательное и гарантированно полное значение. Если upstream завершается с ошибкой до первого usage-события, во внутреннем журнале для такого потока остаётся нулевой расход токенов.
Ошибки до и после открытия потока
Обработка зависит от того, стал ли HTTP-ответ потоком SSE:
- До начала streaming: недоступный маршрут или открытый upstream breaker возвращает обычную JSON-ошибку с HTTP 503 и
error.code = "upstream_unavailable". Ошибка транспорта до получения заголовков upstream возвращает тот же код с HTTP 502. - После начала HTTP 200 и SSE: изменить HTTP-статус уже нельзя. Ошибка чтения, разбора, буфера или idle timeout приводит к synthetic chunk с
finish_reason: "error", после которого отправляетсяdata: [DONE]. Во внутреннем журнале запрос получает статус 502 и последние увиденные значения usage.
Таймауты
Три уровня защищают от зависшего upstream или клиентского соединения:
| Таймер | По умолчанию | Поведение |
|---|---|---|
| Дедлайн ответа upstream | 240 с (UPSTREAM_TIMEOUT_MS) | Прерывает ожидание, если upstream не прислал заголовки ответа |
| Остановка при простое | 120 с (STREAM_IDLE_TIMEOUT_MS; в production: 90 с) | Прерывает открытый SSE-поток, если upstream не присылает chunk заданный интервал |
| Таймаут простоя сокета Bun | 255 с | Оставляет клиентский сокет открытым, пока шлюз пишет поток |
UPSTREAM_TIMEOUT_MS отсчитывает время до заголовков ответа upstream, а не время подключения. Именно от этого зависит, как подбирать значение: non-stream запрос не получает заголовков, пока модель не закончила генерацию, поэтому бюджет должен покрывать всю генерацию. Бюджет меньше времени генерации обрывает каждый медленный запрос и возвращает HTTP 504 с error.code = "upstream_timeout" и сообщением, называющим бюджет самого шлюза; шлюз не повторяет такой запрос и не учитывает его в circuit breaker. Допустимый диапазон — 60–240 с, ограничен таймаутом сокета выше. Non-stream запрос, которому нужно больше бюджета, обслужить нельзя в принципе — выход только streaming, о чём и сообщает текст 504.
Для streaming-запросов дедлайн снимается сразу после получения заголовков успешного ответа, и дальше за паузы между chunks отвечает STREAM_IDLE_TIMEOUT_MS — поэтому поток может законно идти минутами, а non-stream запрос к той же модели не может. Таймаут простоя срабатывает уже внутри потока: он отдаёт finish_reason: "error" и [DONE], а внутренний статус записывает как 502. Если нужно дольше 240 с — используйте streaming.
Backpressure и отмена
Если клиент отключается во время потока, шлюз отменяет чтение upstream и завершает внутреннюю запись запроса со статусом 499 и последними полученными значениями usage. Ошибочный ответ или synthetic error chunk отправить уже нельзя: клиентское соединение закрыто.
Примеры SDK
TypeScript
Python
Когда поток не нужен
Не используйте streaming, если полный ответ нужен до начала обработки, например для разбора JSON или вычисления одного embedding. Обычные запросы проходят полную проверку upstream-ответа (upstream_invalid_response обнаруживает некорректный JSON раньше) и не несут накладных расходов на разбор каждого chunk.