03.11.26
Contexte des agents IA : privilégier les interfaces10 min de lecture1 971 mots

Contexte des agents IA : privilégier les interfaces

Découvrez pourquoi les fichiers de dépendances complets peuvent nuire à un agent de code et comment lui fournir des interfaces utiles, sans bruit superflu.

contexte des agents IAagent de codesquelettes d’interfaceTree-sitterfenêtre de contexte
Contexte des agents IA : privilégier les interfaces

Agents de code : pourquoi leur donner moins de code peut améliorer leurs réponses

Quand on demande à un agent de code de modifier un module, lui passer tout le code des dépendances peut sembler logique. Après tout, il aurait « tout le contexte » pour comprendre le projet. Sauf que plus d'information n'est pas forcément une meilleure décision : des fichiers longs, des commentaires datés et des détails internes peuvent détourner l'attention des règles qui comptent vraiment.

Un benchmark compare deux façons de préparer ce contexte. Sur une chaîne de règlement de paiements en trois modules, l'agent reçoit soit les sources complètes de ses dépendances, soit un résumé structurel avec les signatures et les contrats publics. Dans le second cas, l'agent passe 20 tests cachés en un seul passage ; dans le premier, il en passe 17 après trois passages. Intéressant, oui - mais pas une vérité universelle.

L'idée, c'est donc de comprendre quoi garder dans le contexte, quoi résumer, et comment construire un filtre qui aide l'agent à respecter les frontières du système, sans le noyer dans le superflu.

Pourquoi le code complet peut nuire à un agent

Un agent de code ne « lit » pas un dépôt comme un développeur. Un humain peut revenir sur un fichier, fouiller l'historique, demander à quelqu'un et croiser plusieurs sources. L'agent, lui, travaille avec une sélection de fichiers et des résultats d'outils, dans une fenêtre de contexte limitée. Même si la limite technique est haute, le budget utile reste contraint : plus de texte à traiter veut dire plus de coût, plus de latence, et davantage de risques de rater les consignes importantes.

Le point clé, à mon avis, c'est le rapport entre le signal et le bruit. Une dépendance peut contenir une interface courte et stable, puis des centaines de lignes d'implémentation, des fonctions auxiliaires, des commentaires historiques, et des chemins rarement utilisés. Si la tâche porte sur une règle de validation précise, ces détails n'aident pas forcément - ils peuvent au contraire rendre moins visibles les contraintes déterminantes.

L'interface répond souvent à la bonne question

Pour appeler un module voisin, l'agent a généralement besoin de savoir : quelles fonctions sont accessibles, quels paramètres elles prennent, quelles valeurs elles renvoient, et quelles règles s'y appliquent. Il n'a pas forcément besoin des boucles internes, ni de tout ce qui sert uniquement au calcul interne.

Une signature seule ne suffit cependant pas. settle_batch(txns, tier) montre comment on appelle la fonction, mais pas forcément des règles comme « conserver l'ordre de première apparition » ou « appliquer le seuil à partir de 15 000 centimes inclus ». Un bon résumé d'interface doit donc aussi inclure types, constantes publiques et contrats, surtout pour les cas limites.

Le bruit peut faire ressortir de mauvaises règles

Dans l'expérience, les dépendances contenaient des commentaires et des éléments d'implémentation obsolètes. Par exemple, certains évoquaient un tri des clés ou une condition différente pour calculer une réserve. Le contrat public, lui, demandait un ordre précis et une règle stricte au-dessus de 300 points de base.

Ce genre de divergence existe dans la vraie vie : un commentaire n'est pas toujours à jour, une ancienne version traîne encore, ou un exemple contredit le comportement attendu. Ajouter tout le code voisin sans tri augmente les chances que le modèle « choisisse » un indice secondaire. Ce n'est pas une garantie d'erreur, mais c'est une confusion évitable.

La règle simple qui ressort : transmettez d'abord ce qui sert à l'intégration. Donnez l'implémentation quand la tâche demande d'expliquer ou de modifier un comportement, pas seulement parce que la dépendance est importée.

Ce que le benchmark permet de conclure

Le test compare deux méthodes pour la même tâche : refactoriser une chaîne de règlement composée de validator.py, settlement.py et statement.py. Dans le premier mode, l'agent reçoit des implémentations complètes de dépendances périphériques et des diagnostics bruts. Dans le second, on lui fournit un squelette généré à partir de la structure du code : signatures, annotations et contrats conservés, avec les corps de fonctions remplacés par une ellipse.

Les résultats mesurés

