De 42% à 78% : journal d'itération d'un agent IA en prod
3 semaines, 108 runs, 7 modèles testés. L'histoire complète d'un agent qui passe de 42% à 78%. Les victoires, les 7 échecs d'affilée, et le changement qui a tout débloqué.
Dernier article de la série. Les cinq précédents couvraient les briques : pourquoi c'est un problème d'IA, le prompt engineering qui a pas marché, pourquoi construire un benchmark avant de toucher au prompt, 7 modèles sur la même tâche, et Claude pour auditer l'agent Gemini.
Cet article, c'est la timeline qui relie tout ça. Le récit jour par jour du passage de "ça marche à peu près" à "c'est assez solide pour shipper". Trois semaines, 108 runs de benchmark, et pas mal de git revert.
Semaine 0 : Construire sur du sable
Le truc, c'est que j'ai commencé à benchmarker avant d'avoir un benchmark digne de ce nom. Je piochais des codes-barres au pif dans notre base de 69 millions de produits, je lançais l'agent dessus, et je comparais avec une ground truth bricolée à la main.
Une trentaine de runs sur Gemini Flash, Haiku, GPT-4.1 et Mistral. Utile pour un tri grossier entre modèles. Mais toujours le même hic : quand un run régressait, impossible de savoir si l'agent avait régressé ou si ma ground truth était bidon. Certains pays attendus venaient de l'agent lui-même sur des runs précédents. C'était comme si je notais l'élève avec ses propres copies.
Il me fallait une ground truth solide. Du coup c'est là que j'ai commencé le golden dataset.
Jour 1 : Le premier chiffre
Golden dataset : 19 items. Chacun avec un pays de fabrication vérifié, un niveau de confiance, et une note de difficulté. Petit, mais les labels étaient carrés.
Premier benchmark : Gemini 2.5 Flash, prompt v1. Résultat : 8/19 = 42%.
Ce chiffre a tout rendu concret. Plus le flou du "ça a l'air de marcher sur quelques exemples". 42%. Même pas la moitié. Et 4 items en fausse confiance : l'agent disait "vérifié : fabriqué en France" et se plantait.
J'ai creusé les 4 cas FC le soir même. Trois partageaient le même pattern : l'agent lisait un snippet disant "Fabriqué en France" qui concernait un autre produit sur la même page. Le quatrième, c'était une confusion de marque (marque française, fabrication italienne). Les snippets mal attribués, problème n°1.
Jour 2, matin : Première victoire
Dataset élargi à 30 items. Re-run de la baseline : 14/30 = 47%, 7 FC. Cinq des 7 fausses confiances disaient "France" à tort. L'agent avait un gros biais France.
Puis j'ai switché de modèle. Même prompt, mêmes outils, juste Gemini 3.1 Flash Lite au lieu de 2.5 Flash. Résultat : 18/30 = 60%, 4 FC, et 3.3 secondes plus rapide.
Shippé. Première amélioration concrète. +13 points juste en changeant de modèle.
Jour 2, aprem : Le mur
C'est là que j'ai pris une leçon d'humilité. Galvanisé par le switch du matin, j'ai passé l'après-midi à essayer de pousser Flash Lite plus loin. Sept changements consécutifs :
Stratégie EAN-first + instructions retailers + requêtes multilingues (trois changements d'un coup, 47%). Règles de calibration de confiance (53%). Temperature 1.0 (52%). Plus de budget de réflexion (50%, puis 53%). Règles anti-fausse-confiance (43%, testé trois fois pour être sûr).
Tous pires que la baseline de 60%. Sept d'affilée.
Le plus rageant, c'était les règles anti-FC. Elles corrigeaient exactement les items ciblés (2 des 4 cas FC résolus) mais cassaient la précision partout ailleurs. Le modèle était trop simple pour des règles conditionnelles genre "fais confiance à ce type de snippet mais pas celui-là". Il se méfiait de tout, point.
En fin de journée j'ai accepté : Flash Lite était à un optimum local. Le modèle pouvait pas absorber plus de complexité.
Jour 2, soir : La variance
Avant de passer à autre chose, j'ai lancé un dernier test. Le même code, une deuxième fois. Même modèle, même prompt, même config.
Les deux runs reviennent à 60%. Mais 5 items sur 30 avaient changé de camp. Des produits corrects devenaient faux. Des produits faux devenaient corrects.
17% des items donnaient des résultats différents d'un run à l'autre, sans rien modifier.
Ça change tout. Ça veut dire que la plupart des "régressions" de l'aprem (53%, 57%, 50%) étaient probablement du bruit. Seuls les deltas de ±4 items sur 30 étaient des signaux fiables. Nouvelle règle : minimum 3 runs par config. Sans exception.
Jour 2, tard : Le moment 3 Flash
Premier test de Gemini 3 Flash. Dès le premier run avec le même prompt v2 : 66.7%. Meilleur score jamais vu. Mais aussi 7 fausses confiances. Meilleur match ET pire FC dans le même run.
Les modèles plus malins répondent à plus de questions. Ils hallucinent aussi avec plus d'aplomb.
J'ai aussi testé le pivot brand-level ce soir-là sur 2.5 Flash. L'idée : "quand tu trouves pas où le produit est fabriqué, cherche où la marque fabrique". Résultat : 13 fausses confiances. Pire run de tout le projet. L'idée a été enterrée définitivement.
Jour 4 : Le grand comparatif
Dataset élargi à 34 items. J'ai lancé la plus grosse session de benchmark du projet : 3 modèles × 4 versions de prompt, 3 runs chacun. Chaque config testée trois fois pour absorber la variance.
| Config | Match | FC |
|---|---|---|
| 3.1 Flash Lite v2 (prod) | 54.4% | 7.0 |
| 2.5 Flash v2 | 45.6% | 10.5 |
| 3 Flash v2 (même prompt) | 57.8% | 8.7 |
| 3 Flash v3 (règles anti-FC) | 68.6% | 5.7 |
| 3 Flash v4 (nudge) | 74.5% | 7.7 |
| 3 Flash v5 (variant guard) | 71.6% | 7.0 |
| 3 Flash v6a (blocklist) | 71.6% | 6.3 |
La ligne qui saute aux yeux : 3 Flash v3 à 68.6% avec FC 5.7. Les mêmes règles anti-FC qui faisaient chuter Flash Lite de 60% à 43%. Exactement les mêmes. Sur un modèle plus costaud, ça devenait une amélioration massive au lieu d'un désastre.
J'ai aussi testé Haiku 4.5 (67.6%, mais 0.019$/trace et 17.4s de latence) et GPT-5.1 (26.5%, catastrophique). La comparaison entre fournisseurs fait l'objet de son propre article.
Shippé : Gemini 3 Flash avec prompt v4 (nudge + anti-looping). 74.5% en moyenne.
Jour 8 : Dernier push
Dataset élargi à 46 items. Trois changements testés :
Classification asynchrone (sortir un appel LLM secondaire du pipeline principal) : 1.4 seconde gagnée par scan, zéro impact sur la précision. Shippé.
Dispatch parallèle des outils : passage de l'exécution séquentielle à Promise.all et une ligne ajoutée au prompt sur le batching. J'attendais un petit gain de latence. J'ai eu +8.2% de précision et -3.1 FC. La meilleure amélioration individuelle de tout le projet. Le modèle s'est mis à lancer deux recherches avec des angles différents dans le même tour, ce qui lui donnait une meilleure couverture dans le même budget d'outils. Shippé.
Region EU (ajouter un champ pour les produits "Made in EU") : le modèle a ignoré le nouveau champ sur 6 runs d'affilée, malgré deux rounds de renforcement dans le prompt. Mis de côté. La logique a été déplacée en post-processing côté code.
Meilleur run jamais enregistré : 82.6% (dispatch parallèle, run 3).
Ce qui n'a jamais été corrigé
Quatre items ont résisté à toutes les configs testées. Fausse confiance run après run, tous modèles et versions de prompt confondus.
Un badge retailer qui affiche "Made in Italy" comme filtre global du site, pas comme info produit. Un snippet sur un produit Lotus qui se retrouve attribué à un autre produit Lotus. Une marque française qui fabrique en Allemagne. Un produit du Nicaragua identifié systématiquement comme venant du Pérou.
Ces items m'ont appris un truc sur les limites du prompt engineering. Certaines erreurs sont structurelles. L'agent peut pas distinguer un badge retailer d'une spécification produit sans lire la page, et même là, la mise en page rend le truc ambigu. Aucun ajustement de prompt ne corrigera des données qui sont objectivement trompeuses à la source.
J'ai ajouté ces items au benchmark exprès parce qu'ils sont durs. Ils gardent le score honnête.
Le bilan
| Phase | Date | Dataset | Meilleur score | Événement clé |
|---|---|---|---|---|
| 0 | Semaine 0 | eval-dev (~90 items, bruité) | ~70% (pas fiable) | Ground truth bancale |
| 1 | Jour 1 | 19 items | 42% | Premier benchmark solide |
| 2 | Jour 2 AM | 30 items | 60% | Switch → Flash Lite |
| 3 | Jour 2 PM | 30 items | 60% (plateau) | 7 échecs d'affilée |
| 4 | Jour 2 PM | 30 items | 60% | Découverte variance (17% flip) |
| 5 | Jour 2 soir | 30 items | 66.7% | Premier test 3 Flash |
| 6 | Jour 4 | 34 items | 74.5% | Grand comparatif, v4 shippée |
| 7 | Jour 8 | 46 items | 82.6% (run unique) | Dispatch parallèle, 78% moy. |
42% à 78% en trois semaines. Le dataset est devenu plus dur en cours de route (de 19 à 46 items, en ajoutant volontairement des cas moyens et difficiles), donc le gain effectif est plus gros que ce que les chiffres montrent.
Ce que je referais pareil
Construire le benchmark en premier. Chaque chiffre dans cette série existe parce que j'avais une ground truth pour mesurer. Sans ça, j'aurais shippé les règles anti-FC sur Flash Lite (43%) en pensant avoir amélioré les choses. Et j'aurais loupé le gain du dispatch parallèle parce que "ça avait pas l'air d'un changement de précision".
Logger chaque itération. Les règles anti-FC abandonnées sur Flash Lite sont devenues ma meilleure amélioration sur 3 Flash, des semaines plus tard. Si j'avais pas noté pourquoi elles avaient échoué (modèle trop simple, pas règles trop mauvaises), j'aurais cru que c'était une impasse.
Tester un changement à la fois. La seule fois où j'ai testé trois trucs d'un coup, j'ai obtenu une régression impossible à attribuer. Un run gaspillé.
Ce que je ferais autrement
Démarrer avec 30+ items. Mon premier benchmark avait 19 items. La variance à cette taille est tellement haute que quasi rien se distingue du bruit.
Changer de modèle plus tôt. J'ai passé un aprem entier à essayer d'optimiser Flash Lite alors que le bon move c'était de tester un autre modèle. Les 7 échecs consécutifs étaient instructifs après coup, mais j'aurais pu arriver à la même conclusion plus vite.
Lancer le juge dès le jour 1. Le système LLM-as-judge a fait remonter des patterns (recherche EAN manquante, pas de lecture de page) que le benchmark pouvait pas capter. Si je l'avais eu plus tôt, j'aurais eu un meilleur signal sur quoi corriger.
La suite
78% sur 46 items triés sur le volet, c'est assez pour shipper. C'est pas assez pour arrêter d'itérer.
Les reviews du juge pointent des prochaines étapes claires : l'agent doit lire plus de pages au lieu de faire confiance aux snippets, il doit utiliser tout son budget d'outils au lieu de lâcher trop tôt, et il doit chercher par EAN quand les noms de produits sont foireux.
Le dataset est à 57 items. Je vise 120-150 items de qualité. Mais plus d'items, ça veut dire des cycles d'itération plus lents et plus chers. Nouveau problème : comment garder le dataset lean. Quels items testent un truc unique, et lesquels font doublon ?
L'infra de benchmark est en place. Le journal d'itération continue de grossir. Et chaque semaine j'en apprends plus sur ce qui fait qu'un agent IA marche en prod, pas en démo, pas sur 3 exemples triés sur le volet, mais sur n'importe quel code-barres qu'un mec scanne au rayon.
C'est tout l'enjeu.
Dernier article d'une série sur la construction d'un agent IA de production pour Mio, une app qui agrège les données d'origine de fabrication à partir de codes-barres. La stack : Gemini 3 Flash en prod, Claude Opus 4.6 pour le juge, Langfuse pour le tracing et les benchmarks, Serper.dev pour la recherche web, Jina pour la lecture de pages. Si vous avez construit des systèmes similaires, je serais ravi d'en discuter.
La série complète : Pourquoi c'est un problème d'IA · Le prompt engineering qui a pas marché · Benchmark avant prompt · 7 LLM sur la même tâche · LLM-as-Judge · Cet article
Questions fréquentes
-
Dans mon cas, 3 semaines d'itération concentrée. Mais je partais d'un proto fonctionnel et d'un benchmark. Le premier chiffre utile (42%) est tombé au jour 1. La qualité suffisante pour shipper (78%) a demandé 108 runs sur 7 modèles et 6 versions de prompt.
-
Les deux comptent, mais ça interagit. Passer de Gemini 2.5 Flash à 3.1 Flash Lite a donné +13 points direct. Mais les mêmes règles anti-FC qui ont cassé Flash Lite (-17 points) ont amélioré 3 Flash de +10.8 points. C'est le modèle qui détermine quelles optims de prompt sont possibles.
-
J'en ai fait 108 au total. Le truc important : minimum 3 runs par config à cause du non-déterminisme (17% des items changent d'un run à l'autre). Un seul run par config, impossible de distinguer le signal du bruit.
-
Le dispatch parallèle des outils : passer de l'exécution séquentielle à Promise.all et ajouter une ligne dans le prompt sur le batching. J'attendais un petit gain de latence. J'ai eu +8.2% de précision et -3.1 de fausse confiance. Le modèle s'est mis à lancer deux recherches avec des angles différents dans le même tour.