Возможности
Стриминг
Поставь stream: true в любом запросе /v1/messages или /v1/chat/completions, и токены будут возвращаться по Server-Sent Events. Та же аутентификация, тот же выбор модели, та же арифметика кредитов.
Базовый стрим
Добавь stream: true и читай тело ответа как text/event-stream. Большинство HTTP-библиотек делает это без дополнительных зависимостей. В примере ниже curl с флагом -N отключает буферизацию, чтобы токены было видно в реальном времени.
curl -N https://caicaini.com/v1/messages \
-H "Authorization: Bearer cai_api_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "caicaini/auto",
"max_tokens": 200,
"stream": true,
"messages": [{"role":"user","content":"Count to 10 slowly."}]
}'События на /v1/messages
Эндпойнт messages выдаёт последовательность именованных SSE-событий. У каждого фрейма строка event: и строка data: с JSON. Фреймы разделяются пустой строкой.
| Событие | Смысл |
|---|---|
| message_start | Первый фрейм. Несёт id и количество входных токенов. |
| content_block_start | Начался новый контент-блок. Тип может быть text, tool_use или thinking. |
| content_block_delta | Кусок токенов для текущего блока. Форма delta соответствует типу блока. |
| content_block_stop | Текущий блок завершён. |
| message_delta | Несёт финальный stop_reason и авторитетный credits_consumed. |
| message_stop | Последний фрейм. Прекращай чтение. |
| ping | Keepalive примерно раз в 15 с. Игнорируй. |
| error | Ошибка посреди стрима. Соединение может ненадолго остаться открытым. Считай терминальным событием. |
Пример сырого вывода
event: message_start
data: {"type":"message_start","message":{"id":"msg_01H...","type":"message","role":"assistant","content":[],"model":"caicaini/auto","stop_reason":null,"usage":{"input_tokens":12,"output_tokens":0}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"One, "}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"two, three..."}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":12,"output_tokens":24,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"credits_consumed":18}}
event: message_stop
data: {"type":"message_stop"}События на /v1/chat/completions
Эндпойнт chat-completions стримит безымянные события: каждый фрейм — одна строка data: с объектом chat.completion.chunk, и поток заканчивается литералом data: [DONE].
data: {"id":"chatcmpl_01H...","object":"chat.completion.chunk","created":1746748800,"model":"caicaini/auto","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl_01H...","object":"chat.completion.chunk","created":1746748800,"model":"caicaini/auto","choices":[{"index":0,"delta":{"content":"One, two, "},"finish_reason":null}]}
data: {"id":"chatcmpl_01H...","object":"chat.completion.chunk","created":1746748800,"model":"caicaini/auto","choices":[{"index":0,"delta":{"content":"three..."},"finish_reason":null}]}
data: {"id":"chatcmpl_01H...","object":"chat.completion.chunk","created":1746748800,"model":"caicaini/auto","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":12,"completion_tokens":24,"total_tokens":36,"credits_consumed":18}}
data: [DONE]О чём не стоит забывать
- Всегда разбирай фреймы по пустой строке, а не по отдельным строкам
data:. Многострочные payload-ы в SSE валидны. credits_consumedза поворот становится финальным наmessage_delta(messages) или на последнем чанке перед[DONE](chat completions). Всё, что раньше — частичные значения.- Если стрим обрывается посреди работы, запрос всё равно стоит кредитов за уже сгенерированные токены. Если разрыв произошёл до того, как пришёл хоть какой-то вывод, мы вернём кредиты автоматически.
- Количество одновременных стримов на ключ ограничено тарифом. См. Лимиты запросов.