Sur cette tâche, le mode « code complet » fait 17 réussites sur 20 en trois appels. Le mode « squelette d'interface » fait 20 sur 20 en un appel. Les volumes d'entrée sont respectivement de 45 270 et 2 316 jetons (environ 95 % de moins pour le second mode). Le coût estimé passe de 0,0446 à 0,0078 dollar, et le temps d'exécution d'environ 30 à environ 13 secondes.

Les trois erreurs restantes du premier mode aident à comprendre le pourquoi. L'agent a rejeté une valeur nulle pourtant autorisée pour unit_cents en testant cents <= 0 au lieu de cents < 0. Il a aussi recopié des clés d'entrée non validées dans la sortie, alors que le contrat limitait le dictionnaire à cinq clés. Bref : des erreurs de frontière et de cas limite, précisément le genre de choses qu'un contrat explicite met en évidence.

Comment lire ces chiffres sans en faire une loi

Ce benchmark montre un compromis opérationnel assez logique : un contexte plus court et mieux ciblé peut réduire coûts et itérations, et améliorer le respect de certains contrats. Mais il ne prouve pas que les squelettes seront toujours meilleurs, partout. Ici, on a un modèle, une tâche, un environnement isolé, et 20 tests cachés. La qualité des consignes, le contenu fourni et l'outil de génération comptent aussi. Sur une autre tâche - par exemple quand il faut comprendre une optimisation interne ou corriger une dépendance - le code complet peut redevenir utile.

Pour tester l'approche dans votre cas : prenez des tâches représentatives de votre équipe, gardez le même modèle, les mêmes consignes et les mêmes tests, et ne changez que la stratégie de contexte. Mesurez au moins le taux de réussite, les régressions, le nombre de passages, le volume de jetons, et le temps de revue humaine. Si votre coût baisse mais que le taux de défauts monte (ou que la correction devient plus lente), ce n'est pas un gain.

À retenir : ce résultat sert surtout de raison de tester un contexte filtré par interface, pas une invitation à supprimer aveuglément des dépendances.

Comment produire un contexte centré sur les interfaces

La méthode tient en une idée : séparer les dépendances selon leur rôle dans la tâche. Pour le module que l'agent doit modifier, fournissez le code utile et les consignes de changement. Pour les modules qu'il doit seulement appeler, commencez par leurs interfaces publiques et leurs contrats. N'ajoutez des implémentations ciblées que si une question précise l'exige.

Construire le squelette en Python

En Python, le module standard ast permet de repérer fonctions et classes sans exécuter le code. Voici une version simplifiée qui masque le corps des fonctions tout en conservant leur docstring lorsqu'elle existe :

import ast

def extraire_squelette(source: str) -> str:

"""Conserve les interfaces et docstrings, masque les corps de fonctions."""

arbre = ast.parse(source)

for nœud in ast.walk(arbre):

if isinstance(nœud, (ast.FunctionDef, ast.AsyncFunctionDef)):

doc = ast.get_docstring(nœud, clean=False)

instructions = []

if doc:

instructions.append(ast.Expr(value=ast.Constant(value=doc)))

instructions.append(ast.Expr(value=ast.Constant(value=Ellipsis)))

nœud.body = instructions

ast.fix_missing_locations(arbre)

return ast.unparse(arbre)

Cette base aide à démarrer, mais ce n'est pas un outil de production complet. Elle masque les corps de fonctions parcourues, y compris celles qui sont privées ou imbriquées. Selon le dépôt, il faudra préserver les définitions de classes, les constantes publiques, les décorateurs et tout ce qui permet de comprendre l'API. Dans certains cas, on peut aussi exclure les fonctions privées - sauf si elles constituent un contrat que l'agent doit connaître.

Pour plusieurs langages, un parseur comme Tree-sitter peut fournir une base syntaxique commune. Mais aucun parseur ne sait automatiquement quelles règles métier sont importantes. Ensuite, il faut enrichir : contrats explicites, types, constantes et exceptions pertinentes. Une docstring du style « traite les paiements » dit souvent trop peu sur les cas limites.

Appliquer un filtre en trois niveaux

  1. Contexte initial : fichier cible, interfaces publiques des dépendances directes, contrats pertinents, et tests connus.
  2. Enrichissement ciblé : si l'agent bloque ou si le contrat dépend d'un détail interne, ajoutez la fonction concernée - pas tout le dépôt.
  3. Vérification : lancez les tests, regardez le diff, et contrôlez les invariants métier, notamment les valeurs limites et les effets de bord.

