April 20267 min de lectureJohan Bretonneau

On a regardé un agent IA cramer 200 $ à 3 h du mat
Voici Guardian

Un agent RAG coincé dans un retry loop, un context window qui gonflait au-delà de 200K tokens, et le moment où on a compris qu'aucun provider LLM ne t'alerte à temps. Voici le système anti-loop qu'on a buildé pour que ça n'arrive plus jamais.

Mon téléphone a vibré à 3 h 14. C'était le dashboard de facturation d'Anthropic, j'avais mis une alerte seuil à 100 $/jour juste pour me sentir responsable. L'alerte disait que je venais de la dépasser.

J'étais déjà à 143 $. Le temps d'ouvrir mon laptop, j'étais à 167 $. Le temps de tuer le process, j'étais à 201 $.

Un agent. Trois heures. Deux cents dollars.

Voici ce qui s'est passé, pourquoi aucun filet de sécurité LLM existant ne l'a attrapé, et ce qu'on a buildé pour que ça n'arrive plus jamais, ni à nous, ni à personne utilisant HiWay2LLM.

La timeline

2 h 47. Un job planifié se déclenche sur notre agent de recherche interne. C'est un pipeline RAG qui lit un batch de documents, extrait des insights, et écrit un résumé en base. Run nocturne normal. Coût attendu : environ 3 $.

2 h 49. Le premier document qu'il charge est mal formé, un PDF de 800 pages qui a été OCR-isé en charabia, mais avec un pattern de caractères spécifique qui a déclenché la boucle de raisonnement de l'agent. L'agent décide qu'il doit "relire le document" pour comprendre.

2 h 52. Il relit le document. Chaque relecture envoie le context complet de 800 pages à Claude Opus. Chaque appel : ~180 000 tokens. À 15 $/M en input, ça fait 2,70 $ par appel. L'agent tape maintenant toutes les 40 secondes.

3 h 01. L'agent tombe sur une erreur de tool use. Son résultat d'outil revient vide. L'agent "décide" de retry. Le retry renvoie tout le context. Plus le résultat d'outil précédent. Plus le nouveau résultat d'outil. Le context est maintenant à 210 000 tokens par appel.

3 h 14. Mon téléphone vibre. Je viens de dépenser 143 $ de mon propre fric sur un agent qui relit du charabia OCR en boucle. L'email d'alerte Anthropic a, très serviablement, 9 minutes de retard au moment où je le vois.

3 h 16. Je me SSH en et tue le process à la main. Dégâts finaux : 201 $.

Trois heures de sommeil, disparues. Deux cents balles, disparues. Et le plus flippant ? Si mon téléphone avait été sur silence, ça aurait tourné jusqu'à 9 h quand je me serais réveillé. À ce rythme, c'est encore 600 $.

Le pattern sous-jacent

Ce n'est pas un cas isolé. Dès que tu bosses sur des apps LLM à n'importe quelle échelle, tu vois la même classe de problème encore et encore :

  • Un agent avec function calling qui boucle sur une erreur d'outil parce qu'il "ne comprend pas" pourquoi l'outil a échoué.
  • Un chat dont le context grossit sans fin parce que personne n'a plafonné la longueur de conversation.
  • Un job batch qui retry sur un 429 transitoire, mais chaque retry est plus lourd que le précédent.
  • Une instance de dev laissée à tourner la nuit sur une clé API partagée, personne ne regarde.
  • Un health check (celui qui nous a cramé 40 $/jour dans notre tout premier post) qui tire toutes les 30 minutes contre Opus.

Le fil rouge n'est pas "le code avait un bug". Le fil rouge c'est qu'aucune couche entre ton code et le provider ne surveille les patterns suspects en temps réel. Le provider voit des appels API individuels. Ton application voit des boucles d'agent individuelles. Personne ne voit l'ensemble.

Pourquoi les solutions existantes ne marchent pas

Tu te dirais que les alertes de facturation côté provider gèrent ça. Non, et voici pourquoi :

Elles sont basées sur des seuils, pas sur des taux. Anthropic m'a envoyé un email quand j'ai dépassé 100 $. OK. Mais au moment où je lis l'email, je suis à 167 $. À 50 $/heure de burn, les seuils ne servent pas, tu as besoin d'alertes de taux, et les providers n'en proposent pas.

Elles sont email-only. Tu ne peux pas câbler une alerte facturation à un kill-switch. Au mieux, elles préviennent un humain. Si l'humain n'est pas réveillé, le burn continue.

Elles sont à grain trop gros. Tu as un seuil par compte. Tu ne peux pas dire "alerte-moi si une conversation dépasse 5 $" ou "tue toute requête avec un context au-delà de 180K tokens".

Elles sont post-hoc. Le système de facturation agrège l'usage sur des fenêtres de 15 minutes, pas en temps réel. Au moment où il décide que tu as dépassé un seuil, tu as déjà 15 minutes de burn au-delà.

Donc si tu veux attraper une dépense en roue libre en temps réel, tu dois le construire toi-même. Ou le mettre dans la couche infra.

Ce que fait Guardian

Guardian, c'est le système qu'on a construit après cet incident de 3 h. Il est assis dans le proxy HiWay2LLM et inspecte chaque requête avant qu'elle n'atteigne le provider amont. Il cherche quatre catégories spécifiques d'embrouilles :

