Вибух бюджету: коли витрати агента різко зростають

Budget explosion виникає, коли неконтрольоване виконання агента швидко збільшує витрати на API та модель. Як execution budgets допомагають це стримувати.
На цій сторінці
  1. Проблема
  2. Чому це стається
  3. Які збої трапляються найчастіше
  4. Кумулятивний ріст контексту (Context cost creep)
  5. Роздутий fan-out інструментів (Tool fan-out spike)
  6. Retry amplification по шарах
  7. Снігова куля в черзі (Queue cost snowball)
  8. Як виявляти ці проблеми
  9. Як відрізнити вибух бюджету від справді дорогої задачі
  10. Як зупиняти такі збої
  11. Де це реалізується в архітектурі
  12. Самоперевірка
  13. FAQ
  14. Пов'язані сторінки

Проблема

Запит виглядає простим: перевірити оплату кількох замовлень і дати короткий підсумок.

У трейсах видно інше: за 14 хвилин один run зробив 63 кроки, 41 виклик інструментів і спалив ~$11.80. Для задачі такого класу це зазвичай ~$0.20-0.30.

Явної аварії немає: частина викликів повертає 200, агент формально "працює", але черга run'ів росте, а cost_per_run виходить за межі бюджету вже на перших хвилинах.

Система не падає.

Вона просто повільно роздуває рахунок і чергу run'ів, поки витрати виходять за межі бюджету.

Аналогія: уяви таксі з лічильником, який не вимикають між поїздками. Машина рухається, пасажири змінюються, але сума лише накопичується. Budget explosion в агентів виглядає так само: робота ніби йде, а витрати ростуть швидше за користь.

Чому це стається

Budget explosion зазвичай виникає не через один дорогий виклик, а через відсутність жорсткого контролю накопичених витрат у runtime.

У production зазвичай так:

  1. контекст і history ростуть turn за turn, тому дорожчає кожен новий model call;
  2. один крок агента може запускати fan-out інструментів, і їхня вартість множиться;
  3. ретраї живуть у кількох шарах і перетворюють короткий збій на довгу хвилю витрат;
  4. немає єдиного budget gate на кроки, токени, tool calls, час і USD;
  5. без stop reasons і cost-метрик інцидент помічають лише після рахунку.

У trace це видно як одночасний ріст prompt_tokens, tool_calls і retry_attempts, де кожен наступний крок коштує дорожче за попередній.

Без budget gate на рівні runtime кожен новий крок лише поглиблює інцидент.

Які збої трапляються найчастіше

У production найчастіше видно чотири повторювані патерни budget explosion.

Кумулятивний ріст контексту (Context cost creep)

Prompt росте без пріоритетів: history, retrieval і tool output додаються майже без обмежень.

Типова причина: немає max_prompt_tokens, caps на джерела і summarization-tier.

Роздутий fan-out інструментів (Tool fan-out spike)

Один крок запускає забагато зовнішніх викликів, часто паралельно. Навіть без помилок це різко піднімає вартість run'а.

Типова причина: немає per-tool caps і bounded fan-out.

Retry amplification по шарах

Ретраї одночасно робить runtime, tool gateway і SDK. Коротка деградація залежності перетворюється на хвилю повторних витрат.

Типова причина: retry policy не централізована в одному місці.

Снігова куля в черзі (Queue cost snowball)

Довгі дорогі run'и займають воркери, backlog росте, нові run'и теж стають дорожчими через wait і timeout.

Типова причина: немає жорсткого max_seconds, max_steps і stop reason для budget overflow.

Як виявляти ці проблеми

Budget explosion добре видно по поєднанню cost-, runtime- і queue-метрик.

МетрикаСигнал budget explosionЩо робити
cost_per_runрізкий ріст вартості одного runувімкнути max_usd і budget gate перед кожним кроком
tool_cost_shareчастка витрат на tools непропорційно великаобмежити fan-out і ввести per-tool caps
retry_attempts_per_runбагато повторів для тих самих викликівцентралізувати retries у tool gateway, додати retry budget
prompt_tokens_per_runстабільний ріст токенів без приросту якостіcaps на context sources + summarization
queue_backlogчерга росте разом із довгими дорогими run'амиобмежити max_seconds, завершувати runaway run керовано

