обычный запрос возвращает ответ целиком: пока модель не закончит генерацию, клиент ждёт. длинный ответ так может собираться десятки секунд. стриминг решает эту проблему — ответ передаётся по мере генерации, и первые слова появляются почти сразу.
как включить
в теле запроса указывается "stream": true. всё остальное — адрес, ключ, model и messages — не меняется. официальные библиотеки в этом режиме отдают итерируемый объект вместо готового ответа.
curl "https://api.airakurs.ru/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <КЛЮЧ>" \
-d '{
"model": "anthropic/claude-sonnet-4.5",
"messages": [
{"role": "user", "content": "Расскажи о Байкале."}
],
"stream": true
}'from openai import OpenAI
client = OpenAI(api_key="<КЛЮЧ>", base_url="https://api.airakurs.ru/v1")
stream = client.chat.completions.create(
model="anthropic/claude-sonnet-4.5",
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)формат потока
ответ приходит по протоколу server-sent events: последовательность строк, каждая из которых начинается с data: и содержит очередной фрагмент в json. поток закрывается строкой data: [DONE].
data: {"choices":[{"index":0,"delta":{"content":"Байкал"}}]}
data: {"choices":[{"index":0,"delta":{"content":" — самое"}}]}
data: [DONE]
главное отличие от обычного ответа: текст лежит не в message.content, а в delta.content, и в каждом фрагменте это лишь кусочек. полный ответ получается склеиванием всех фрагментов подряд.
признак завершения приходит там же, где и в обычном ответе: в последнем фрагменте с текстом заполняется finish_reason.
расход токенов
самым последним, уже после текста, приходит служебный фрагмент: массив choices в нём пустой, зато есть объект usage с итоговым расходом токенов.
data: {"choices":[],"usage":{"prompt_tokens":18,"completion_tokens":214,"total_tokens":232}}
по этим числам считается стоимость запроса. официальные библиотеки такой фрагмент обрабатывают сами, а вот самописный разбор потока лучше сразу писать с расчётом на него — иначе обращение к choices[0] на последнем фрагменте уронит код.
где применяется
стриминг работает там, где модель пишет ответ постепенно: /v1/chat/completions, /v1/responses и /v1/messages. у подсчёта токенов промежуточного результата не бывает, поэтому ответ приходит целиком.
что дальше
- генерация текста — из чего состоит запрос и ответ.
- ошибки — что приходит вместо потока, если запрос не принят.
частые вопросы
чем delta отличается от message?
в обычном ответе весь текст лежит в message.content. в потоке каждый фрагмент несёт только добавку к уже полученному тексту, и лежит она в delta.content. полный ответ собирается склеиванием фрагментов.
почему в последнем фрагменте пустой массив choices?
это служебный фрагмент с итоговым расходом токенов в поле usage. текста в нём нет, поэтому choices пустой. код, который разбирает поток самостоятельно, должен это учитывать.
стриминг работает с моделями любого вендора?
да. достаточно добавить stream в запрос — формат потока при этом одинаковый для всех текстовых моделей каталога.
всегда ли стоит включать стриминг?
нет. он нужен там, где ответ читает человек и важно не заставлять его ждать. для фоновых задач обычный ответ удобнее: он приходит одним объектом, который не нужно собирать по частям.