Normal path: execute → tool → observe.
Problem (aus der Praxis)
Du shipst einen Agent.
In Tests kostet er “ein paar Cent”.
Dann kommt Production Traffic und jemand schreibt:
“Warum haben wir gestern $900 für den Agent ausgegeben?”
Budget Explosions sind selten ein Big Bang. Es ist death by a thousand cuts:
- Token Usage driftet hoch
- Retries multiplizieren
- Tool Calls werden Loops
- Prompts werden größer “nur dieses eine Mal”
Ohne Messen + Caps lernst du Spend von Finance. Finance ist kein Monitoring.
Warum das in Production bricht
Kosten komponieren in Agent-Systemen.
1) Tokens skalieren mit Kontext, nicht mit Intent
Intent: “summarize this”. Implementierung: “paste 40 Messages + 6 Tool Outputs + 2 Runbooks”.
Token Costs skalieren mit dem, was du reinpackst.
2) Retries multiplizieren Kosten
Retry bei Model Call:
- du zahlst doppelt
- du addest Latenz
Retry bei Tool Call:
- du zahlst Tool Costs
- plus oft mehr Tokens (weil du Failure erklärst)
Retries sind nicht free. In Loops sind sie multiplikativ.
3) Planning ist Overhead
Planning-heavy Agents verbrennen Tokens, bevor sie irgendwas Nützliches tun. Kann ok sein, wenn es Tool Spam verhindert. Nicht ok, wenn es nur “mehr nachdenken” ist.
4) Tool Spam macht Token Budgets wertlos
Ohne Tool-Call Caps kann ein Agent $0.01 an Tokens und $5 an Tools verbrennen. Dein Token Budget hat dich nicht geschützt. Falscher Budget-Typ.
5) Ohne Logs weißt du Spend nicht
Ohne:
- tokens in/out
- tool calls/run
- per-run cost estimate
- stop reason
…kannst du nicht alerten.
Implementierungsbeispiel (echter Code)
Minimaler per-run Budget Tracker:
- stoppt bei time/steps/tool calls
- schätzt Cost grob und stoppt bei spend
- liefert stop reason für Alerts
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:
return (tokens_in + tokens_out) * 0.000002 # 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()
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_answerexport 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) {
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 werden kontinuierlich geprüft, nicht am Ende. Du willst stoppen, bevor du am Cliff bist.
Echter Incident (mit Zahlen)
Ein Agent lief in dev mit ~3k Tokens/Request.
Dann kam “helpful context”:
- letzte 20 Messages
- full Tool Outputs (inkl. HTML)
- Runbook Snippet
Prompt size driftete. Keiner merkte es.
Impact über 48 Stunden:
- median tokens/request: 3k → 16k
- p95 Latenz: 2.4s → 8.9s
- spend: +$740 vs baseline
Fix:
- harte Budgets (tokens, tools, time, spend)
- prompt builder mit Caps + Summaries
- Alerts auf tokens/request und spend/run
- safe-mode fallback wenn budgets hitten
Das Modell wurde nicht “schlechter”. Wir haben mehr reingeschoben und gehofft, der Bill merkt’s nicht.
Abwägungen
- Tight budgets erhöhen “stopped early”. Besser als runaway spend.
- Cost Estimation ist grob. Muss nicht perfekt sein.
- Summaries sparen Tokens, verlieren Nuance.
Wann du es NICHT nutzen solltest
- Wenn du Cost gar nicht schätzen kannst: starte mit time/tool budgets.
- Deterministisch? Workflow mit fixen Kosten.
- Long-context nötig? Dann Budget bewusst höher setzen.
Checkliste (Copy/Paste)
- [ ] Budgets: steps, tool calls, seconds, USD
- [ ] tokens in/out pro Run tracken
- [ ] spend/run schätzen + Alerts auf Spikes
- [ ] Retries cappen (model + tool)
- [ ] untrusted text size cappen (HTML/log dumps)
- [ ] summarizen/truncaten bei over-budget
- [ ] stop reason returnen (keine silent timeouts)
Sicheres Default-Config-Snippet (JSON/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)
Von Patterns genutzt
Verwandte Failures
Q: Brauche ich exakte Cost Accounting für Budgets?
A: Nein. Guards dürfen grob sein. Ziel ist Runaway Runs zu stoppen, bevor sie Rechnungen werden.
Q: Mit welchem Budget starte ich?
A: Time + tool calls. Tokens/Spend dazu, sobald du’s messen kannst.
Q: Was bei Tasks, die mehr Budget brauchen?
A: Escalate: Confirmations, größere Budget-Tiers, oder async Runs mit Status.
Q: Kann ich einfach ein riesiges Budget setzen?
A: Kannst du. Dann lernst du wieder von Finance und on-call.
Verwandte Seiten (3–6 Links)
- Foundations: LLM-Limits · Production-ready Agent
- Failure: Tool-Spam Loops · Infinite Loop
- Governance: Tool Permissions
- Production stack: Production Stack