# Je me trompais de coupable

Langue : fr

Identifiant : article:mesurer-cout-agent

Noa Matout

Je croyais savoir ce que mon agent me coûtait. J'ai construit un compteur, et mes hypothèses ne tenaient pas.

J'ai regardé ma facture d'API après quatre jours d'agent personnel, et je me
suis mis à chercher un responsable. Je l'ai trouvé sans effort : une tâche qui
se réveille toutes les quinze minutes pour vérifier s'il y a une urgence.
Quatre-vingt-seize réveils par jour, évidemment que c'était elle.

Elle ne coûtait rien du tout. Voici comment je m'en suis rendu compte, et les
deux autres choses que je croyais savoir et qui étaient fausses aussi.

## Il n'y avait aucune mesure

Premier constat, gênant : mon outil ne conservait aucune trace de consommation.
Ni par tâche, ni par jour, ni du tout. Le fournisseur renvoie pourtant les
compteurs à chaque appel, dans la réponse. Ils étaient simplement jetés.

J'ai donc mis un relais local devant l'API. Un petit programme qui écoute sur un
port, transmet la requête telle quelle, laisse passer la réponse, et note au
passage ce que le fournisseur annonce : tokens d'entrée, tokens de sortie, part
servie par le cache, durée, code de retour. Il ne garde ni les questions, ni les
réponses, ni la clé. Un port par profil d'agent, ce qui suffit à attribuer la
consommation sans jamais lire le contenu.

Une précaution que je recommande si vous faites pareil : ce relais devient un
point de passage obligatoire. S'il tombe, l'agent ne fonctionne plus. Redémarrage
automatique, et le retour arrière doit tenir en une ligne de configuration.

