03.11.26
Logs pytest pour agent IA : réduire les tokens12 min de lecture2 441 mots

Logs pytest pour agent IA : réduire les tokens

Réduisez le bruit des tests transmis à votre agent IA : méthode de filtrage JSON, résultats d’un benchmark et limites à connaître avant déploiement.

logs pytest agent IAfiltrage JSON des testsréduire les tokens d’un agent IAsortie de tests automatisésoutil de développement IA
Logs pytest pour agent IA : réduire les tokens

Pourquoi votre agent de code n'a pas besoin des 76 tests réussis

Quand un agent de code lance une suite de tests, la tentation est simple : lui envoyer toute la sortie du terminal. Après tout, il « voit » ce que vous verriez. Sauf que, la plupart du temps, il reçoit aussi des couleurs ANSI, des bannières d'environnement, des traces internes du framework - et souvent des dizaines de lignes qui indiquent surtout que tout s'est bien passé. Beau pour un humain qui scrolle. Moins utile pour repérer l'échec et corriger.

Le coût ne se limite pas à la facture de l'API. Les sorties finissent aussi dans l'historique de la conversation : au tour suivant, le modèle peut devoir relire une partie de ces journaux pour produire une modification. Résultat : plus l'agent fait d'allers-retours, plus le bruit s'accumule, et plus il risque de passer à côté de ce qui compte vraiment.

Un benchmark comparant des journaux pytest bruts à un résumé JSON structuré montre l'ampleur du décalage : 94,1 % de tokens d'entrée en moins, sans perte de réussite sur les 20 cas évalués. Ce n'est pas une garantie « universelle » pour tous les dépôts. Mais c'est une bonne preuve de principe : la sortie d'un outil, pour un agent, devrait ressembler à une interface - pas à un copier-coller du terminal. Voici les chiffres, la méthode de filtrage, et les précautions.

Pourquoi les logs de tests saturent-ils le contexte d'un agent IA ?

Une sortie de test n'est pas, par défaut, une bonne entrée pour un modèle. Le terminal a été pensé pour la lecture humaine pendant une session. Une réponse d'outil destinée à un agent doit, elle, mettre en avant ce qui permet de décider quoi faire ensuite. Les deux usages se chevauchent, mais ils ne sont pas identiques.

Le volume s'accumule d'un tour à l'autre

Dans l'expérience étudiée, la suite lance 80 tests : 76 réussites et 4 échecs, répartis dans trois modules Python. Avec pytest en mode verbeux et des traces longues, le premier lancement produit 60 749 caractères. On y trouve les résultats individuels, des éléments de débogage et des infos de plateforme, en plus des échecs.

Le problème devient plus net quand on enchaîne les tours. Si l'agent reçoit cette sortie brute, puis relance les tests après une correction partielle, il ne repart pas de zéro : les anciennes sorties restent dans l'historique. Le prompt du tour suivant contient donc une partie du poids des exécutions précédentes. Dans le benchmark, les deux lancements bruts totalisent environ 117 391 caractères.

Et c'est là que ça devient contre-intuitif : le coût d'un outil ne se mesure pas uniquement à la réponse immédiate. Une sortie longue peut augmenter le nombre de tokens consommés à chaque étape suivante, selon la façon dont le harnais reconstruit l'historique. Sur une tâche isolée, l'écart peut sembler faible. Dans une boucle de correction répétée, il prend de l'épaisseur.

Réussites et bruit ne sont pas des diagnostics

Quand 76 tests passent et 4 échouent, les 76 lignes « vertes » confirment surtout un résultat global. Elles aident rarement à localiser le défaut. Les traces internes et les bannières peuvent être utiles dans certains cas - par exemple pour diagnostiquer un problème de configuration ou un test instable - mais pas forcément besoin de les injecter dans chaque échange avec le modèle.

La question devient donc pragmatique : quelle information doit être là pour que l'agent sache quoi faire ensuite ? Le plus souvent, il suffit de disposer du nom du test, du fichier, de la ligne, du résultat attendu et du résultat obtenu. Un extrait de trace peut aider, mais pas forcément toutes les lignes des tests réussis.

Ce n'est pas « supprimer les journaux ». C'est séparer ce qu'on conserve de ce qu'on transmet : garder la sortie complète pour l'inspection humaine (ou l'archivage), et fournir à l'agent une vue compacte, fidèle, et assez détaillée pour corriger.

Que montre le benchmark sur le filtrage JSON ?

L'expérience compare deux façons de renvoyer les résultats d'une même suite à un agent exécuté dans une machine virtuelle isolée. Dans le premier mode, on renvoie la sortie brute de pytest. Dans le second, on applique un prétraitement déterministe : suppression des séquences de couleur, des bannières, des tests réussis et du bruit de débogage. Ensuite, on émet un résumé JSON ne contenant que les échecs.

Les chiffres montrent surtout l'effet cumulé

