Budget explosion (коли агент спалює гроші) + фікси + код

  • Побач ранні сигнали, поки рахунок не поліз вгору.
  • Зрозумій, що ламається в проді й чому.
  • Скопіюй guardrails: budgets, stop reasons, validation.
  • Знай, коли це не справжня root cause.
Сигнали виявлення
  • Tool calls на run зростають (або повторюються з args hash).
  • Витрати/токени ростуть без кращих результатів.
  • Retries стають постійними (429/5xx).
Бюджети рідко падають одним багом. Вони течуть через ретраї, prompt bloat і tool spam. Як budget explosion стається в проді й як капнути spend per run.
На цій сторінці
  1. Проблема (з реального продакшену)
  2. Чому це ламається в продакшені
  3. 1) Токени масштабуються контекстом, а не інтенцією
  4. 2) Ретраї множать cost
  5. 3) “Planning” — чистий overhead
  6. 4) Tool spam робить будь-який budget безсенсовним
  7. 5) Ти не бачиш spend, якщо не логиш його
  8. Приклад реалізації (реальний код)
  9. Реальний інцидент (з цифрами)
  10. Компроміси
  11. Коли НЕ варто
  12. Чекліст (можна копіювати)
  13. Безпечний дефолтний конфіг (JSON/YAML)
  14. FAQ (3–5)
  15. Пов’язані сторінки (3–6 лінків)
Інтерактивний флоу
Сценарій:
Крок 1/2: Execution

Normal path: execute → tool → observe.

Проблема (з реального продакшену)

Ти шипиш агента.

У тестах це “кілька центів”.

Потім він ловить прод‑трафік і хтось пише в Slack:

“Чому ми витратили $900 на агента вчора?”

Budget explosions майже ніколи не одна велика помилка. Це смерть від тисячі дрібних:

  • токени дрейфують вгору
  • ретраї множаться
  • tool calls стають лупами
  • промпти ростуть “ще трошки, і все”

Якщо ти не міряєш і не капаєш бюджети — ти дізнаєшся про spend від фінансів. Фінанси — не моніторинг.

Чому це ламається в продакшені

В агентних системах витрати множаться.

1) Токени масштабуються контекстом, а не інтенцією

Інтенція: “підсумуй це”. Реалізація: “встав останні 40 повідомлень + 6 tool outputs + 2 runbooks”.

Вартість залежить від того, що ти годуєш моделлю, а не від того, що попросив користувач.

2) Ретраї множать cost

Якщо падає модель‑кол і ти ретраїш:

  • платиш двічі
  • додаєш лейтенсі

Якщо падає tool‑кол і ти ретраїш:

  • платиш у tool cost
  • часто ще й платиш більше токенів, бо пояснюєш фейл

Ретраї не безкоштовні. У loop’ах вони мультиплікативні.

3) “Planning” — чистий overhead

Planning‑важкі агенти палять токени ще до того, як зроблять щось корисне. Це ок, якщо це зупиняє tool spam. Це не ок, якщо це просто “ще подумай”.

4) Tool spam робить будь-який budget безсенсовним

Якщо ти не капнеш tool calls, агент може витратити $0.01 на токени і $5 на tools. Твій “token budget” не захистив. Бо це був не той бюджет.

5) Ти не бачиш spend, якщо не логиш його

Якщо в логах немає:

  • model tokens in/out
  • tool calls count
  • per-run cost estimate
  • stop reason

…ти не зловиш drift алертами.

Приклад реалізації (реальний код)

Мінімальний per-run budget tracker:

  • зупинка по часу, кроках, tool calls
  • груба оцінка cost і стоп по spend
  • stop reason, на який можна повісити алерт
PYTHON
from dataclasses import dataclass
import time


@dataclass(frozen=True)
class Budget:
  max_steps: int = 25
  max_seconds: int = 60
  max_tool_calls: int = 12
  max_usd: float = 1.00


@dataclass
class Usage:
  tool_calls: int = 0
  model_tokens_in: int = 0
  model_tokens_out: int = 0
  estimated_usd: float = 0.0


class BudgetExceeded(RuntimeError):
  pass


def estimate_usd(tokens_in: int, tokens_out: int) -> float:
  # Replace with real pricing for your model(s).
  # This is intentionally simple: budgets need to be approximate, not perfect.
  return (tokens_in + tokens_out) * 0.000002  # $/token (placeholder)


class BudgetGuard:
  def __init__(self, budget: Budget) -> None:
      self.budget = budget
      self.usage = Usage()
      self.started = time.time()
      self.steps = 0

  def on_step(self) -> None:
      self.steps += 1
      if self.steps > self.budget.max_steps:
          raise BudgetExceeded("step budget exceeded")
      if time.time() - self.started > self.budget.max_seconds:
          raise BudgetExceeded("time budget exceeded")
      if self.usage.tool_calls > self.budget.max_tool_calls:
          raise BudgetExceeded("tool budget exceeded")
      if self.usage.estimated_usd > self.budget.max_usd:
          raise BudgetExceeded("cost budget exceeded")

  def on_tool_call(self) -> None:
      self.usage.tool_calls += 1

  def on_model_call(self, *, tokens_in: int, tokens_out: int) -> None:
      self.usage.model_tokens_in += tokens_in
      self.usage.model_tokens_out += tokens_out
      self.usage.estimated_usd = estimate_usd(
          self.usage.model_tokens_in, self.usage.model_tokens_out
      )


