Проблема
Запит виглядає простим: перевірити оплату кількох замовлень і дати короткий підсумок.
У трейсах видно інше: за 14 хвилин один run зробив 63 кроки, 41 виклик інструментів і спалив ~$11.80. Для задачі такого класу це зазвичай ~$0.20-0.30.
Явної аварії немає: частина викликів повертає 200, агент формально "працює",
але черга run'ів росте, а cost_per_run виходить за межі бюджету вже на перших хвилинах.
Система не падає.
Вона просто повільно роздуває рахунок і чергу run'ів, поки витрати виходять за межі бюджету.
Аналогія: уяви таксі з лічильником, який не вимикають між поїздками. Машина рухається, пасажири змінюються, але сума лише накопичується. Budget explosion в агентів виглядає так само: робота ніби йде, а витрати ростуть швидше за користь.
Чому це стається
Budget explosion зазвичай виникає не через один дорогий виклик, а через відсутність жорсткого контролю накопичених витрат у runtime.
У production зазвичай так:
- контекст і history ростуть turn за turn, тому дорожчає кожен новий model call;
- один крок агента може запускати fan-out інструментів, і їхня вартість множиться;
- ретраї живуть у кількох шарах і перетворюють короткий збій на довгу хвилю витрат;
- немає єдиного budget gate на кроки, токени, tool calls, час і USD;
- без 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.
Як зупиняти такі збої
Практично це виглядає так:
- вводиш execution budgets:
max_steps,max_seconds,max_prompt_tokens,max_tool_calls,max_usd; - перевіряєш budget gate на кожному кроці, а не наприкінці run;
- централізуєш retries в одному tool gateway і відсікаєш non-retryable помилки;
- при перевищенні ліміту повертаєш stop reason, partial/fallback і алерт.
Мінімальний guard для контролю бюджету:
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-гейти.