Документация / Стриминг
Ответ по частям
stream: true — и токены приходят по мере генерации, кадрами server-sent events. Формат тот же, что у OpenAI; отличается только то, что шлюз кладёт в последний кадр.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://ai.globall.dev/v1",
api_key=os.environ["TOK_API_KEY"],
)
stream = client.chat.completions.create(
model="anthropic/claude-haiku-5.5",
messages=[{"role": "user", "content": "Считай до пяти"}],
max_tokens=64,
stream=True,
)
for chunk in stream:
if chunk.usage:
print("\n", chunk.usage.cost, "USD")
elif chunk.choices:
print(chunk.choices[0].delta.content or "", end="")
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://ai.globall.dev/v1",
apiKey: process.env.TOK_API_KEY,
});
const stream = await client.chat.completions.create({
model: "anthropic/claude-haiku-5.5",
messages: [{ role: "user", content: "Считай до пяти" }],
max_tokens: 64,
stream: true,
});
for await (const chunk of stream) {
if (chunk.usage) console.log("\n", chunk.usage.cost, "USD");
else process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
curl -N https://ai.globall.dev/v1/chat/completions \
-H "Authorization: Bearer $TOK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-5.5",
"messages": [{"role": "user", "content": "Считай до пяти"}],
"max_tokens": 64,
"stream": true
}'
Как устроен поток
- Каждый кадр — строка data: с JSON-объектом; кадры разделены пустой строкой.
- Дельты несут кусок текста в choices[0].delta.content. Поля usage в них нет вообще — не пустое, а отсутствующее.
- У состоявшегося ответа ровно один кадр перед концом несёт usage: prompt_tokens, completion_tokens, total_tokens и cost. Двух кадров с usage не бывает никогда, а у потока, который не состоялся, его не будет вовсе.
- Последняя строка — data: [DONE].
- После неё приходит трейлер X-Cost-Usd с той же суммой, что и usage.cost.
Пока модель молчит, шлюз шлёт строку-комментарий «: keepalive» — раз в 10 секунд, с момента отправки заголовков. Это не кадр данных, SDK его пропустит; нужен он затем, чтобы соединение не сочли мёртвым, пока модель думает.
Стоимость в долларах
usage.cost — это списанная с вас сумма в USD, шесть знаков после запятой, а не себестоимость у вендора. В кабинете те же деньги показаны в рублях по курсу дня, поэтому цифры совпадут не побайтово, а по смыслу.
Если оборвалось
Обрыв после первого куска текста приходит последним кадром: в нём есть и error, и usage, и cost в нём — ноль. Уже сгенерированное вы получаете, деньги за неотданный ответ возвращаются на баланс.
200 — ещё не ответ
Если модель молчит дольше, чем silence_budget шлюза, поток открывается заранее: приходит 200, заголовки SSE и keepalive, а текста ещё нет. Если после этого лестница закончится ничем, вы получите кадр с error без единого кадра с текстом и без usage. Считайте поток успешным по кадрам, а не по статусу.
- stream
- boolean
- Включить поток.
- stream_options
- object
- Не читается вовсе: usage приходит всегда, просить его отдельно не нужно.
- Idempotency-Key
- header
- В потоке не работает: повторить можно только буферный ответ.
Клиент, закрывший соединение, останавливает генерацию: шлюз отменяет запрос у вендора и возвращает на баланс разницу между резервом и тем, что успело сгенерироваться.