1. Détection de boucles via fingerprinting des requêtes. Chaque requête reçoit une empreinte, un hash du message utilisateur, du system prompt, et des derniers tours de conversation (3 derniers). Si la même empreinte apparaît plus de N fois dans une fenêtre, on bloque.

Dans notre incident de 3 h, l'agent envoyait le même prompt "relis le document et extrais des insights" avec un context légèrement plus long à chaque fois. Le message utilisateur était identique. Le fingerprinting l'attrape au 3e appel.

Toggleable. Par défaut : 5 empreintes identiques en 5 minutes = block.

2. Throttling de la taille du context. On compte les tokens à l'entrée (en utilisant le tokenizer du provider amont pour être précis). Trois seuils :

  • À 50K tokens : on log un warning dans le dashboard utilisateur.
  • À 100K tokens : on throttle, backoff exponentiel requis entre appels.
  • À 200K tokens : hard block, acquittement manuel requis pour continuer.

Notre agent envoyait des contexts de 210K tokens. Guardian aurait bloqué à 200K, avant même que le premier appel coûteux atteigne Anthropic.

3. Détection de zombie agents. Si une clé API tire des requêtes hors des heures de bureau configurées, sans signal d'interaction humaine (pas de tokens de session UI, pas de headers interactifs), Guardian flag. Tu opt-in par clé : "cette clé est pour le batch nocturne, elle a le droit de tirer à 3 h" ou "cette clé est pour mon chatbot, si elle tire à 3 h y'a un problème".

4. Alerting sur spikes de coût. On tracke le burn horaire roulant par clé. Si l'heure courante est 3× la moyenne glissante sur 24 h, Guardian peut :

  • Envoyer un webhook (Slack, PagerDuty, ton propre endpoint).
  • Auto-throttler la clé à N requêtes par minute.
  • Hard-killer toutes les requêtes suivantes jusqu'à réactivation.

Les trois actions sont indépendamment toggleables. Tu choisis ton niveau de paranoïa.

Un aperçu du fingerprinting

La fonction de hash est plus simple que tu pourrais croire. Voici en gros ce qu'on fait :

function fingerprintRequest(req: LLMRequest): string {
  const lastThreeTurns = req.messages.slice(-3);
  const systemPrompt = req.messages.find(m => m.role === "system")?.content ?? "";

  const normalized = {
    system: systemPrompt.trim().slice(0, 500),
    turns: lastThreeTurns.map(m => ({
      role: m.role,
      content: m.content.trim().slice(0, 1000),
    })),
    model: req.model,
  };

  return sha256(JSON.stringify(normalized)).slice(0, 16);
}

Deux trucs qui comptent :

  • On ne fingerprint pas tout le context. Une conversation d'agent accumule des tokens dans le temps, donc une empreinte full-context ne matcherait jamais deux fois. On fingerprint l'intention (dernier tour utilisateur + system + queue), qui est stable même quand la conversation grossit.
  • On normalise agressivement. Trim des espaces, slice à une longueur fixe. Sinon un timestamp ou un session ID invaliderait l'empreinte et on raterait la boucle.

C'est une fonction de 40 lignes. C'est toute la chose. Le boulot c'était de trouver quelles parties fingerprinter, pas la crypto.

Pourquoi c'est à sa place dans la couche infra

Une protection comme ça ne marche vraiment que si elle est devant chaque requête, avec la visibilité sur toute la forme du trafic — pas cousue dans le chemin retry de chaque app. Un routeur partagé est l'endroit naturel :

  • La déduplication a besoin d'un état global sur tous les clients.
  • Les maths de burn-rate n'ont de sens que quand tu vois toute la dépense.
  • Les kill-switches doivent pouvoir interrompre les appels en vol, pas juste throttler le suivant.
  • Les seuils évoluent avec les prix des modèles ; tu ne veux pas que ce tuning soit éparpillé entre les apps.

Tu peux tout à fait construire une version étroite dans une seule app. Des équipes le font, et ça tient jusqu'à ce que la forme du trafic change. Déplacer le sujet d'une couche en-dessous, c'est chaque app sur HiWay2LLM qui hérite de la protection sans écrire une ligne.

Le fix qui a tenu

Après avoir déployé Guardian en interne, on a rejoué l'incident de 3 h contre le nouveau proxy en staging. Guardian a bloqué au 3e appel. Dégâts totaux dans le replay simulé : 8,10 $ au lieu de 201 $.

On n'a plus eu d'incident d'agent en roue libre depuis. Pas un seul. Le premier mois, Guardian a bloqué :

  • 3 boucles de health-check (coût évité : 340 $)
  • 12 events de context bloat au-delà de 150K tokens
  • 1 zombie agent sur un env de dev qui a commencé à retry à 2 h un samedi

C'est du vrai argent économisé sur de vrais patterns, en un mois, dans une seule boîte. Une fois que tu l'as en marche, tu comprends que toute équipe devrait avoir ça. Aucun provider ne l'offre. Aucun SaaS revendeur ne l'offre. Tu peux soit le construire, soit l'avoir.

Si tu fais tourner des appels LLM en production, tu as besoin de cette couche. Pas éventuellement, maintenant. Le call de 3 h du mat que tu ne veux pas recevoir, c'est celui dont tu ne savais pas qu'il était possible.

Commencer à économiser →

Pas de carte bancaire requise


Lectures liées : Comment on a réduit nos coûts LLM de 85 %, Les maths cachées du pricing LLM.

Partager

LinkedInXEmail

Cet article t'a servi ?

Commentaires

Sois le premier à commenter.