ток

Документация / Стриминг

Ответ по частям

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
В потоке не работает: повторить можно только буферный ответ.

Клиент, закрывший соединение, останавливает генерацию: шлюз отменяет запрос у вендора и возвращает на баланс разницу между резервом и тем, что успело сгенерироваться.