Cette progression évite le tout ou rien : envoyer le dépôt entier dès le départ, ou au contraire priver l'agent des infos dont il a besoin. On transforme la construction du contexte en décision réversible. Si le squelette ne suffit pas, on élargit plutôt que de repartir de zéro.

Pour une équipe, le filtre doit aussi être prévisible. Faites-le tourner sur des fichiers de test, puis inspectez le résultat après chaque changement. Un outil qui supprime par erreur une surcharge, une annotation ou un décorateur peut créer une confusion pire que le bruit qu'il voulait enlever.

Quand faut-il transmettre l'implémentation complète ?

Les squelettes d'interface sont surtout utiles quand l'agent travaille sur un module et doit respecter des contrats de dépendances qu'il ne doit pas changer. Ils sont moins adaptés quand la tâche porte précisément sur ces dépendances, ou sur le comportement interne qu'elles mettent en œuvre.

Donnez davantage d'implémentation quand l'agent doit déboguer un défaut, suivre une transformation de données, comprendre un algorithme, modifier une fonction existante, ou évaluer des effets de bord. Une signature peut dire qu'une fonction reçoit une liste et renvoie un dictionnaire ; elle ne révèle ni les mutations, ni les erreurs levées, ni les hypothèses implicites si le contrat ne les documente pas.

Les cas où le squelette ne suffit pas

Faites attention notamment dans ces cas :

  • Contrats incomplets : les signatures et docstrings ne décrivent pas toujours les invariants réels. Il faut souvent passer par les tests ou le code.
  • Dépendance à modifier : masquer le corps prive l'agent de ce dont il a besoin pour corriger proprement.
  • Comportement implicite : l'ordre, la gestion des erreurs, l'encodage ou les effets de bord peuvent être importants, mais non documentés.
  • Génération imparfaite : le filtre peut supprimer des éléments essentiels, produire du code ambigu, ou manquer des constructions du langage.
  • Investigation d'incident : les traces d'exécution et le chemin réel des données comptent plus qu'une API « théorique ».

Un compromis pratique : donner d'abord une carte d'interface à l'agent, puis ouvrir les fichiers (ou fonctions) au fur et à mesure. C'est plus naturel pour une investigation : on délimite le système, puis on descend au détail utile. À l'inverse, pour une modification locale bien spécifiée, le squelette peut suffire dès le premier passage.

La règle de décision

Avant d'ajouter une dépendance au prompt, posez une question simple : l'agent doit-il connaître le contrat, ou comprendre l'implémentation ? Si seul le contrat est nécessaire, passez par l'interface et les règles métier. Si la tâche implique de changer ou d'expliquer le fonctionnement interne, fournissez le code pertinent.

Enfin, gardez une règle de contrôle : le contexte réduit ne doit jamais remplacer les tests. Les contrats synthétisés doivent être vérifiables, et les changements doivent être jugés sur le comportement attendu - pas sur la capacité de l'agent à produire une réponse « en un passage ».

Conclusion

Donner « tout » à un agent de code peut avoir l'air complet, mais l'exhaustivité n'est pas toujours utile. Quand l'agent doit intégrer des modules sans les modifier, les signatures et contrats publics peuvent mieux protéger les règles importantes, avec moins de jetons, moins de coût et moins de bruit.

La bonne approche reste graduelle : commencez par l'interface, ajoutez l'implémentation quand la tâche le justifie, puis validez avec des tests et une revue du diff. Testez cette méthode sur vos propres tâches avant de l'étendre.

Questions frequentes

C’est une représentation compacte d’un module qui conserve ses éléments visibles, comme les signatures de fonctions, les types, certaines constantes et les contrats, tout en masquant une partie des corps d’implémentation. Il aide l’agent à comprendre comment utiliser une dépendance sans lui transmettre chaque détail interne.
Non. Masquer une implémentation est pertinent si l’agent doit seulement appeler le module et si son contrat est suffisamment clair. Pour déboguer, modifier ou expliquer le fonctionnement interne, il faut fournir le code concerné. Le bon contexte dépend de la tâche, pas d’une règle absolue.
Oui, pour analyser la structure syntaxique d’un fichier Python et produire une première projection. Il faut cependant vérifier ce qui est conservé : décorateurs, types, classes, constantes et docstrings peuvent être déterminants. Pour d’autres langages, un parseur adapté, comme Tree-sitter, peut servir de base.
Non. Il rapporte un résultat sur une tâche précise, un modèle et 20 tests cachés. Il montre qu’un contexte ciblé peut être plus efficace dans certains cas. Pour savoir si le gain se retrouve dans votre projet, comparez les méthodes sur des tâches représentatives et mesurez aussi les régressions et le temps de revue.

Articles similaires