Практика эксплуатации
Бюджеты и лимиты расходов для AI-агента
Стоимость агента редко растёт одним заметным скачком. Чаще каждый запрос становится немного тяжелее: история диалога удлиняется, инструменты возвращают лишние данные, а неудачные операции повторяются. Разберём, как превратить эти скрытые расходы в измеримые величины, поставить пределы и получать алерты до перерасхода.
Почему счёт растёт незаметно
Токен — единица текста, по которой многие модели учитывают объём входа и выхода. Конкретный способ разбиения текста и тариф зависят от выбранного провайдера и модели, поэтому расчёт следует строить на фактических данных ответа API, а не на оценке количества символов.
Для агентного цикла важен не только последний ответ. В стоимость отдельного шага могут войти системные инструкции, история сообщений, результаты инструментов, найденные документы и повторно отправленные фрагменты. Если агент выполняет пять шагов, одна и та же история может оплачиваться на каждом из них.
Удобно рассматривать расходы на четырёх уровнях:
- шаг — один вызов модели;
- запуск — вся работа агента над одной задачей;
- пользователь или проект — сумма запусков за период;
- система — общий дневной или месячный бюджет.
Один месячный лимит защищает счёт, но плохо объясняет причину перерасхода. Поэтому ниже мы добавим ограничения на каждом уровне.
Шаг 1. Рассчитываем стоимость каждого вызова
Сохраняйте показатели использования из ответа API: входные токены, выходные токены, модель и идентификатор запуска. Точные названия полей различаются между SDK. Перед внедрением проверьте документацию используемой версии клиента.
Базовая формула выглядит так:
cost =
input_tokens / price_unit * input_price
+ output_tokens / price_unit * output_price
price_unit — единица, для которой задан тариф, например тысяча или миллион токенов. Значения тарифов не следует зашивать в код: они могут отличаться по модели и меняться. Храните их в конфигурации с датой начала действия.
Пример конфигурации
Ниже — демонстрационная конфигурация. Числа условны и не являются тарифами какого-либо сервиса.
{
"currency": "USD",
"price_unit_tokens": 1000000,
"models": {
"example-model": {
"input_price": 2.00,
"output_price": 8.00,
"effective_from": "2026-01-01"
}
}
}
Пример расчёта
def calculate_cost(usage, tariff):
unit = tariff["price_unit_tokens"]
return (
usage["input_tokens"] / unit * tariff["input_price"]
+ usage["output_tokens"] / unit * tariff["output_price"]
)
Округляйте сумму только при отображении. Для накопления расходов используйте десятичный тип данных, а не двоичное число с плавающей точкой.
Шаг 2. Записываем событие расходов
Для каждого вызова создавайте отдельную запись. Не сохраняйте в ней текст запроса, ответ или секреты: для финансового разбора достаточно метаданных.
{
"timestamp": "2026-07-28T09:15:00Z",
"request_id": "generated-request-id",
"run_id": "generated-run-id",
"project_id": "internal-project-key",
"model": "example-model",
"operation": "document_summary",
"attempt": 1,
"input_tokens": 12500,
"output_tokens": 700,
"cost": "0.0306",
"currency": "USD",
"status": "success",
"tariff_version": "2026-01-01"
}
Это пример структуры, а не реальный журнал. Идентификаторы должны генерироваться приложением. Поле attempt особенно полезно: оно показывает, сколько денег ушло на повторы.
Записывайте событие после каждого полученного ответа, включая ошибки, если API вернул показатели использования. Если стоимость рассчитывается асинхронно, обеспечьте идемпотентность: повторная обработка одного request_id не должна дважды увеличивать сумму.
Шаг 3. Ставим жёсткие лимиты
Лимит должен проверяться до вызова модели и после него. Предварительная проверка использует оценку максимальной стоимости, итоговая — фактические показатели API.
Безопасная конфигурация
Значения ниже условны. Подберите их по собственным данным и допустимому риску.
{
"limits": {
"max_steps_per_run": 8,
"max_retries_per_step": 2,
"max_input_tokens_per_call": 30000,
"max_output_tokens_per_call": 2000,
"max_cost_per_run": "0.50",
"max_cost_per_project_day": "20.00",
"max_cost_total_month": "400.00"
},
"on_limit": "stop_with_explanation"
}
Проверка перед запросом может выглядеть так:
estimated_cost = calculate_cost(
{
"input_tokens": estimated_input_tokens,
"output_tokens": limits["max_output_tokens_per_call"]
},
tariff
)
if run_cost + estimated_cost > limits["max_cost_per_run"]:
raise BudgetExceeded("Лимит запуска исчерпан")
if step_count >= limits["max_steps_per_run"]:
raise BudgetExceeded("Достигнут предел шагов")
Не разрешайте агенту самостоятельно повышать лимит. При остановке сохраните причину, текущую сумму, число шагов и безопасное краткое описание незавершённого действия. Пользователь должен понимать, что задача прервана бюджетным ограничением, а не завершена успешно.
Шаг 4. Добавляем предупреждения до остановки
Жёсткий предел предотвращает дальнейшие расходы, но приходит слишком поздно для оперативной реакции. Добавьте несколько порогов:
- 50% — информационное событие для наблюдаемости;
- 80% — предупреждение владельцу проекта;
- 100% — блокировка новых запусков или переход в заранее определённый экономный режим.
Проценты — пример политики, а не универсальная рекомендация. В системах с неравномерной нагрузкой полезнее прогноз: если текущий темп сохранится, будет ли исчерпан бюджет до конца периода?
usage_ratio = spent / budget
if usage_ratio >= 1:
block_new_runs()
elif usage_ratio >= 0.8:
send_alert_once(
key=f"{project_id}:{period}:80",
message={
"project_id": project_id,
"spent": str(spent),
"budget": str(budget),
"period": period,
"top_operation": top_operation
}
)
send_alert_once должен подавлять дубликаты. Иначе каждый новый запрос после достижения порога создаст ещё одно уведомление. Полезный алерт содержит период, потраченную сумму, лимит, крупнейшую операцию и ссылку на внутренний отчёт; содержимое пользовательских запросов в него включать не нужно.
Шаг 5. Находим причину перерасхода
Ежедневно агрегируйте события минимум по модели, операции, проекту и статусу. Для поиска длинного контекста сравнивайте медиану и верхние процентили входных токенов. Для поиска повторов группируйте данные по run_id и attempt.
SELECT
operation,
COUNT(*) AS calls,
SUM(input_tokens) AS input_tokens,
SUM(output_tokens) AS output_tokens,
SUM(cost) AS total_cost,
AVG(cost) AS average_call_cost
FROM agent_cost_events
WHERE timestamp >= :period_start
AND timestamp < :period_end
GROUP BY operation
ORDER BY total_cost DESC;
Затем проверьте дорогие запуски отдельно:
SELECT
run_id,
COUNT(*) AS steps,
MAX(attempt) AS max_attempt,
SUM(input_tokens) AS input_tokens,
SUM(cost) AS total_cost
FROM agent_cost_events
WHERE timestamp >= :period_start
AND timestamp < :period_end
GROUP BY run_id
ORDER BY total_cost DESC
LIMIT 20;
Эти запросы предполагают таблицу agent_cost_events с полями из примера. Адаптируйте синтаксис к своей базе данных и используйте параметризованные значения периода.
Как читать результат
- Растут входные токены при стабильном числе шагов — вероятно, раздувается история или результат инструмента.
- Растёт число шагов — агент слишком долго планирует или не распознаёт условие завершения.
- Высокий
max_attempt— расходы создают повторы, тайм-ауты либо нестабильный инструмент. - Выход значительно больше ожидаемого — проверьте ограничение ответа и формат задания.
- Одна операция доминирует в расходах — оптимизацию стоит начинать с неё, а не со средней стоимости системы.
Что сокращать в первую очередь
- Результаты инструментов. Передавайте модели нужные поля, а не полный ответ API или документ.
- Историю. Удаляйте сообщения, которые больше не влияют на задачу; важное состояние храните отдельно в компактной структуре.
- Повторы. Ограничьте их число, используйте задержку и не повторяйте ошибки, которые заведомо не исчезнут без изменения входных данных.
- Число шагов. Определите явные условия завершения и запрещайте повтор одного действия с теми же аргументами.
- Размер ответа. Задавайте достаточный, но ограниченный максимум и требуйте структурированный результат там, где это оправдано.
Автоматическое резюмирование истории тоже стоит денег и может удалить важную деталь. Сравнивайте его стоимость и качество с более простым удалением нерелевантных данных.
Проверка результата
Проверку проводите в изолированном окружении с искусственно низким бюджетом. Не используйте реальные секреты в журналах и тестовых данных.
- Установите лимит запуска ниже расчётной стоимости двух максимальных вызовов.
- Запустите контролируемую задачу, которая требует нескольких шагов.
- Убедитесь, что первый вызов создал ровно одно событие расходов.
- Повторно обработайте то же событие и проверьте, что общая сумма не изменилась.
- Доведите расход до порога предупреждения и убедитесь, что алерт пришёл один раз.
- Превысьте предварительный бюджет следующего шага и проверьте, что вызов модели не состоялся.
- Сверьте итог запуска с суммой его событий и данными панели провайдера за тот же период, учитывая возможную задержку отображения.
Проверка успешна, если сумма воспроизводима, дубликаты не учитываются, лимит действительно останавливает новые вызовы, а сообщение об остановке не маскируется под готовый ответ.
Типовые ошибки
- Считать только выходные токены
- У агента значительная часть нагрузки может приходиться на повторно отправляемый контекст. Учитывайте вход и выход отдельно.
- Проверять бюджет только после запроса
- Такой контроль умеет сообщить о перерасходе, но не предотвратить его. До вызова используйте консервативную оценку.
- Хранить один тариф без версии
- После изменения цены старые отчёты станут невоспроизводимыми. Записывайте версию тарифа в каждом событии.
- Разрешать бесконечные повторы
- Ошибка инструмента превращается в цикл расходов. Ограничивайте попытки на шаг и шаги на запуск.
- Отправлять алерт на каждый запрос
- Поток одинаковых уведомлений скрывает полезный сигнал. Дедуплицируйте алерты по проекту, периоду и порогу.
- Записывать промпты в финансовый журнал
- Это увеличивает риск утечки данных. Для анализа стоимости используйте метаданные и внутренние идентификаторы.
Ограничения подхода
Предварительная стоимость остаётся оценкой: фактическая длина ответа заранее неизвестна. Некоторые сервисы учитывают кэшированный ввод, рассуждения, пакетную обработку, инструменты или изображения по отдельным правилам. Формула должна отражать условия конкретного API.
Локальный счётчик также может расходиться с биллингом провайдера из-за округления, задержки данных, сетевой ошибки после обработки запроса или изменения тарифа. Поэтому локальные события нужны для оперативного контроля, а данные провайдера — для периодической сверки.
Наконец, жёсткий бюджет влияет на качество: агент может остановиться до получения результата. Для критичных процессов заранее определите, что важнее при достижении лимита — остановка, запрос подтверждения или переход на более дешёвую модель. Такой переход следует тестировать отдельно, поскольку модели могут по-разному соблюдать формат и инструкции.
Минимальный рабочий набор
- версионированные тарифы вне кода;
- событие стоимости для каждого вызова;
- идемпотентный учёт по идентификатору запроса;
- лимиты токенов, шагов, повторов и суммы запуска;
- дневной и месячный пределы;
- дедуплицированные предупреждения до блокировки;
- отчёт по операциям и самым дорогим запускам;
- регулярная сверка с данными провайдера.
Дополнительные схемы эксплуатации агентов собраны в практических руководствах, а определения терминов — в глоссарии.