Normal path: execute → tool → observe.
Le problème (côté prod)
Tu shipes un agent.
En test, ça coûte “quelques centimes”.
Puis ça passe en prod et quelqu’un demande :
“Pourquoi on a dépensé $900 hier ?”
Les explosions de budget sont rarement un gros bug. C’est l’accumulation :
- tokens qui dérivent
- retries qui multiplient
- tools appelés en boucle
- prompts gonflés “juste un peu”
Si tu ne mesures pas, tu apprends via finance. Finance n’est pas un système d’alerting.
Pourquoi ça casse en prod
1) Les tokens scalent avec le contexte, pas avec l’intention
Intention : “résume ça”. Réalité : “colle 40 messages + 6 tool outputs + 2 runbooks”.
2) Les retries multiplient coût + latence
Modèle retry → tu payes deux fois. Tool retry → tu payes tool + plus de tokens pour “expliquer l’erreur”.
3) Planning = overhead
Planifier avant d’agir peut être utile… ou juste coûteux.
4) Tool spam rend le budget tokens inutile
Tu peux avoir $0.01 de tokens et $5 de tools.
5) Sans logs, tu ne sais pas ce que tu dépenses
Tu veux des logs sur tokens, tool calls, estimations de coût, stop reasons.
Exemple d’implémentation (code réel)
Budget guard minimal :
- time/steps/tool calls
- estimation de coût
- stop reason exploitable
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
)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) {
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);
}
}Les budgets se checkent en continu. Tu veux arrêter avant la falaise.
Incident réel (avec chiffres)
On avait un agent à ~3k tokens/request en dev.
On a ajouté du “contexte utile” :
- 20 derniers messages
- outputs bruts (HTML)
- extrait de runbook
Personne n’a surveillé.
Impact (48h) :
- tokens/request : 3k → 16k
- latence p95 : 2.4s → 8.9s
- spend : +$740
Fix :
- budgets (tokens/tools/temps/spend)
- prompt builder cap + summarization
- alerting sur tokens/request + spend/run
- safe-mode quand budgets hit
Ce n’était pas “le modèle”. C’était nous.
Compromis
- Budgets serrés = plus de “stopped early”.
- Estimation coût approximative = OK.
- Summaries perdent de la nuance.
Quand NE PAS l’utiliser
- Si tu ne peux pas estimer le coût, commence par budgets temps/tools.
- Si c’est déterministe, workflow.
- Si tu as besoin de long contexte, assume un budget plus haut et rends-le explicite.
Checklist (copier-coller)
- [ ] Budgets: steps, tool calls, seconds, USD
- [ ] Tokens in/out par run
- [ ] Estimation spend + alerting
- [ ] Caps sur retries
- [ ] Caps sur dumps HTML/logs
- [ ] Summarize quand over-budget
- [ ] Stop reason explicite (pas de timeouts silencieux)
Config par défaut sûre (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)
Utilisé par les patterns
Pannes associées
Gouvernance requise
Q : J’ai besoin d’un coût exact pour un budget ?
R : Non. Un guard peut être approximatif. Le but est d’éviter les runs runaway.
Q : Je commence par quel budget ?
R : Temps + tool calls, puis tokens/spend quand tu sais mesurer.
Q : Et les requêtes qui demandent plus ?
R : Escalation : confirmation, tier de budget plus haut, ou async avec status.
Q : Je mets un gros budget et basta ?
R : Tu peux. Tu reviendras apprendre via finance et on-call.
Pages liées (3–6 liens)
- Foundations: Limites des LLM et agents · Un agent prêt pour la prod
- Failure: Tool spam loops · Boucle infinie
- Governance: Tool permissions
- Production stack: Production stack