Lección 7 — Démo et marché

Du rouge au vert, mesuré

8 min de lectura

Este contenido aún no está traducido. Se muestra la versión original.

Du rouge au vert, mesuré

« L'agent a corrigé le bug » ne suffit pas à décider d'un déploiement. Votre directeur technique vous demandera combien de tours, combien de tokens, combien d'euros, avec quelle variance d'une exécution à l'autre, et comment vous savez que c'est vraiment corrigé. Cette leçon assemble les pièces et produit ces chiffres sur une tâche réelle.

La tâche

Le dépôt d'exemple (files/exemple/) est le module de facturation d'un grossiste fictif. Son README fixe trois règles : remise de 5 % à partir de 50 unités et de 10 % à partir de 100, arrondi commercial au centime (0,125 € donne 0,13 €), TVA de 20 % sur le HT remisé. Deux bugs réalistes y sont cachés :

  • remises.py compare quantite > seuil : un client qui commande exactement 50 unités n'a pas sa remise ;
  • montants.py appelle quantize(CENTIME) sans mode d'arrondi : decimal applique alors l'arrondi bancaire (ROUND_HALF_EVEN), et 0,125 devient 0,12.

Le second est le plus intéressant : le code semble correct, et seule la lecture conjointe de la règle métier et de la documentation de decimal le révèle. Quatre tests sur six échouent.

flowchart LR
  A["make e2e"] --> B["copie du dépôt dans sorties/depot, git init"]
  B --> C["pytest par le harness : rouge"]
  C --> D["agent : Gemini + 5 outils, mode auto"]
  D --> E["pytest par le harness, indépendamment de l'agent"]
  E -->|"vert et statut terminé"| F["code de sortie 0"]
  E -->|"sinon"| G["code de sortie 1"]
  D --> H["trajectoire.jsonl, trajectoire.html"]

Le point clé est l'étape E : le harness relance les tests lui-même. Le compte rendu de l'agent (« les 6 tests passent ») n'est jamais cru sur parole.

Résultat obtenu

Exécution réelle de make e2e, 2026-10-15 à 20 h 49, gemini-3.8-flash, mode auto :

Sortie réelle de make e2e
Avant  : 4 failed, 2 passed in 0.05s
Après  : 6 passed in 0.02s
  statut                terminé
  tours                 17
  appels d'outils       16
  refus de permission   2
  points de reprise     2
  tokens en entrée      60 661
  tokens en sortie      2 321
  coût estimé           0.0542 $ (tarif 0.75 / 3.75 $ par M)
  durée                 45.1 s

Diff final, identique dans les exécutions 1, 3 et 4 dont le journal a été conservé : >= dans remises.py, rounding=ROUND_HALF_UP dans montants.py. Aucun test modifié.

Cinq exécutions, même tâche

Exécution (2026-10-15)ToursAppelsRefusCompactionEntréeSortieCoûtDurée
1 · budget 24 00016151056 8261 7650,0492 $44,0 s
2 · budget 5 00013121134 2902 1710,0339 $38,7 s
3 · budget 5 00013121134 4182 0860,0336 $39,0 s
4 · make e2e final17162060 6612 3210,0542 $45,1 s
5 · contrôle des codes de sortie16152051 5781 6830,0450 $34,1 s

Ce que ces chiffres disent, avec la prudence qu'impose un échantillon de cinq :

  • L'entrée domine le coût. Sur l'exécution 4, l'entrée pèse 0,0455 etlasortie0,0087et la sortie 0,0087 : 84 % du coût vient de l'historique renvoyé à chaque tour. Les leviers sont donc la longueur de l'historique (compaction, troncature) et le nombre de tours, bien avant la verbosité du modèle.
  • La compaction a coûté moins cher ici, environ 0,034 contre0,045aˋ0,054contre 0,045 à 0,054, résumé compris. Mais les trajectoires diffèrent aussi par leur nombre de tours : ne généralisez pas un écart mesuré sur deux exécutions.
  • Les trajectoires varient. 13 à 17 tours pour le même résultat. L'exécution 4 a perdu un tour sur un pytest nu refusé ; les exécutions 1 à 4 ont toutes tenté git diff, refusé. Ajouter git diff à la liste blanche économiserait probablement un ou deux tours par session.
  • Le modèle travaille en série. Un seul appel d'outil par tour dans les exécutions 1 à 4, alors que le harness accepte des appels parallèles. Lire les trois fichiers sources en un tour économiserait deux tours ; une consigne système le suggérant est un levier à mesurer.

