Le problème
La demande paraît simple : vérifier le paiement de plusieurs commandes et fournir un résumé court.
Dans les traces, c'est autre chose : en 14 minutes, un run a fait 63 étapes, 41 appels d'outil et a consommé environ 11,80 $. Pour ce type de tâche, on est généralement autour de 0,20-0,30 $.
Il n'y a pas de crash évident : une partie des appels retourne 200, l'agent "fonctionne" formellement,
mais la file de runs grossit, et cost_per_run sort du budget dès les premières minutes.
Le système ne s'effondre pas brutalement.
Il gonfle simplement la facture et la file de runs, jusqu'à sortir des limites de budget.
Analogie : imagine un taxi dont le compteur n'est jamais remis à zéro entre deux courses. La voiture roule, les passagers changent, mais le montant ne fait qu'augmenter. L'explosion de budget côté agents ressemble exactement à ça : le travail semble avancer, mais les coûts augmentent plus vite que la valeur.
Pourquoi ça arrive
L'explosion de budget vient rarement d'un appel unique très cher, mais plutôt de l'absence de contrôle strict des coûts cumulés en runtime.
En production, c'est généralement ce mélange :
- le contexte et la history grossissent turn après turn, donc chaque nouvel appel modèle coûte plus cher ;
- une étape d'agent peut déclencher un fan-out d'outils, et le coût se multiplie ;
- les retries vivent dans plusieurs couches et transforment un incident court en vague de coûts ;
- il n'existe pas de budget gate unique pour les étapes, tokens, appels d'outil, temps et USD ;
- sans stop reasons ni métriques de coût, on détecte souvent l'incident seulement à la facture.
Dans les traces, on voit une hausse simultanée de prompt_tokens, tool_calls et retry_attempts,
où chaque étape suivante coûte plus cher que la précédente.
Sans budget gate au niveau runtime, chaque nouvelle étape aggrave l'incident.
Pannes les plus fréquentes
En production, on voit surtout quatre schémas récurrents d'explosion de budget.
Croissance cumulative du contexte (Context cost creep)
Le prompt grossit sans priorités : history, retrieval et sortie des outils s'ajoutent presque sans limites.
Cause typique : pas de max_prompt_tokens, pas de caps de sources, pas de niveau de summarization.
Fan-out d'outils gonflé (Tool fan-out spike)
Une seule étape déclenche trop d'appels externes, souvent en parallèle. Même sans erreur, cela augmente brutalement le coût du run.
Cause typique : pas de caps par outil, pas de bounded fan-out.
Amplification des retries entre couches
Les retries sont faits à la fois par runtime, tool gateway et SDK. Une courte dégradation de dépendance devient une longue vague de coûts répétés.
Cause typique : la policy de retry n'est pas centralisée dans un seul endroit.
Effet boule de neige dans la file (Queue cost snowball)
Les runs longs et chers occupent les workers, le backlog grossit, et les nouveaux runs deviennent aussi plus chers à cause de l'attente et des timeouts.
Cause typique : absence de max_seconds, max_steps stricts et de stop reason pour overflow budget.
Comment détecter ces problèmes
L'explosion de budget se voit bien via la combinaison des métriques coût, runtime et file d'attente.
| Métrique | Signal d'explosion de budget | Action à prendre |
|---|---|---|
cost_per_run | hausse brutale du coût d'un run | activer max_usd et vérifier le budget gate avant chaque étape |
tool_cost_share | part des coûts outils disproportionnée | limiter le fan-out et ajouter des caps par outil |
retry_attempts_per_run | beaucoup de répétitions sur les mêmes appels | centraliser les retries dans le tool gateway et ajouter un retry budget |
prompt_tokens_per_run | hausse continue des tokens sans gain de qualité | caps sur les sources de contexte + summarization |
queue_backlog | la file augmente avec des runs longs et chers | limiter max_seconds, arrêter les runs runaway de manière contrôlée |
Comment distinguer une explosion de budget d'une tâche vraiment coûteuse
Toute tâche coûteuse n'est pas un incident. La question clé : est-ce que les coûts supplémentaires apportent un gain de qualité prévisible.
C'est normal si :
- le coût augmente avec la précision ou la couverture d'une tâche complexe ;
- il existe un profil de coût contrôlé pour cette classe de requêtes ;
cost_per_successreste dans les objectifs d'unit economics.
C'est dangereux si :
- le coût augmente plus vite que le taux de succès ;
- les mêmes retries et signatures d'outils se répètent sans nouveau signal ;
- le budget "explose" sans changement de complexité de tâche ou de SLA.
Comment stopper ce type de panne
En pratique, le pattern est le suivant :
- définir des execution budgets :
max_steps,max_seconds,max_prompt_tokens,max_tool_calls,max_usd; - vérifier le budget gate à chaque étape, pas uniquement à la fin du run ;
- centraliser les retries dans un tool gateway unique et couper les erreurs non retryable ;
- en cas de dépassement, renvoyer stop reason, partial/fallback et alerte.
Guard minimal pour contrôler le budget :
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
C'est un guard de base.
En production, on le complète généralement avec des limites par outil, du backoff + jitter
et des budgets séparés pour la partie modèle et la partie outils.
check() est appelé après chaque étape avant de planifier l'action suivante.
on_model_call(...) et on_tool_call(...) mettent à jour l'usage juste après l'appel réel,
pour que la stop reason reflète le coût réel du run.
Où c'est implémenté dans l'architecture
En production, le contrôle de l'explosion de budget est presque toujours réparti sur trois couches du système.
Agent Runtime gère les execution budgets, les stop reasons et la terminaison contrôlée des runs. C'est ici que le budget devient une règle, pas un souhait.
Tool Execution Layer contrôle le fan-out, les retries, les timeouts et le coût des appels externes. Si les retries sont répartis entre plusieurs couches, les coûts se multiplient presque toujours.
Memory Layer décide ce qui entre dans le prompt et ce qui reste en mémoire longue. Sans cette couche, le coût en tokens augmente régulièrement même sans tâches plus complexes.
Auto-vérification
Vérification rapide avant release. Coche les points et regarde le statut ci-dessous.
C'est un sanity-check court, pas un audit formel.
Progression: 0/7
⚠ Il y a des signaux de risque
Il manque des contrôles de base. Fermez les points clés de la checklist avant release.
FAQ
Q : Faut-il un calcul de coût exact pour mettre un budget guard ?
R : Non. Au départ, une estimation conservatrice suffit. Le but n'est pas la comptabilité, mais l'arrêt précoce des runs runaway.
Q : Avec quelle limite commencer ?
R : Commence avec un max_usd et un max_seconds conservateurs, puis augmente uniquement là où un gain de qualité est démontré.
Q : Que faire si le budget est épuisé pour une demande importante ?
R : Renvoyer une stop reason explicite, montrer un résultat partial et proposer une escalade contrôlée (tier plus élevé ou review manuel).
Q : Où placer les retries pour éviter l'inflation des coûts ?
R : Dans un seul choke point, généralement le tool gateway. Si les retries vivent dans plusieurs couches, l'explosion de budget devient presque inévitable.
L'explosion de budget ressemble rarement à un crash bruyant. C'est une dégradation financière lente, visible surtout dans les métriques et la comparaison au baseline. Les agents de production ont donc besoin non seulement de meilleurs modèles, mais aussi d'un contrôle strict de l'execution budget.
Pages liées
Si ce problème apparaît en production, ces pages sont aussi utiles :
- Pourquoi les agents IA échouent - carte générale des pannes en production.
- Token overuse - comment la croissance du contexte devient croissance des coûts.
- Tool spam - comment les appels d'outils répétés gonflent le budget.
- Tool failure - comment les vagues d'erreurs et retries augmentent le coût des runs.
- Agent Runtime - où placer execution budgets et stop reasons.
- Tool Execution Layer - où tenir retries, fan-out et cost gates.