Je l'ai publié, avec ses limites écrites noir sur blanc :
[Consulter le code de mon compteur de consommation](https://github.com/NoaMatout/agent-meter).
Il ne garde que des compteurs : jamais le contenu des échanges, jamais la clé.
Le premier enregistrement du compteur date du <time datetime="2026-09-21T14:34" data-fact-key="article:mesurer-cout-agent:/events/counterStart">21 septembre 2026 à 14 h 34</time>.
Il ne couvre pas les jours précédents.

## Première hypothèse fausse : la fréquence

La tâche des 15 minutes a produit 370 déclenchements en
quatre jours. Sur ces 370, **un seul** a dépassé cinq
secondes. Ce décompte des réveils est distinct des relevés du compteur API.

Elle est construite sur un motif que je recommande maintenant partout : un script
bon marché tourne d'abord, son résultat est comparé à celui du tour précédent, et
le modèle n'est réveillé que si quelque chose a changé. Tant que le détecteur ne
trouve rien, il n'y a pas d'appel. Pas d'appel court, pas d'appel réduit : pas
d'appel.

La fréquence n'est donc pas un coût en soi. Une tâche qui tourne toutes les
15 minutes et une tâche quotidienne coûtent la même chose si toutes les deux
ne réveillent le modèle que quand il y a matière. Ce qui coûte, c'est de faire
juger une machine qui n'a rien à juger.

## Deuxième hypothèse fausse : le catalogue

J'ai mesuré une question triviale, du genre « réponds par le mot A ». Le relevé affichait
14 950 tokens d'entrée. Pour trois mots.

Ce contexte permanent, je l'ai attribué au catalogue de compétences installées :
70 outils, environ 12 familles, dont plusieurs parfaitement
inutiles chez moi. J'en ai retiré une, manifestement inutilisable puisqu'elle
pilote un système d'exploitation que je n'ai pas. J'ai refait exactement la même
mesure.

Le relevé affichait encore 14 950 tokens. Gain : zéro.

J'ai recommencé en retirant les règles de projet, puis en lançant depuis un
dossier neutre, sans aucun fichier d'instructions. Le relevé affichait
14 947 tokens. Ce contexte permanent comprend le prompt système de l'outil et la
description de ses outils. Je ne peux pas les réduire depuis l'extérieur, et
aucun élagage ne m'aurait fait gagner quoi que ce soit.

J'ai remis la famille en place. C'est le point que je tiens à souligner : une
expérience qui revient négative est une expérience réussie. Elle m'a évité de
retirer des fonctions à mon agent en croyant l'optimiser, et de l'expliquer
ensuite par un raisonnement plausible mais faux.

## Ce qui coûte réellement

Trois choses, et aucune n'était dans mes hypothèses.

**Le nombre d'étapes.** Une exécution de mon brief enchaîne 13 appels au modèle. À
chaque étape, tout le contexte accumulé repart. 511 963 tokens
d'entrée pour une seule exécution. Le chiffre paraît énorme et il l'est : c'est
le contexte multiplié par le nombre d'allers-retours.

**Ce qui invalide le cache.** Le fournisseur facture 10 fois moins cher les
tokens déjà vus, à condition que le début de la requête ne change pas. Sur des
appels répétés, je mesure 98 % de cache. Sur un appel
juste après avoir modifié le fichier de configuration de l'agent, 31 %. Autrement dit : éditer la configuration de son agent coûte plus cher
que de le faire tourner. J'ai passé un week-end à corriger ses instructions ligne
par ligne, et c'est probablement la chose la plus chère que j'aie faite.

**Le travail déterministe confié au modèle.** Mon brief demandait de parcourir
quatre boîtes mail, de lister les dossiers, de chercher les messages récents,
compte par compte. Environ 15 allers-retours pour une opération sans aucune
décision. Je l'ai remplacée par un script qui rend le même inventaire en une
exécution, 1 000 tokens environ, format fixe. Le modèle ne fait plus la collecte, il fait
le tri, qui est la seule chose pour laquelle il est utile.

## Ce que les corrections ont donné

J'ai changé trois choses. L'effort de raisonnement, fixé par tâche au lieu d'un
réglage global : faible pour du tri, moyen là où il y a du jugement. La collecte
des messages, sortie du modèle et confiée à un script qui rend un inventaire en
une exécution. Et le profil de voix, relu une fois par semaine au lieu de chaque
matin.

Voici d'abord l'historique des durées totales des briefs du matin.
Ces durées ne sont pas le temps modèle cumulé mesuré par le compteur.

| Brief matinal | Durée totale |
| --- | ---: |
| <time datetime="2026-09-18T07:30" data-fact-key="article:mesurer-cout-agent:/events/morning18">18 septembre 2026 à 7 h 30</time> | 181,7 s |
| <time datetime="2026-09-19T07:30" data-fact-key="article:mesurer-cout-agent:/events/morning19">19 septembre 2026 à 7 h 30</time> | 182,5 s |
| <time datetime="2026-09-20T07:30" data-fact-key="article:mesurer-cout-agent:/events/morning20">20 septembre 2026 à 7 h 30</time> | 181,7 s |
| <time datetime="2026-09-21T07:30" data-fact-key="article:mesurer-cout-agent:/events/morning21">21 septembre 2026 à 7 h 30</time> | 241,6 s |
| <time datetime="2026-09-22T07:30" data-fact-key="article:mesurer-cout-agent:/events/morning22">22 septembre 2026 à 7 h 30</time> | 120,8 s |

Les trois corrections précèdent la dernière ligne. Cet historique
ne suffit pas à isoler leur effet : le volume de courrier varie aussi.

## Deux exécutions, deux horaires

Côté consommation, je n'ai que deux exécutions mesurées. Elles ont eu lieu
le <time datetime="2026-09-21T14:56" data-fact-key="article:mesurer-cout-agent:/events/beforeRun">21 septembre 2026 à 14 h 56</time> avant corrections, puis le <time datetime="2026-09-22T07:31" data-fact-key="article:mesurer-cout-agent:/events/afterRun">22 septembre 2026 à 7 h 31</time> après.
Ce ne sont pas deux briefs lancés à la même heure.

| Mesure | Avant | Après |
| --- | ---: | ---: |
| Appels au modèle | 13 | 12 |
| Tokens d'entrée brute | 511 963 | 383 913 |
| Tokens lus en cache | 469 504 | 348 288 |
| Part lue en cache | 91,7 % | 90,7 % |
| Tokens de sortie | 20 655 | 12 453 |
| Temps modèle cumulé | 97,2 s | 62,1 s |

L'entrée brute comprend les lectures de cache. Ce n'est pas un montant facturé.
Après corrections, les 24 requêtes enregistrées comprennent
12 appels au modèle et 12 sondes de capacité
sans token. Je ne compare pas ce total aux seuls appels au modèle d'avant.

## Mon premier relevé était faux

Mon premier relevé donnait 392 666 tokens d'entrée. J'avais
additionné tout ce qui passait dans une fenêtre horaire, sondes et autre tâche
comprises. Je m'en suis aperçu en ajoutant l'attribution par tâche : elle
rattache chaque appel au travail qui le demandait au lieu de le deviner à l'heure.

La seule exécution concernée représente 383 913 tokens d'entrée brute.
La correction retire 2,2 % du premier total.
Je ne peux pas écrire un article sur la mesure et laisser une attribution par
approximation quand j'ai depuis l'outil qui la mesure vraiment.

## La limite de la comparaison

Les heures diffèrent, et les volumes de courrier aussi : 17
messages avant corrections, avec un arriéré de week-end, contre
cinq après. Une partie de la baisse vient donc du courrier.
Je ne peux pas isoler l'effet de mes corrections avec ces deux exécutions.

L'entrée brute baisse de 25 %, la sortie de
39,7 % et le temps modèle cumulé de
36,1 %. Ce sont des écarts observés, pas une mesure
du gain imputable aux corrections. Je ne peux pas attribuer la baisse de la
sortie au seul effort de raisonnement.

Le nombre d'appels au modèle a lui aussi changé : 13
avant, 12 après. Il y a donc
un appel en moins dans l'exécution observée.
Je ne peux plus écrire que mes corrections n'ont supprimé aucune étape,
ni conclure que chaque étape a été allégée sans l'avoir mesurée séparément.
Le gisement suivant reste de décider quel travail mérite un aller-retour avec
un modèle.

## Ce que j'aurais dû faire en premier

Mesurer. Avant d'avoir des hypothèses.

J'ai perdu du temps sur deux théories confiantes et fausses, alors que le
compteur a tranché en quelques minutes une fois construit. Et il a produit des
résultats que je n'aurais pas devinés : à la date de rédaction,
517 sondes de découverte parmi 814
requêtes enregistrées, soit 63,5 %. Ce périmètre est celui
du relevé à la rédaction, pas celui de la seule exécution du brief.
Ces sondes renvoient un statut 404 ou 200 et ne consomment aucun token.
Elles existaient depuis le début sans que rien ne les signale.

Il y a une règle générale là-dedans, et elle dépasse largement les agents. Quand
un système a un comportement qu'on ne peut pas observer, on le remplace par une
histoire. L'histoire est cohérente, elle désigne un coupable plausible, et elle
peut être fausse. La seule façon d'en sortir est de rendre
le comportement observable, même grossièrement, même avec un programme de cent
lignes écrit en une heure.

## La forme, pas le montant

Je n'avais pas de problème de coût. J'avais une **forme de consommation** qui
aurait mal grossi : à dix tâches de plus, chacune enchaînant quinze étapes dont
douze sans décision, le montant aurait cessé d'être anecdotique. C'est le bon
moment pour corriger la forme, précisément parce qu'il ne fait pas encore mal.

## Périmètre de la mesure

beforeInput : 511 963 tokens d’entrée brute. Exécution avant corrections : entrée totale, lectures de cache comprises.

beforeOutput : 20 655 tokens de sortie. Exécution avant corrections : sortie annoncée par le fournisseur.

beforeCached : 469 504 tokens lus en cache. Part de l’entrée brute de l’exécution avant corrections.

beforeModelCalls : 13 appels au modèle. Exécution avant corrections ; ne compte pas les sondes de capacité.

beforeModelSeconds : 97,2 secondes de temps modèle. Temps modèle cumulé pour l’exécution avant corrections, distinct de la durée complète du brief.

afterInput : 383 913 tokens d’entrée brute. Une seule exécution après corrections, attribuée par tâche, lectures de cache comprises.

afterOutput : 12 453 tokens de sortie. Une seule exécution après corrections, attribuée par tâche.

afterCached : 348 288 tokens lus en cache. Part de l’entrée brute de l’exécution après corrections.

afterModelCalls : 12 appels au modèle. Exécution après corrections ; ne compte pas les sondes de capacité.

afterProbes : 12 sondes de capacité. Requêtes de l’exécution après corrections, sans token consommé.

wrongWindowInput : 392 666 tokens d’entrée brute. Ancienne attribution erronée : toute la fenêtre horaire, autre tâche et sondes comprises.

discoveryProbes : 517 sondes de découverte. Sondes dans le relevé à la date de rédaction, pas dans la seule exécution du brief.

recordedRequests : 814 requêtes enregistrées. Dénominateur du relevé des sondes à la date de rédaction.

beforeMessages : 17 messages. Courrier traité avant corrections selon le brouillon, avec un arriéré de week-end.

afterMessages : 5 messages. Courrier traité après corrections selon le brouillon.

observedWakeups : 370 déclenchements. Décompte des réveils fourni dans le brouillon, distinct des compteurs API.

minimalPromptInput, prunedPromptInput : 14 950 tokens d’entrée. Demande minimale avant retrait d’une famille d’outils, relevée dans le brouillon. Même demande après retrait d’une famille d’outils, relevée dans le brouillon.

neutralPromptInput : 14 947 tokens d’entrée. Demande depuis un dossier neutre sans règles de projet, relevée dans le brouillon.

repeatedCachePercent : 98 % de cache. Relevé sur des appels répétés dans l’expérience distincte du brouillon.

editedCachePercent : 31 % de cache. Relevé après modification de la configuration dans l’expérience distincte du brouillon.

inventoryTokens : 1 000 tokens environ. Inventaire produit par le script de collecte selon le brouillon.

duration18, duration20 : 181,7 secondes. Durée totale du brief matinal du premier jour décrit dans le brouillon ; pas temps modèle cumulé. Durée totale du troisième brief matinal dans le brouillon.

duration19 : 182,5 secondes. Durée totale du brief matinal du jour suivant dans le brouillon.

duration21 : 241,6 secondes. Durée totale du brief matinal précédent les corrections dans le brouillon.

duration22 : 120,8 secondes. Durée totale du brief matinal après les corrections dans le brouillon.

afterModelSeconds : 62,1 secondes de temps modèle. Temps modèle cumulé après corrections indiqué dans le brouillon, distinct de la durée totale du brief.

beforeCachePercent : 91,7 % de cache. Part des tokens d’entrée brute déjà lus en cache avant corrections ; arrondi à une décimale.

afterCachePercent : 90,7 % de cache. Part des tokens d’entrée brute déjà lus en cache après corrections ; arrondi à une décimale.

inputReductionPercent : 25 % de baisse observée. Écart relatif d’entrée brute entre les exécutions ; ne mesure pas un effet causal des corrections.

outputReductionPercent : 39,7 % de baisse observée. Écart relatif de sortie entre les exécutions ; heures et volumes de courrier différents.

discoveryPercent : 63,5 % de sondes. Part des sondes dans les requêtes enregistrées à la date de rédaction ; arrondi à une décimale.

modelCallsRemoved : 1 appel au modèle en moins. Différence du nombre d’appels au modèle entre les deux exécutions observées.

windowOvercountPercent : 2,2 % de surestimation du premier relevé. Écart rapporté au premier relevé erroné de la fenêtre horaire, arrondi à une décimale.

afterRequests : 24 requêtes enregistrées. Appels au modèle et sondes de capacité de l’exécution après corrections.

modelTimeReductionPercent : 36,1 % de baisse observée. Écart relatif du temps modèle cumulé ; ne mesure pas un effet causal des corrections.
