← Все гайды AI Release
EN Подписаться на @ai_release1

Как настроить лимиты расходов и бюджеты для ИИ-агентов: контроль затрат на API

Обновлено: 21.10.2026 · AI Release · @ai_release1
Как настроить лимиты расходов и бюджеты для ИИ-агентов: контроль затрат на API

TL;DR

Требования и подготовка

Иллюстрация к разделу «Требования и подготовка»
Иллюстрация к разделу «Требования и подготовка»

Шаг 1. Инвентаризация: раздельные ключи и журнал расходов

Что сделать: создайте отдельный API-ключ на каждого агента, присвойте агенту идентификатор через переменную окружения и включите запись журнала расходов в формате JSONL (JSON Lines — один JSON-объект на строку).

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

Если у вас команда субагентов, заведите ключ и идентификатор каждому — как это устроено в материале Claude Code для команды: как настроить субагентов и общий ИИ-суперагент.

Пример: переменные окружения в ~/.bashrc (Windows: setx AI_AGENT_ID "support-bot"):

export AI_AGENT_ID=support-bot
export AI_LEDGER_PATH="$HOME/projects/support-bot/logs/ledger.jsonl"
export AI_BUDGET_FILE="$HOME/projects/support-bot/budgets.yaml"
mkdir -p "$(dirname "$AI_LEDGER_PATH")"
touch "$AI_LEDGER_PATH"

Каждая запись журнала — одна завершённая генерация:

{"ts":"2025-05-14T09:12:03Z","request_id":"req_7f3a91","agent":"support-bot","model":"<модель-из-прайса>","prompt_tokens":1180,"completion_tokens":260,"cost_usd":0.0094}

Поле cost_usd берите из ответа провайдера или считайте по его прайсу — точные цены за 1000 токенов сверьте с документацией провайдера, здесь они не приводятся намеренно.

Как проверить: допишите одну тестовую строку и убедитесь, что журнал читается:

echo '{"ts":"2025-05-14T09:12:03Z","request_id":"req_test_1","agent":"support-bot","cost_usd":0.01}' >> "$AI_LEDGER_PATH"
wc -l "$AI_LEDGER_PATH"
jq -c . "$AI_LEDGER_PATH" | tail -n 1

Ожидаемый вывод: 1 (число строк) и распечатанный JSON без ошибок парсинга. Если jq ругается parse error: Invalid numeric literal — в файл попала не JSON-строка, удалите её.

Шаг 2. Бюджеты, лимиты и жёсткий стоп

Иллюстрация к разделу «Шаг 2. Бюджеты, лимиты и жёсткий стоп»
Иллюстрация к разделу «Шаг 2. Бюджеты, лимиты и жёсткий стоп»

Что сделать: опишите лимиты в YAML-файле и поставьте перед запуском агента скрипт-гейт, который сравнивает фактические траты с лимитом и завершает работу с ненулевым кодом.

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

budgets.yaml:

version: 1
currency: USD
defaults:
  monthly_usd: 20
  daily_usd: 2
  hard_stop: true
agents:
  support-bot:
    monthly_usd: 40
    daily_usd: 3
    hard_stop: true      # true — блокировать запуск, false — только предупреждать
  research-agent:
    monthly_usd: 120
    daily_usd: 10
    hard_stop: true      # исследовательский агент дороже: больше инструментов и контекста

check_budget.py:

#!/usr/bin/env python3
"""Гейт бюджета: 0 — можно тратить, 1 — лимит превышен, 2 — ошибка конфигурации."""
import argparse, json, os, sys
from datetime import datetime, timezone
import yaml

def load_limits(path, agent):
    with open(path, encoding="utf-8") as f:
        cfg = yaml.safe_load(f) or {}
    limits = dict(cfg.get("defaults", {}))
    limits.update((cfg.get("agents") or {}).get(agent, {}))
    if "monthly_usd" not in limits or "daily_usd" not in limits:
        sys.exit(f"config error: monthly_usd/daily_usd missing for agent={agent}")
    return limits