Mesure Sortie brute Résumé structuré Écart observé
Sortie cumulée de l'outil 117 391 caractères 1 604 caractères -98,6 %
Première sortie de test 60 749 caractères 1 506 caractères -97,5 %
Tokens d'entrée cumulés 92 385 5 445 -94,1 %
Appels au modèle 3 2 -33,3 %
Durée mesurée 25,94 s 16,88 s -35,0 %
Coût estimé 0,0770 $ 0,0100 $ -87,0 %
Résultat de la suite cachée 20/20 20/20 Égalité

Le point n'est pas seulement que 1 506 caractères tiennent dans moins d'espace que 60 749. Dans le mode brut, le deuxième tour démarre avec 31 170 tokens d'entrée, et l'agent ne corrige alors qu'un module. Un nouveau lancement ajoute encore une longue sortie ; au troisième tour, le modèle reçoit 58 809 tokens d'entrée avant de proposer la correction des trois modules. Le total atteint 92 385 tokens.

Avec le filtre, le deuxième tour reçoit 3 030 tokens d'entrée. Les quatre défauts apparaissent dans un seul résumé : une devise autorisée manquante et des règles de validation, d'ordre de règlement, de remise et de formatage. L'agent corrige les trois fichiers en un seul appel de correction. Ce n'est donc pas uniquement une histoire de compression : la réduction du bruit a aussi changé la manière dont le modèle enchaîne vers la résolution.

Un résultat utile, mais à garder en tête

Les deux modes réussissent les 20 cas cachés : six contrôles de validation, neuf contrôles de règlement, et cinq tests de bout en bout. Dans ce contexte précis, la compression n'a pas empêché l'agent de traiter les invariants testés.

Cela dit, il faut lire ces chiffres comme ceux d'une expérience, pas comme une règle qui s'applique partout. Le résultat dépend du modèle, du harnais, du format des tests, de la qualité du filtre, et surtout de ce que contiennent les échecs. Les durées et le coût sont aussi spécifiques à cet environnement : changer d'API, de suite ou de cache peut déplacer les valeurs.

Le signal le plus général reste simple : éviter de réinjecter des lignes sans valeur diagnostique peut réduire la charge cumulée d'une boucle d'agent.

Comment filtrer les résultats sans masquer une régression ?

L'objectif du filtre doit être clair : réduire le bruit, pas interpréter le résultat à la place du modèle. Une transformation trop agressive peut supprimer précisément l'indice nécessaire à la correction - une erreur de configuration, une différence de valeur, une trace d'exécution. La mise en œuvre doit donc partir des décisions attendues de l'agent, et préserver ce qu'il faut pour les prendre.

Définir un contrat de sortie

Pour chaque exécution, un résumé utile peut inclure :

  • le nombre total de tests, de réussites et d'échecs ;
  • pour chaque échec, le nom du test, le fichier et la ligne ;
  • le message d'assertion et les valeurs attendue et obtenue, lorsqu'elles existent ;
  • un extrait de trace limité, avec une indication si la trace complète est disponible ;
  • le code de sortie du processus et les erreurs de lancement ou de collecte.

Le benchmark renvoie notamment noms des tests, fichiers, lignes et assertions. Pour des tests liés au paiement, les assertions résument des règles concrètes : conserver l'ordre de première apparition, appliquer une réserve seulement au-dessus d'un seuil, respecter une limite de remise, ou préserver un signe négatif pour zéro. Avec ce niveau de détail, l'agent travaille sur des invariants exploitables - pas seulement sur une liste de tests rouges.

Les réussites peuvent être réduites à un compteur, mais l'absence de réussites ne doit jamais être confondue avec une exécution réussie. Le résumé doit aussi signaler clairement si la collecte des tests a échoué, si le processus a été interrompu, ou si le format attendu n'a pas pu être produit. Une réponse vide ou mal formée ne devrait jamais être « convertie » en succès par défaut.

Garder un accès à la sortie complète

Une approche robuste prévoit deux niveaux : une réponse compacte pour l'agent, et un journal intégral consultable quand il faut creuser. Le filtre peut joindre au résumé un identifiant d'exécution ou un chemin local, sans pousser automatiquement des dizaines de milliers de caractères dans le contexte. Si le modèle ne peut pas résoudre un échec à partir du résumé, le harnais peut demander un extrait ciblé (ou la trace complète) de ce test.

Côté pytest, des options comme --tb=short, -q, --no-header et --no-summary peuvent déjà réduire le volume affiché. Un rapport machine lisible, par exemple via un greffon de rapport JSON, facilite ensuite l'extraction des champs pertinents. Attention : ces options ne remplacent pas la vérification du format réel. Selon les suites, certaines sorties (erreurs de configuration, tests personnalisés, etc.) peuvent échapper au schéma prévu.

