Потоковый ответ
Как получать ответ модели по частям, из каких кадров он состоит, где искать расход и что происходит при обрыве или ошибке посреди потока.
Включите поток
Добавьте в запрос "stream": true, и ответ пойдёт по мере появления. Чтобы получить расход, добавьте и stream_options со значением include_usage.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://epicai.ru/v1",
api_key=os.environ["EPICAI_API_KEY"],
)
stream = client.chat.completions.create(
model="gpt-6-luna",
messages=[{"role": "user", "content": "Напиши три слова: привет, мир, epicai."}],
max_tokens=100,
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage:
print("\nСписано TOK:", chunk.model_extra["epic"]["cost_tok"])
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://epicai.ru/v1",
apiKey: process.env.EPICAI_API_KEY,
});
const stream = await client.chat.completions.create({
model: "gpt-6-luna",
messages: [{ role: "user", content: "Напиши три слова: привет, мир, epicai." }],
max_tokens: 100,
stream: true,
stream_options: { include_usage: true },
});
for await (const chunk of stream) {
const piece = chunk.choices[0]?.delta?.content;
if (piece) process.stdout.write(piece);
if (chunk.usage) console.log("\nСписано TOK:", chunk.epic.cost_tok);
}
Без библиотеки поток читают построчно, у curl для этого есть ключ -N, он отключает буферизацию вывода.
Из чего состоит поток
Ответ приходит как server-sent events с типом text/event-stream. Каждое событие — строка data: с JSON и пустая строка после неё. Идентификатор id и время created одинаковы во всех кадрах, а в конце стоит служебная строка [DONE]. Это не JSON, её не разбирают.
data: {"id":"chatcmpl-7ca0…","object":"chat.completion.chunk","created":1793095684,"model":"gpt-6-luna","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-7ca0…","object":"chat.completion.chunk","created":1793095684,"model":"gpt-6-luna","choices":[{"index":0,"delta":{"content":"пр"},"finish_reason":null}]}
data: {"id":"chatcmpl-7ca0…","object":"chat.completion.chunk","created":1793095684,"model":"gpt-6-luna","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-7ca0…","object":"chat.completion.chunk","created":1793095684,"model":"gpt-6-luna","choices":[],"usage":{"prompt_tokens":16,"completion_tokens":11,"total_tokens":27},"epic":{"cost_tok":1,"request_id":"req_7ca0…"}}
data: [DONE]
- Первый кадр несёт роль
assistantи пустойcontent. - Дальше идут кадры с кусочками текста в
delta.content. Рассуждения модели, если вендор их отдаёт, приходят вdelta.reasoning_content, а вызовы функций вdelta.tool_calls(формат разобран в статье Вызов функций). - Предпоследний обычный кадр содержит пустой
deltaиfinish_reason. - Кадр с
usageиepicприходит только приinclude_usage, и у него пустой массивchoices. Код, который читаетchoices[0]без проверки, на этом кадре падает.
Если модель молчит долго, epicai раз в 10 секунд шлёт строку-комментарий : keep-alive, чтобы прокси не закрыл соединение. Клиенты на основе SSE её пропускают, а свой разбор должен пропускать строки, которые начинаются с двоеточия.
Где взять расход
Заголовок x-epic-cost-tok в потоковом ответе не приходит, потому что заголовки уходят раньше, чем известна сумма. Расход лежит в кадре с usage, в поле epic.cost_tok. Без include_usage числа в потоке нет, хотя TOK списываются как обычно. Сумму тогда покажут GET /v1/usage и история операций на сайте.
Если что-то пошло не так
Ошибка до первого байта приходит обычным ответом с кодом 4xx или 5xx и телом {"error": …}. Когда поток уже начался, код 200 отправлен, и вместо смены статуса epicai присылает кадр с ошибкой, а за ним [DONE].
data: {"error":{"message":"Модель не ответила вовремя","type":"server_error","param":null,"code":"upstream_timeout"}}
data: [DONE]
Поэтому разбор потока должен проверять наличие ключа error в каждом кадре. Всё, что модель успела отдать до сбоя, остаётся у вас и оплачивается, а если не отдано ничего, TOK не списываются.
Закрытие соединения на вашей стороне epicai замечает, прекращает приём ответа у вендора и списывает токены за доставленную часть. Если вендор успел сообщить расход, берётся он, иначе токены оцениваются по уже отправленному тексту. Максимальная длительность потока 900 секунд.
Не нашли ответ?
Посмотрите частые вопросы или напишите в поддержку.