def spend(ledger, agent):
    month = datetime.now(timezone.utc).strftime("%Y-%m")
    day = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    mtd = dtd = 0.0
    seen = set()
    with open(ledger, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            row = json.loads(line)
            if row.get("agent") != agent:
                continue
            rid = row.get("request_id")
            if rid:
                if rid in seen:      # ретраи не должны считаться дважды
                    continue
                seen.add(rid)
            ts = row["ts"]
            cost = float(row.get("cost_usd", 0))
            if ts.startswith(month):
                mtd += cost
            if ts.startswith(day):
                dtd += cost
    return mtd, dtd

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--agent", default=os.environ.get("AI_AGENT_ID"))
    p.add_argument("--budgets", default=os.environ.get("AI_BUDGET_FILE", "budgets.yaml"))
    p.add_argument("--ledger", default=os.environ.get("AI_LEDGER_PATH", "logs/ledger.jsonl"))
    a = p.parse_args()
    lim = load_limits(a.budgets, a.agent)
    mtd, dtd = spend(a.ledger, a.agent)
    m_lim, d_lim = float(lim["monthly_usd"]), float(lim["daily_usd"])
    blocked = a.hard_stop_check = bool(lim.get("hard_stop")) and (mtd >= m_lim or dtd >= d_lim)
    print(f"agent={a.agent} month={mtd:.2f}/{m_lim:.2f} day={dtd:.2f}/{d_lim:.2f} "
          f"status={'BLOCKED' if blocked else 'OK'}")
    sys.exit(1 if blocked else 0)

Обёртка run_with_budget.sh ставит гейт перед агентом:

#!/usr/bin/env bash
set -euo pipefail
python3 check_budget.py --agent "$AI_AGENT_ID" || {
  echo "budget stop: agent=$AI_AGENT_ID — запуск отменён" >&2
  exit 1
}
exec python3 run_agent.py "$@"

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

Как проверить: запустите гейт вручную и посмотрите на статус; затем убедитесь, что агент не стартует при превышении:

chmod +x run_with_budget.sh
python3 check_budget.py --agent support-bot; echo "exit=$?"

Ожидаемый вывод при тратах ниже лимита: agent=support-bot month=12.40/40.00 day=1.10/3.00 status=OK, затем exit=0. Чтобы увидеть блокировку, временно поставьте в budgets.yaml для support-bot значение monthly_usd: 1 — вывод станет status=BLOCKED, exit=1, а ./run_with_budget.sh напечатает budget stop: agent=support-bot — запуск отменён. Верните лимит обратно.

Шаг 3. Проверка работы и финальный результат

Иллюстрация к разделу «Шаг 3. Проверка работы и финальный результат»
Иллюстрация к разделу «Шаг 3. Проверка работы и финальный результат»

Что сделать: проведите три проверки — нормальный запуск, срабатывание стопа и корректность цифр в журнале — и зафиксируйте чек-лист приёмки.

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

Пример прогона:

AI_AGENT_ID=research-agent ./run_with_budget.sh --task "собрать 10 источников"
echo "---"
tail -n 3 "$AI_LEDGER_PATH" | jq -c '{agent, request_id, cost_usd}'

Успех выглядит так: агент отработал, в журнале появились новые строки с "agent":"research-agent", а повторный запуск python3 check_budget.py --agent research-agent печатает status=OK уже с выросшей суммой month=….

Чек-лист приёмки:

Как проверить: grep -c '"request_id"' "$AI_LEDGER_PATH" и wc -l < "$AI_LEDGER_PATH" должны дать одинаковые числа. Расхождение означает, что часть записей пишется без идентификатора и дедупликация в скрипте по ним не работает.

Возможные проблемы и решения

Проблема. Гейт падает при первом запуске на импорте YAML

Проблема. Лимит срабатывает раньше времени, хотя траты реально ниже

Проблема. Агент получает отказ от провайдера, хотя локальный гейт показывает OK

Резюме и следующие шаги

Рабочая схема контроля — три слоя: отдельные ключи на агента, собственный журнал расходов и гейт, блокирующий запуск до запроса, плюс независимый лимит в консоли провайдера. Дальше имеет смысл снизить стоимость запросов: часть дешёвых вызовов можно увести на локальную модель — см. Локальная модель или облако на iPhone: как настроить гибридный роутинг ИИ-запросов.

FAQ

Как считать стоимость запроса, если провайдер не возвращает её в ответе?

Умножьте prompt_tokens и completion_tokens из ответа на цену за 1000 токенов из прайса провайдера и запишите результат в cost_usd. Цены меняются — сверьтесь с документацией провайдера, а не с зашитыми в скрипт константами.

Нужен ли отдельный бюджет каждому субагенту внутри одного процесса?

Да, если у них разные профили нагрузки. Один AI_AGENT_ID на субагента и отдельная секция в budgets.yaml дают понять, кто именно выедает месячный лимит.

Что делать, когда месячный бюджет исчерпан, а агент нужен сегодня?

Переключите агента на резервный ключ с отдельной секцией бюджета (AI_AGENT_ID=agent-fallback) или понизьте стоимость запроса: сократите контекст и переведите часть шагов на более дешёвую модель из прайса.

Гейт проверяет бюджет только перед запуском — не потратит ли агент лишнее внутри длинного цикла?

Потратит: между стартом и финишем лимит не пересчитывается. Для длинных циклов вызывайте check_budget.py после каждого шага и завершайте цикл по коду возврата 1.

🤖 Кратко для ИИ