Au tarif publié pour gemini-3.8-flash (0,75 enentreˊe,3,75en entrée, 3,75 en sortie par million de tokens jusqu'au 31 décembre 2026, puis 1,50 et7,50et 7,50), la même session coûtera environ deux fois plus à partir de 2027. Le prix est une variable d'environnement (PRICE_IN_PER_M, PRICE_OUT_PER_M) pour cette raison.

Ce qui casse en production

  • L'agent déclare un succès qu'il n'a pas vérifié. Fréquent quand la session s'arrête sur un plafond juste après une édition. Parade : la vérification indépendante de l'étape E, qui conditionne le code de sortie.
  • Tests instables. Un test qui échoue une fois sur dix fait conclure à l'agent que sa correction est fausse ; il défait du code correct. Parade : stabiliser la suite avant de la confier à un agent, ou relancer les échecs une fois avant de les montrer.
  • Quota. Plusieurs agents en parallèle sur la même clé reçoivent des 429. Le harness réessaie avec attente exponentielle (cinq essais, jusqu'à 60 s), puis échoue proprement. En CI, limitez la concurrence plutôt que d'allonger les attentes.
  • Coût de queue. La moyenne est de 0,05 $, mais une session qui tourne en rond jusqu'à MAX_TURNS=25 avec un historique de 20 000 tokens coûte environ dix fois plus. C'est le 90e centile qui fait la facture : mesurez-le (exercice).

La règle de décision

Confiez à un agent en mode auto les tâches dont le critère de réussite est exécutable (une suite de tests, un linter, un build) et vérifié par le harness. Pour une tâche sans critère exécutable, gardez le mode ask et une revue humaine du diff. Avant d'industrialiser, faites tourner la même tâche 20 fois et regardez le taux de réussite, le 90e centile des tours et du coût.

Pratique

Un rapport dit « 6 passed » dans le compte rendu de l'agent, mais make e2e renvoie le code 1. Quelle explication est la plus probable ?

Vous devez diviser par deux le coût moyen par tâche. D'après les mesures de cette leçon, par quoi commencez-vous ?

Exercice. Écrivez stats.py : à partir de plusieurs fichiers trajectoire*.jsonl, affichez le taux de réussite, la médiane et le 90e centile des tours et du coût.

Voir la solution
import statistics, sys
from pathlib import Path
from harness.trajectory import load

ends = [next(e for e in load(Path(p)) if e["event"] == "end") for p in sys.argv[1:]]
ok = [e for e in ends if e["status"] == "terminé"]
def p90(xs): return statistics.quantiles(xs, n=10)[-1] if len(xs) >= 2 else xs[0]
for key in ("turns", "cost_usd"):
    xs = [e[key] for e in ends]
    print(f"{key}: médiane {statistics.median(xs)}  p90 {p90(xs):.4f}")
print(f"réussite : {len(ok)}/{len(ends)}")

« terminé » signifie que le modèle s'est arrêté de lui-même, pas que la suite est verte. Pour un vrai taux de réussite, journalisez aussi le verdict de la vérification indépendante (événement verify dans cmd_demo) et comptez celui-là.

  • Je vérifie le résultat de l'agent indépendamment de ce qu'il affirme
  • Je mesure tours, tokens d'entrée et de sortie, coût et durée pour chaque session
  • Je sais lire d'où vient le coût d'un agent et quel levier actionner
  • Je raisonne sur une distribution d'exécutions, pas sur un seul run
Siguiente