Если в счёте за Claude есть длинный хвост неинтерактивной нагрузки (ночная классификация, разметка датасетов, ежемесячные проверки контента, прогоны эвалов), вы оставляете на столе примерно половину денег, пока эти вызовы идут через синхронный Messages API. Batch API сделан именно для таких задач. Отправляете пакет, ждёте до 24 часов и платите на 50% меньше за входные и выходные токены.
Что такое Batch API на самом деле
Batch API это асинхронная очередь задач. Запросы не уходят по одному с ожиданием ответа. Вы загружаете JSONL-файл, где в каждой строке лежит полный запрос Messages API, Anthropic обрабатывает весь пакет с SLA в 24 часа, а вы скачиваете JSONL с ответами, привязанными к custom_id из каждой строки.
Экономика простая:
- Входные токены: минус 50%.
- Выходные токены: минус 50%.
- Суммируется с prompt caching. Скидку за кеш можно наложить на скидку за батч на закешированной части входа.
- Один батч вмещает до 100 000 запросов.
- SLA 24 часа. Большинство батчей завершается за минуты или несколько часов, но закладываться на скорость нельзя.
Дешевле запускать Claude в большом объёме не получится. Точка.
Для каких задач Batch подходит
Batch годится там, где ни один запрос не горит по времени. Конкретно:
- Ночная классификация миллионов записей. Размечаете тикеты поддержки, маршрутизируете лиды, оцениваете документы на релевантность. Отправили в 18:00, к утру результат готов.
- Разметка датасетов и синтетические данные. Вы строите эвал-сеты, генерируете обучающие пары, дополняете RAG-корпуса саммари, заголовками и извлечёнными сущностями.
- Ежемесячная модерация контента. Пользовательский контент проверяют заново под обновлённую политику. Задача идёт раз в месяц, латентность не важна, объём очень большой.
- Прогоны эвалов. Новый промпт или модель вы прогоняете по всему регресс-набору из тысяч тест-кейсов.
- Обогащение RAG-корпуса. Саммари, FAQ и классы чанков для базы знаний вы считаете заранее. Собрали один раз и дальше отдаёте дёшево.
- Массовая семантика вместо эмбеддингов. Вы потянулись бы за эмбеддингами и классификатором, но на деле нужен вердикт LLM по каждому элементу. Батч делает путь «LLM на элемент» посильным по цене.
Когда Batch использовать не нужно
Не менее важно знать, где он не работает:
- Интерактивный UX. Всё, чего ждёт пользователь: чат, автодополнение кода, агенты внутри продукта. SLA в 24 часа здесь убийствен.
- Срочные потоки. Фрод-решения, живая модерация, алерты: тут ответ нужен сейчас.
- Мелкие задачи. При 50 запросах вы тратите на загрузку, поллинг и склейку результатов больше сил, чем экономите копеек. Batch начинает окупаться на тысячах запросов.
- Цепочки со строгой зависимостью между запросами. Если запросу N+1 нужен ответ запроса N, нужен синхронный цикл, а не батч.
Как считается цена: рабочий пример
Возьмём реалистичную задачу массовой классификации: 100 000 вызовов Sonnet 4.6, по 1 000 входных и 500 выходных токенов в каждом. В сумме это 100M входных и 50M выходных токенов.
По синхронным ставкам итог вам знаком по месячному счёту. Через Batch API та же нагрузка стоит ровно вдвое меньше:
- Синхронно: вход + выход =
X. - Batch:
X / 2.
На таком масштабе эти 50% обычно решают, делать задачу раз в квартал или запускать каждую ночь. Если же вы ещё и кешируете стабильный системный промпт на все 100 000 запросов, скидка кеша суммируется с батчевой на закешированной части, и эффективная цена входа падает ещё ниже.
Код: собираем JSONL для батча
Каждая строка входного файла самодостаточна и несёт ваш custom_id. Через codexkey направляйте клиент на https://api.codexkey.ru/v1: Batch API проксируется прозрачно.
import json
from anthropic import Anthropic
client = Anthropic(
base_url="https://api.codexkey.ru/v1",
api_key="cxa_your_key_here",
)
# 1. Собираем JSONL-файл запросов
records = load_records_to_classify() # ваши 100k элементов
with open("batch.jsonl", "w") as f:
for r in records:
line = {
"custom_id": f"record-{r['id']}",
"params": {
"model": "claude-sonnet-5",
"max_tokens": 256,
"system": [
{
"type": "text",
"text": CLASSIFIER_SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"},
}
],
"messages": [
{"role": "user", "content": r["text"]},
],
},
}
f.write(json.dumps(line) + "\n")
# 2. Отправляем батч
batch = client.messages.batches.create(
requests=[json.loads(line) for line in open("batch.jsonl")]
)
print("submitted:", batch.id)
Поллинг и разбор результатов
Поллинг устроен просто. Проверяйте processing_status с разумным интервалом: раз в 60 секунд более чем достаточно, а для большинства задач хватит и раза в 5 минут. Горячих циклов не делайте.
import time
while True:
b = client.messages.batches.retrieve(batch.id)
if b.processing_status == "ended":
break
time.sleep(60)
# Стримим результаты построчно
for result in client.messages.batches.results(batch.id):
cid = result.custom_id
if result.result.type == "succeeded":
message = result.result.message
save_classification(cid, message.content[0].text)
elif result.result.type == "errored":
log_error(cid, result.result.error)
elif result.result.type == "expired":
# запрос не успел в SLA 24h — возвращаем в очередь
requeue(cid)
Ошибки обрабатываются построчно
Учтите: Batch API сообщает об ошибках по каждому запросу, а не по батчу в целом. Несколько упавших строк остальные не портят. Код обработки должен учитывать три терминальных состояния для каждого custom_id:
succeeded: нормальный ответ, сохраняете и идёте дальше.errored: ошибка модели или валидации. Логируете, при желании повторяете одиночным синхронным вызовом, продолжаете.expired: SLA вышел раньше, чем дошла очередь до этого запроса. Ставите его в следующий батч.
Файл результатов читайте как стрим. 100k ответов в память не грузите.
Подключите prompt caching: скидки складываются
Batch и prompt caching дают независимые скидки. Если 100 000 ваших запросов делят длинный системный промпт или контекст документа, пометьте его cache_control, и закешированная часть получит скидку кеша сверх батчевых 50%. Если агентный классификатор держит системный промпт на 4k токенов, это самый мощный рычаг после самого перехода на батч.
Операционный нюанс при работе через codexkey
codexkey проксирует Batch API в Anthropic прозрачно: те же эндпоинты, тот же формат JSONL, тот же SDK. Есть один момент, свойственный шлюзу с оплатой за токены: батч списывается в момент отправки, поэтому пополните баланс на всю сумму задачи до вызова batches.create. Дашборд показывает оценку стоимости батча по входному файлу. Отталкивайтесь от неё и добавьте запас на выходные токены.
Итог
Если задача не обязана отвечать за секунды, ей не место на синхронном API. Переносите ночные джобы, обработку датасетов, ежемесячные прогоны и эвалы в Batch, добавляйте prompt caching на стабильные части, а синхронный API оставьте для того, чего реально ждёт пользователь.