Як відрізнити вибух бюджету від справді дорогої задачі

Не кожна дорога задача означає інцидент. Ключове питання: чи дають додаткові витрати прогнозований приріст якості.

Нормально, якщо:

  • вартість росте разом із точністю або покриттям складної задачі;
  • є контрольований профіль витрат для цього класу запитів;
  • cost_per_success залишається в межах цільової unit economics.

Небезпечно, якщо:

  • вартість росте швидше, ніж success rate;
  • ті самі retries і tool signatures повторюються без нового сигналу;
  • бюджет "вибухає" без зміни складності задачі чи SLA.

Як зупиняти такі збої

Практично це виглядає так:

  1. вводиш execution budgets: max_steps, max_seconds, max_prompt_tokens, max_tool_calls, max_usd;
  2. перевіряєш budget gate на кожному кроці, а не наприкінці run;
  3. централізуєш retries в одному tool gateway і відсікаєш non-retryable помилки;
  4. при перевищенні ліміту повертаєш stop reason, partial/fallback і алерт.

Мінімальний guard для контролю бюджету:

PYTHON
from dataclasses import dataclass
import time


@dataclass(frozen=True)
class BudgetLimits:
    max_steps: int = 30
    max_seconds: int = 120
    max_prompt_tokens: int = 12000
    max_tool_calls: int = 20
    max_retries: int = 6
    max_usd: float = 2.0


@dataclass
class BudgetUsage:
    steps: int = 0
    prompt_tokens: int = 0
    completion_tokens: int = 0
    tool_calls: int = 0
    retries: int = 0
    model_usd: float = 0.0
    tool_usd: float = 0.0


def estimate_model_usd(prompt_tokens: int, completion_tokens: int) -> float:
    # Placeholder pricing: replace with your real model pricing.
    return (prompt_tokens / 1000) * 0.003 + (completion_tokens / 1000) * 0.015


class BudgetGuard:
    def __init__(self, limits: BudgetLimits = BudgetLimits()):
        self.limits = limits
        self.usage = BudgetUsage()
        self.started_at = time.time()

    def total_usd(self) -> float:
        return self.usage.model_usd + self.usage.tool_usd

    def on_step(self) -> None:
        self.usage.steps += 1

    def on_model_call(self, prompt_tokens: int, completion_tokens: int) -> None:
        self.usage.prompt_tokens += prompt_tokens
        self.usage.completion_tokens += completion_tokens
        self.usage.model_usd = estimate_model_usd(
            self.usage.prompt_tokens,
            self.usage.completion_tokens,
        )

    def on_tool_call(self, tool_cost_usd: float = 0.0) -> None:
        self.usage.tool_calls += 1
        self.usage.tool_usd += tool_cost_usd

    def on_retry(self) -> None:
        self.usage.retries += 1

    def check(self) -> str | None:
        elapsed_s = time.time() - self.started_at

        if self.usage.steps > self.limits.max_steps:
            return "budget:max_steps"
        if elapsed_s > self.limits.max_seconds:
            return "budget:timeout"
        if self.usage.prompt_tokens > self.limits.max_prompt_tokens:
            return "budget:prompt_tokens"
        if self.usage.tool_calls > self.limits.max_tool_calls:
            return "budget:tool_calls"
        if self.usage.retries > self.limits.max_retries:
            return "budget:retries"
        if self.total_usd() > self.limits.max_usd:
            return "budget:usd"
        return None

Це базовий guard. У production його зазвичай доповнюють пер-інструментними лімітами, backoff+jitter і окремими бюджетами для model/tool частин. check() викликають після кожного кроку перед плануванням наступної дії. on_model_call(...) і on_tool_call(...) оновлюють usage одразу після фактичного виклику, щоб stop reason відображав реальну вартість run.

Де це реалізується в архітектурі

У production контроль budget explosion майже завжди розкладений між трьома шарами системи.