def run(task: str, *, budget: Budget) -> str:
  guard = BudgetGuard(budget)

  while True:
      guard.on_step()

      # model call (pseudo)
      action, tokens_in, tokens_out = llm_decide(task)  # (pseudo)
      guard.on_model_call(tokens_in=tokens_in, tokens_out=tokens_out)

      if action.kind == "tool":
          guard.on_tool_call()
          result = call_tool(action.name, action.args)  # (pseudo)
          task = update_state(task, action, result)  # (pseudo)
      else:
          return action.final_answer
JAVASCRIPT
export class BudgetExceeded extends Error {}

export class BudgetGuard {
constructor(budget) {
  this.budget = budget;
  this.started = Date.now();
  this.steps = 0;
  this.usage = { toolCalls: 0, tokensIn: 0, tokensOut: 0, estimatedUsd: 0 };
}

estimateUsd(tokensIn, tokensOut) {
  // Replace with real pricing. Approximate is fine for guards.
  return (tokensIn + tokensOut) * 0.000002;
}

onStep() {
  this.steps += 1;
  const elapsedS = (Date.now() - this.started) / 1000;
  if (this.steps > this.budget.maxSteps) throw new BudgetExceeded("step budget exceeded");
  if (elapsedS > this.budget.maxSeconds) throw new BudgetExceeded("time budget exceeded");
  if (this.usage.toolCalls > this.budget.maxToolCalls) throw new BudgetExceeded("tool budget exceeded");
  if (this.usage.estimatedUsd > this.budget.maxUsd) throw new BudgetExceeded("cost budget exceeded");
}

onToolCall() {
  this.usage.toolCalls += 1;
}

onModelCall({ tokensIn, tokensOut }) {
  this.usage.tokensIn += tokensIn;
  this.usage.tokensOut += tokensOut;
  this.usage.estimatedUsd = this.estimateUsd(this.usage.tokensIn, this.usage.tokensOut);
}
}

Ключова деталь: budgets перевіряються постійно, а не “під кінець”. Треба зупинитись до того, як ти впадеш зі скелі.

Реальний інцидент (з цифрами)

У нас був агент, який у деві стабільно працював на ~3k tokens/request.

Потім ми додали “helpful context”:

  • останні 20 повідомлень користувача
  • повні tool outputs (включно з HTML)
  • шматок runbook

Розмір промпта поплив. Ніхто не помітив.

Impact за 48 годин:

  • median tokens/request: 3k → 16k
  • p95 latency: 2.4s → 8.9s
  • spend: +$740 відносно baseline

Fix:

  1. hard budgets (tokens, tool calls, time, spend)
  2. prompt builder з caps + summarization
  3. алерти на tokens/request і spend/run
  4. safe-mode fallback, коли budget hit

Це не “модель стала гіршою”. Ми дали їй більше тексту і сподівались, що рахунок не помітить.

Компроміси

  • Tight budgets підвищують “stopped early” відповіді. Ок — краще, ніж runaway spend.
  • Spend estimation приблизний. Йому не треба бути ідеальним, щоб бути корисним.
  • Саммарі економлять токени, але можуть втратити нюанс. Використовуй там, де безпечно.

Коли НЕ варто

  • Якщо ти взагалі не можеш оцінити cost (кілька моделей/tools) — почни з time/tool budgets і додай cost потім.
  • Якщо ворклоад детермінований — workflow із фіксованим cost кращий.
  • Якщо тобі потрібно long-context reasoning — плануй більший бюджет і роби це явно.

Чекліст (можна копіювати)

  • [ ] Budgets: steps, tool calls, seconds, USD
  • [ ] Track tokens in/out per run
  • [ ] Оцінюй spend/run і алерть на спайки
  • [ ] Капни retries (model + tool)
  • [ ] Капни untrusted text (HTML/tool dumps)
  • [ ] Summarize або truncate over-budget контекст
  • [ ] Повертай stop reason (не мовчи таймаутом)

Безпечний дефолтний конфіг (JSON/YAML)

YAML
budgets:
  max_steps: 25
  max_seconds: 60
  max_tool_calls: 12
  max_usd: 1.0
llm:
  retries: { max_attempts: 2 }
context:
  max_prompt_tokens: 2500
  summarize_when_over_budget: true

FAQ (3–5)

Потрібна точна калькуляція вартості для budgets?
Ні. Guards можуть бути приблизні. Мета — зупиняти runaway runs до того, як це стане інвойсом.
З якого бюджету почати?
Час + tool calls. Потім додай token/spend, коли зможеш їх міряти.
Як обробляти запити, яким треба більше бюджету?
Ескалюй: попроси підтвердження, переключи на більший tier або запусти async із видимим статусом.
Можна поставити величезний budget і забути?
Можна. Тоді знову дізнаєшся про проблеми від фінансів і on-call.

Q: Потрібна точна калькуляція вартості для budgets?
A: Ні. Guards можуть бути приблизні. Мета — зупиняти runaway runs до того, як це стане інвойсом.

Q: З якого бюджету почати?
A: Час + tool calls. Потім додай token/spend, коли зможеш їх міряти.

Q: Як обробляти запити, яким треба більше бюджету?
A: Ескалюй: попроси підтвердження, переключи на більший tier або запусти async із видимим статусом.

Q: Можна поставити величезний budget і забути?
A: Можна. Тоді знову дізнаєшся про проблеми від фінансів і on-call.

Пов’язані сторінки (3–6 лінків)

Не впевнені, що це ваш кейс?

Спроєктувати агента →
⏱️ 7 хв читанняОновлено Бер, 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-агентів у продакшені.

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

Патерни та рекомендації базуються на постмортемах, режимах відмов і операційних інцидентах у розгорнутих системах, зокрема під час розробки та експлуатації governance-інфраструктури для агентів у OnceOnly.