Explosion de budget : quand les coûts d’un agent explosent

Une explosion de budget apparaît quand l’exécution incontrôlée fait grimper rapidement les coûts d’API et de modèle. Comment l’éviter.
Sur cette page
  1. Le problème
  2. Pourquoi ça arrive
  3. Pannes les plus fréquentes
  4. Croissance cumulative du contexte (Context cost creep)
  5. Fan-out d'outils gonflé (Tool fan-out spike)
  6. Amplification des retries entre couches
  7. Effet boule de neige dans la file (Queue cost snowball)
  8. Comment détecter ces problèmes
  9. Comment distinguer une explosion de budget d'une tâche vraiment coûteuse
  10. Comment stopper ce type de panne
  11. Où c'est implémenté dans l'architecture
  12. Auto-vérification
  13. FAQ
  14. Pages liées

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 :

  1. le contexte et la history grossissent turn après turn, donc chaque nouvel appel modèle coûte plus cher ;
  2. une étape d'agent peut déclencher un fan-out d'outils, et le coût se multiplie ;
  3. les retries vivent dans plusieurs couches et transforment un incident court en vague de coûts ;
  4. il n'existe pas de budget gate unique pour les étapes, tokens, appels d'outil, temps et USD ;
  5. 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étriqueSignal d'explosion de budgetAction à prendre
cost_per_runhausse brutale du coût d'un runactiver max_usd et vérifier le budget gate avant chaque étape
tool_cost_sharepart des coûts outils disproportionnéelimiter le fan-out et ajouter des caps par outil
retry_attempts_per_runbeaucoup de répétitions sur les mêmes appelscentraliser les retries dans le tool gateway et ajouter un retry budget
prompt_tokens_per_runhausse continue des tokens sans gain de qualitécaps sur les sources de contexte + summarization
queue_backlogla file augmente avec des runs longs et cherslimiter 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_success reste 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 :

  1. définir des execution budgets : max_steps, max_seconds, max_prompt_tokens, max_tool_calls, max_usd ;
  2. vérifier le budget gate à chaque étape, pas uniquement à la fin du run ;
  3. centraliser les retries dans un tool gateway unique et couper les erreurs non retryable ;
  4. en cas de dépassement, renvoyer stop reason, partial/fallback et alerte.

Guard minimal pour contrôler le budget :

PYTHON
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 :

⏱️ 9 min de lectureMis à jour 12 mars 2026Difficulté: ★★☆
Implémenter dans OnceOnly
Guardrails for loops, retries, and spend escalation.
Utiliser dans 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 }
Intégré : contrôle en productionOnceOnly
Ajoutez des garde-fous aux agents tool-calling
Livrez ce pattern avec de la gouvernance :
  • Budgets (steps / plafonds de coût)
  • Kill switch & arrêt incident
  • Audit logs & traçabilité
  • Idempotence & déduplication
  • Permissions outils (allowlist / blocklist)
Mention intégrée : OnceOnly est une couche de contrôle pour des systèmes d’agents en prod.
Exemple de policy (concept)
# Example (Python — conceptual)
policy = {
  "budgets": {"steps": 20, "seconds": 60, "usd": 1.0},
  "controls": {"kill_switch": True, "audit": True},
}

Auteur

Nick — ingénieur qui construit une infrastructure pour des agents IA en production.

Focus : patterns d’agents, modes de défaillance, contrôle du runtime et fiabilité des systèmes.

🔗 GitHub: https://github.com/mykolademyanov


Note éditoriale

Cette documentation est assistée par l’IA, avec une responsabilité éditoriale humaine pour l’exactitude, la clarté et la pertinence en production.

Le contenu s’appuie sur des défaillances réelles, des post-mortems et des incidents opérationnels dans des systèmes d’agents IA déployés.