Enfin, le filtre doit être déterministe et testable, comme le reste du code. Vérifiez qu'un échec conserve bien son assertion, qu'une erreur d'exécution ne devient pas un faux succès, que les caractères spéciaux sont sérialisés correctement, et que les sorties volumineuses sont plafonnées sans couper silencieusement des éléments importants.

Quand cette approche fonctionne-t-elle et quand échoue-t-elle ?

Le filtrage devient particulièrement intéressant quand l'agent exécute souvent une suite verbeuse, que le rapport contient beaucoup de réussites répétitives, et que les échecs sont suffisamment structurés. C'est courant dans les boucles de correction : l'agent lance les tests, modifie le code, puis relance. Dans ce contexte, chaque sortie inutile alourdit non seulement le tour en cours, mais potentiellement la suite.

Les cas où le résumé apporte le plus

La méthode marche bien pour les tests unitaires et d'intégration dont le résultat expose un nom stable, une assertion, et une localisation. Elle aide aussi quand un dépôt contient beaucoup de contrôles « semblables », mais peu d'échecs réels : le résumé permet alors de comparer plusieurs défauts et de proposer une correction cohérente, au lieu de laisser l'agent lire des pages de succès avant de tomber enfin sur le signal.

Et ce point est important : le filtrage ne « rend pas » les modèles intelligents. Il améliore surtout l'interface entre l'outil et le modèle. Moins de tours intermédiaires, plus de contexte pour le code et les exigences produit. Le gain devient intéressant quand le filtre est réutilisé assez souvent pour rentabiliser sa conception et sa maintenance.

Les limites à traiter explicitement

Pour un échec intermittent, une erreur de concurrence, ou un souci d'environnement, la trace complète peut être décisive. Un filtre qui ne conserve que la dernière assertion risque alors d'effacer le contexte utile. De même, certains tests end-to-end produisent des sorties peu structurées : des bibliothèques peuvent imprimer les diagnostics essentiels sur la sortie d'erreur standard plutôt que dans le rapport de tests.

Il y a aussi un risque plus subtil : si le filtre « réécrit » les assertions sous forme de résumés, ou supprime ce qu'il juge secondaire, il peut orienter l'agent vers une fausse cause. La bonne approche reste mécanique au départ : extraire des champs, pas générer une synthèse. Gardez les valeurs brutes, et n'ajoutez un extrait qu'en cas de besoin - quand le format de test ne permet pas d'exposer directement une différence exploitable.

Côté déploiement, le bon réflexe est progressif : comparez d'abord, sur vos exécutions réelles, le résumé et la sortie complète ; vérifiez que chaque échec reste diagnostiquable ; puis passez au mode compact par défaut en gardant un mécanisme d'inspection. Si les incidents se concentrent sur certains types de tests, ajustez le schéma ou désactivez le filtrage pour ces cas.

L'objectif n'est pas de réduire le nombre de tokens à tout prix. C'est d'obtenir le meilleur compromis entre coût, précision et capacité de diagnostic.

Conclusion

Envoyer au modèle la sortie intégrale de pytest est simple au départ, mais cette simplicité devient vite coûteuse quand les journaux s'accumulent dans une boucle d'agent. Dans le benchmark, un résumé JSON a fortement réduit le volume et les tokens d'entrée, tout en gardant les résultats sur les 20 cas évalués.

La décision pratique n'est donc pas « supprimer les logs ». C'est distinguer ce qui sert vraiment à l'agent de ce qui doit rester disponible pour l'analyse humaine. Commencez par compter vos sorties et vos tokens sur des exécutions réelles, définissez un schéma de diagnostic minimal, puis vérifiez que vous n'avez masqué aucun échec. Réduire le bruit est une optimisation utile. Garder le signal, c'est la condition de sa fiabilité.

Questions frequentes

Les sorties verbeuses incluent les tests réussis, les traces, les bannières et parfois des messages de débogage. Quand elles restent dans l’historique, elles peuvent être relues aux tours suivants. Le coût dépend du harnais, mais l’accumulation peut dépasser largement le volume du code réellement à corriger.
Non. Les 94,1 % correspondent aux tokens d’entrée mesurés dans une expérience précise, avec une suite de 80 tests et quatre échecs. Le résultat varie selon le projet, le modèle et la sortie initiale. Il faut mesurer le gain sur votre propre boucle d’exécution avant d’en faire une hypothèse budgétaire.
Souvent, on peut les remplacer par un compteur, mais pas sans vérifier les besoins de diagnostic. Certaines suites impriment des informations utiles même en cas de réussite. Conservez le journal complet hors du contexte courant et prévoyez un moyen de récupérer une sortie détaillée lorsqu’un incident l’exige.
Pas nécessairement. Un outil spécialisé peut convenir, mais un wrapper déterministe autour du lanceur de tests suffit parfois. Le choix dépend de la diversité des commandes, de la maintenance et du besoin de contrôle. Dans tous les cas, testez les erreurs de collecte et la préservation des détails d’échec.

Articles similaires