Пачки (Batch API)
POST /v1/batches — тысячи запросов одной отправкой, ответ в течение суток и вдвое дешевле, когда провайдер берёт их в свою отложенную очередь.
Пачка — для работы, которой некуда спешить: ночная разметка, пересчёт описаний, классификация накопленного за день. Вы отправляете список обычных chat-completion запросов одним вызовом и забираете ответы позже.
Взамен вы получаете цену: элементы, которые уходят в отложенную очередь самого провайдера, стоят примерно вдвое дешевле обычного вызова. Платите вы за это временем — ответ приходит в течение суток, а не секунд.
Пачка — не способ «послать много запросов быстрее». Если ответ нужен сейчас, используйте POST /v1/chat/completions: он и быстрее, и предсказуемее. Пачка выгодна ровно тогда, когда сутки ожидания вас устраивают.
Когда это ваш инструмент
| Задача | Пачка? |
|---|---|
| Разметить 20 тыс. отзывов к утру | да |
| Пересчитать описания каталога после импорта | да |
| Сгенерировать заголовок чата, который пользователь сейчас видит | нет |
| Ответить в интерфейсе | нет |
Отправить
curl -s "$PROSTOR_API_BASE_URL/batches" \
-H "Authorization: Bearer $PROSTOR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{
\"requests\": [
{
\"custom_id\": \"review-1041\",
\"body\": {
\"model\": \"$PROSTOR_MODEL\",
\"max_tokens\": 200,
\"messages\": [{\"role\": \"user\", \"content\": \"Оцени тональность: ...\"}]
}
},
{
\"custom_id\": \"review-1042\",
\"body\": {
\"model\": \"$PROSTOR_MODEL\",
\"max_tokens\": 200,
\"messages\": [{\"role\": \"user\", \"content\": \"Оцени тональность: ...\"}]
}
}
]
}" | jq .| Поле | Описание |
|---|---|
requests | Обязательно. От 1 до 1000 элементов. |
requests[].custom_id | Обязательно. Ваш ключ элемента, уникальный внутри пачки. По нему вы найдёте результат — порядок ответов не гарантируется. |
requests[].body | Обязательно. Обычное тело /v1/chat/completions. Переносить вызов в пачку не значит переписывать его. |
Что делает шлюз с телом элемента:
streamпринудительно выключается — смотреть на токены отложенной генерации некому;max_tokensприводится к потолку, доступному вашему ключу (см.max_output_tokens
в GET /v1/key). Элемент не может сгенерировать больше, чем под него зарезервировано.
Ответ — 200 с объектом пачки; все элементы в статусе queued.
custom_id разумно делать идентификатором вашего задания. Тогда повторная отправка потерянной пачки идемпотентна с вашей стороны, а результаты ложатся на ваши записи без сопоставления по порядку.
Опросить и забрать результаты
curl -s "$PROSTOR_API_BASE_URL/batches/$BATCH_ID" \
-H "Authorization: Bearer $PROSTOR_API_KEY" | jq .{
"id": "batch_9f1c...",
"object": "batch",
"status": "completed",
"mode": "mixed",
"created_at": 1785000000,
"completed_at": 1785031200,
"counts": { "total": 2, "queued": 0, "running": 0, "succeeded": 2, "failed": 0, "cancelled": 0 },
"results": [
{
"custom_id": "review-1041",
"status": "succeeded",
"mode": "native",
"response": {
"id": "gen-...", "object": "chat.completion",
"choices": [{ "index": 0, "message": { "role": "assistant", "content": "..." }, "finish_reason": "stop" }],
"usage": { "prompt_tokens": 120, "completion_tokens": 48, "total_tokens": 168, "cost": 0.0021 }
}
},
{
"custom_id": "review-1042",
"status": "succeeded",
"mode": "degraded",
"response": { "...": "..." }
}
]
}statusпачки:in_progress→completed|cancelled.statusэлемента:queued,running,succeeded,failed,cancelled.response— ровно тот объект, который вернул бы одиночный/v1/chat/completions,
вместе с usage и вашей стоимостью в usage.cost.
errorу элемента — тот же тип и сообщение, что у одиночного запроса. Обработка
ошибок не раздваивается на «пакетную» и «обычную».
Результаты появляются по мере готовности: опрос до завершения пачки уже отдаёт то, что успело посчитаться. Ошибка одного элемента не валит остальные.
Разумный интервал опроса — раз в несколько минут. Пачка живёт часами; опрос раз в секунду ничего не ускорит, но упрётся в лимит запросов вашего ключа.
mode: за что вы заплатили
Это поле есть и у пачки, и у каждого элемента, и оно — единственное, что честно отвечает на вопрос «сэкономил ли я».
mode | Что произошло | Цена |
|---|---|---|
native | элемент исполнила отложенная очередь провайдера | ≈ вдвое дешевле |
emulated | обычные вызовы: отложенная очередь для элемента даже не планировалась | обычная |
degraded | очередь взяла элемент, но не отдала результат вовремя — доделали обычным путём | обычная |
refused | очередь не взяла элемент вовсе: у модели нет отложенного эндпоинта | обычная |
mixed | только у пачки: элементы пошли разными путями | — |
Ни degraded, ни refused не ошибка. Ответ у вас есть, и он пришёл в обещанный срок; не случилось только скидки. Мы предпочитаем отдать вам ответ по обычной цене, чем уложиться в скидку и не отдать ничего.
Различать их стоит: они требуют разного. degraded означает, что очередь у модели есть и сегодня подвела — тут нечего менять, разве что следить за долей. refused означает, что спрашивать было бессмысленно: провайдер отказал сразу, и ожидание ничего бы не изменило. Если refused держится у модели постоянно — берите для пачек другую модель, эта отложенную очередь не поддерживает.
Какие модели попадают в native, зависит от провайдера и меняется без предупреждения с его стороны. Планируйте бюджет по обычной цене, а экономию считайте постфактум — по GET /v1/batches.
Отменить
curl -s -X POST "$PROSTOR_API_BASE_URL/batches/$BATCH_ID/cancel" \
-H "Authorization: Bearer $PROSTOR_API_KEY" | jq .Элементы, которые ещё не начались, отменяются и не оплачиваются. Уже исполняющиеся доводятся до конца и оплачиваются: их вызов к провайдеру уже сделан. Отмена завершённой пачки ничего не меняет.
Список пачек и сводка расхода
curl -s "$PROSTOR_API_BASE_URL/batches?limit=20" \
-H "Authorization: Bearer $PROSTOR_API_KEY" | jq .{
"object": "list",
"data": [
{
"id": "batch_9f1c...",
"object": "batch",
"items": 500,
"billing_status": "settled",
"created_at": 1785000000,
"completed_at": 1785031200,
"reserved_credits": 4200,
"billed_credits": 1180,
"modes": { "native": 460, "emulated": 0, "degraded": 38, "refused": 0, "failed": 2, "cancelled": 0 }
}
],
"usage": {
"batches": 12,
"items": 5400,
"by_mode": [
{ "mode": "native", "items": 4900, "total_tokens": 812000, "billed_credits": 9800, "cost_usd": "0.98" },
{ "mode": "degraded", "items": 400, "total_tokens": 66000, "billed_credits": 1600, "cost_usd": "0.16" },
{ "mode": "refused", "items": 100, "total_tokens": 17000, "billed_credits": 400, "cost_usd": "0.04" }
]
}
}usage — сводка за последние 30 дней по этому ключу. Доля native и есть ответ на вопрос, окупается ли отложенная отправка на вашем профиле нагрузки, а degraded и refused говорят, почему она не окупилась: очередь подвела или её нет вовсе.
billing_status — состояние расчёта, а не генерации: held (резерв стоит, пачка ещё идёт), settled (посчитано), released (ничего не доставлено, резерв возвращён), expired (вы перестали опрашивать, мы закрыли расчёт сами — см. ниже).
Деньги
- На отправке резервируется худший случай — сумма по всем элементам, если каждый
выберет свой max_tokens целиком. Резерв виден в заголовке X-Prostor-Cost-Credits ответа на отправку.
- На завершении списывается факт. Неиспользованный резерв возвращается. Списание
происходит один раз, на том опросе, который первым увидел пачку завершённой, — опрашивать в цикле безопасно.
- Ошибочные и отменённые элементы не оплачиваются. Они всё равно видны в
результатах: вам нужно знать, что с ними случилось.
- Больше резерва не спишется никогда.
Если вы перестали опрашивать пачку, расчёт мы закроем сами — примерно через полтора суток. Оплачены будут те элементы, которые провайдер успел выдать: бросить пачку — не способ получить генерации бесплатно, но и не способ заморозить свои кредиты навсегда.
Что может пойти не так
| Код | Когда | Что делать |
|---|---|---|
400 invalid_request_error | пустой список, нет custom_id или body.model, дубль custom_id | поправить тело |
400 too_many_items | больше 1000 элементов | разбить на несколько пачек |
402 insufficient_credits | баланса не хватает на худший случай всей пачки | пополнить или уменьшить max_tokens/размер пачки |
403 model_not_allowed | модель элемента не в белом списке ключа | см. allowed_models в GET /v1/key |
404 not_found | пачки с таким id у этого ключа нет | проверить id; чужие пачки не видны |
404 batch_lost | пачка потеряна на нашей стороне (перезапуск, истёк срок хранения) | отправить заново; доставленное уже оплачено, остальное возвращено |
429 | лимит запросов ключа | реже опрашивать |
404 batch_lost — единственная неприятность, специфичная для пачек. Незавершённые пачки не переживают перезапуск нашего узла, поэтому неизвестный id значит «отправить заново», а не «продолжать опрашивать». Держите custom_id привязанным к своим заданиям, и повторная отправка обойдётся вам только в то, что не успело посчитаться.
Ограничения
- До 1000 элементов в пачке.
- Только chat completions. Изображения, видео и эмбеддинги в пачку не принимаются.
streamигнорируется.- Пачка не переживает перезапуск узла (см.
batch_lost). - Срок ответа — до 24 часов. Обычно быстрее, но закладывайтесь на сутки.