Agent Runtime тримає execution budgets, stop reasons і кероване завершення run'ів. Саме тут budget стає правилом, а не побажанням.

Tool Execution Layer контролює fan-out, retries, timeout і вартість зовнішніх викликів. Якщо retries розмазати по кількох шарах, витрати майже завжди мультиплікуються.

Memory Layer керує тим, що потрапляє в prompt, а що лишається в довгій пам'яті. Без цього шару токен-витрати стабільно ростуть навіть без складніших задач.

Самоперевірка

Швидка перевірка перед релізом. Відмічайте пункти і дивіться статус нижче.
Це короткий sanity-check, а не формальний аудит.

Прогрес: 0/7

⚠ Є сигнали ризику

Бракує базових контролів. Закрийте ключові пункти цього чекліста перед релізом.

FAQ

Q: Чи потрібна точна калькуляція вартості, щоб ставити budget guard?
A: Ні. На старті достатньо консервативної оцінки. Мета guard-а не фінансова звітність, а рання зупинка runaway run'ів.

Q: З якого ліміту починати?
A: Почни з консервативного max_usd і max_seconds, а потім піднімай лише там, де є підтверджений приріст якості.

Q: Що робити, якщо budget вичерпано на важливому запиті?
A: Поверни явний stop reason, покажи partial результат і запропонуй керовану ескалацію (higher tier або ручний review).

Q: Де мають жити retries, щоб не роздувати витрати?
A: В одному choke point, зазвичай у tool gateway. Коли retries є в кількох шарах, budget explosion стає майже неминучим.


Budget explosion майже ніколи не виглядає як гучна аварія. Це повільна фінансова деградація, яку зазвичай видно лише в метриках і порівнянні з baseline. Тому production-агентам потрібні не лише кращі моделі, а й жорсткий execution budget-контроль.

Пов'язані сторінки

Якщо ця проблема виникла у production, корисно також подивитися:

  • Чому AI агенти ламаються — загальна карта збоїв у production.
  • Token overuse — як ріст контексту перетворюється на ріст витрат.
  • Tool spam — як повторні виклики інструментів роздувають бюджет.
  • Tool failure — як хвиля помилок і ретраїв піднімає собівартість run'ів.
  • Agent Runtime — де ставити execution budgets і stop reasons.
  • Tool Execution Layer — де тримати retries, fan-out і cost-гейти.
⏱️ 7 хв читанняОновлено 12 березня 2026 р.Складність: ★★☆
Реалізувати в OnceOnly
Guardrails for loops, retries, and spend escalation.
Використати в OnceOnly
# onceonly guardrails (concept)
version: 1
budgets:
  max_steps: 25
  max_tool_calls: 12
  max_seconds: 60
  max_usd: 1.00
policy:
  tool_allowlist:
    - search.read
    - http.get
controls:
  loop_detection:
    enabled: true
    dedupe_by: [tool, args_hash]
  retries:
    max: 2
    backoff_ms: [200, 800]
stop_reasons:
  enabled: true
logging:
  tool_calls: { enabled: true, store_args: false, store_args_hash: true }
Інтегровано: продакшен-контрольOnceOnly
Додай guardrails до агентів з tool-calling
Зашип цей патерн з governance:
  • Бюджетами (кроки / ліміти витрат)
  • Kill switch та аварійна зупинка
  • Audit logs та трасування
  • Ідемпотентність і dedupe
  • Дозволами на інструменти (allowlist / blocklist)
Інтегрована згадка: OnceOnly — контрольний шар для продакшен агент-систем.
Приклад policy (концепт)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}

Автор

Микола — інженер, який будує інфраструктуру для продакшн AI-агентів.

Фокус: патерни агентів, режими відмов, контроль рантайму та надійність систем.

🔗 GitHub: https://github.com/mykolademyanov


Редакційна примітка

Ця документація підготовлена з допомогою AI, із людською редакторською відповідальністю за точність, ясність і продакшн-релевантність.

Контент базується на реальних відмовах, постмортемах та операційних інцидентах у розгорнутих AI-агентних системах.