Пакетная обработка: как считать массовые задачи

Разметить десять тысяч отзывов, перевести каталог, вытащить поля из архива документов — задачи, где обращений много, а ждать ответ немедленно не нужно. Отправлять их по одному можно, но это самый дорогой и самый медленный способ из возможных.

Почему ряд запросов упирается в потолок

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

Десять тысяч однотипных задачУсловное сравнение двух способов обработкиПо одному запросу≈ 6 чвремя обработки100 %базовая ценаУпирается в ограничение по числу запросов вминуту. Каждая ошибка обрабатывается отдельно.Пакетная обработка≈ 1 чвремя обработкинижецена за токенОграничение по частоте не мешает. Результатзабирается целиком, когда пакет готов.Плата за выгодуОтвет приходит не сразу. Для диалога с человеком способ не годится — только для фоновой работы.
Одинаковый объём работы синхронными запросами и пакетом

Пакетный режим снимает именно эту проблему. Вы отправляете все задачи одним обращением, сервис обрабатывает их в своём темпе, вы забираете результат целиком. Ограничение по частоте перестаёт быть фактором, а цена за токен обычно ниже, чем при синхронной работе.

Доступность режима стоит проверить у конкретного поставщика: у одних он есть, у других запросы просто идут по одному. При работе через российский anthropic api это указано в описании доступа. Плата за пакетный режим одна: ответ приходит не сразу. Для диалога с человеком режим не годится в принципе. Для ночной обработки — подходит идеально.

Конвейер

Конвейер пакетной задачиСборкастроки превращаются в запросыОтправкапакет уходит одним обращениемОжиданиестатус опрашивается порасписаниюРазборответы раскладываются поисходным строкамГде чаще всего теряются данныеПотерян ключ строки — ответ не привязать к исходной записиЧасть запросов завершилась ошибкой, а результат считают полнымПакет собран из строк с пустыми полями и отработал впустую
Четыре шага пакетной обработки и точки, где теряются данные

Каждый шаг выглядит тривиально, и на каждом теряются данные.

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

Отправка. Здесь стоит проверить размер: слишком большой пакет отклоняется целиком, и обидно узнать об этом после часа подготовки. Разумнее сразу резать на части фиксированного размера.

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

Разбор. Самый опасный шаг. Часть задач почти наверняка завершится ошибкой, и результат придёт неполным. Если код считает полученное полным набором, вы тихо потеряете несколько процентов данных и узнаете об этом через месяц.

Как считать стоимость заранее

Прикидка делается на выборке, а не в уме. Возьмите сто случайных задач, прогоните обычным способом, запишите средний расход входных и выходных токенов. Умножьте на общее число задач — получите порядок величины.

Что регулярно портит прикидку:

  • Средний размер посчитан на коротких записях, а в массиве есть длинный хвост. Смотрите не только среднее, но и максимум.
  • Не учтён повтор: часть задач упадёт и будет обработана дважды. Закладывайте несколько процентов сверху.
  • Не учтена инструкция. Она уходит с каждой задачей, и на десяти тысячах обращений её длина превращается в заметную сумму.

Последний пункт стоит проверить особенно внимательно. Инструкция на четыреста токенов при десяти тысячах задач — это четыре миллиона входных токенов только на повторение правил.

Что улучшить до запуска

Перед массовым прогоном полезно потратить час на три вещи.

Сократить инструкцию до минимума, который держит качество на тестовой сотне. Экономия умножается на число задач.

Ограничить max_tokens по факту, а не с запасом. Если ответ — это одно слово из списка категорий, десятка токенов хватит; лимит в тысячу означает, что однажды модель им воспользуется.

Отсеять заведомо пустые задачи. Записи без текста, дубликаты, служебные строки — всё это стоит денег ровно так же, как полезная работа.

Идемпотентность и повторный запуск

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

Практически это означает две вещи. Результат каждой задачи сохраняется отдельно и сразу, а не копится в памяти до конца прогона. И перед отправкой проверяется, нет ли уже готового результата по этому ключу — тогда повторный запуск обработает только недостающее, а не весь массив заново.

Экономия здесь прямая: повторный прогон десяти тысяч задач стоит столько же, сколько первый, а пользы приносит на несколько процентов.

Разделение на партии

Резать массив на части полезно даже когда ограничение позволяет отправить всё разом.

Партия в несколько сотен задач даёт возможность посмотреть на результат раньше, чем обработается весь объём. Если инструкция написана неудачно, вы узнаете об этом на первой партии, а не после того, как оплатите весь массив.

Партии удобнее и в разборе: ошибка локализуется в пределах одной, а не размазывается по всему прогону. И перезапуск после сбоя занимает минуты вместо часов.

Разумный порядок для большого массива: сотня задач на проверку глазами, затем партия побольше для контроля расхода, и только потом весь объём.

Что фиксировать по итогам

Средний расход на задачу, долю ошибок, время обработки партии и версию инструкции, с которой шёл прогон. Через месяц, когда результат понадобится повторить или объяснить, эти четыре числа окажутся единственным, что связывает данные с тем, как они были получены.

Проверка результата

Свести число отправленных задач с числом полученных ответов — обязательный шаг, а не необязательная роскошь. Расхождение должно приводить к остановке и разбору, а не к записи в журнал, которую никто не читает.

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

Коротко

Массовые задачи — это отдельный режим работы, а не тот же код в цикле. Он снимает ограничение по частоте обращений и обходится дешевле, но требует терпения: результат приходит не сразу. Главные риски конвейера — потерянный ключ строки и неполный результат, принятый за полный. Стоимость считайте на выборке из сотни задач, не забывая про инструкцию, которая уходит с каждой из них.