Aller au contenu

Ajuster le contexte d’outils d’un agent

Ce guide couvre le travail pratique autour de la surface d’outils à deux niveaux : voir ce qu’une exécution envoie, la modifier et diagnostiquer un agent qui a saisi le mauvais outil. Pour comprendre pourquoi il en va ainsi, lisez Contexte des outils et cache de prompts.

Examiner ce qu’une exécution envoie réellement

Section intitulée « Examiner ce qu’une exécution envoie réellement »

Ouvrez une conversation et touchez l’indicateur d’utilisation du contexte dans l’en-tête de la conversation. Il reconstruit la surface exacte que le prochain lancement enverrait, sans rien exécuter. Voir plus (ou la commande /context) ouvre l’explorateur de contexte complet, où vous pouvez inspecter chaque outil.

Deux segments comptent ici :

  • Définitions d’outils et MCP et outils dynamiques — l’ensemble résident, avec le coût réel en jetons de chaque outil.
  • Outils chargés à la demande — l’index différé. Chaque entrée n’est facturée que pour son nom, car une requête ne transporte que le nom ; ouvrir une entrée affiche tout de même la description et le schéma complets, ce qui vous montre ce qu’elle coûterait si l’exécution la chargeait.

Si le segment différé est vide, la mise en différé est désactivée pour ce serveur. Vérifiez --tool-deferral dans la CLI cc_server.

L’ensemble résident relève de la politique, pas de la configuration : il réside dans ModeToolPolicy (packages/cc_domain/lib/features/mcp/domain/value_objects/mode_tool_policy.dart) et s’applique à chaque exécution :

  • residentBuiltins — le vocabulaire des fichiers et du shell.
  • residentDiscovery — search_tools et list_my_tools. Ne les retirez pas ; c’est ainsi qu’un agent atteint tout le reste.
  • residentMcpTools — la poignée d’outils de Control Center qui valent leur schéma à chaque requête.
  • Un bloc par mode, pour les outils qu’un mode donné utilise à presque chaque tour.

Deux règles lorsque vous le modifiez :

N’ajoutez un outil que si la plupart des exécutions l’utilisent. Chaque nom coûte son schéma à chaque requête de chaque exécution, pour toujours. Un outil important lorsqu’il sert, mais globalement rare, appartient à la moitié différée — l’agent le trouvera quand même.

Utilisez le nom réel de l’outil. Un nom résident qui ne correspond à aucun outil est inerte : aucune erreur, aucun avertissement, l’outil est simplement différé. test/tooling/resident_tool_names_test.dart détecte ce cas et maintient aussi chaque mode sous 40 outils résidents, ce qui est tout l’intérêt de l’exercice — au-delà de cette plage, la sélection d’outils se dégrade de façon mesurable.

Les verbes requis et épinglés d’un mode sont ajoutés automatiquement, vous n’avez donc jamais besoin de lister le verbe de sortie du mode lui-même.

Quand un agent a utilisé le mauvais outil ou a affirmé ne pas pouvoir faire quelque chose, déterminez laquelle de ces trois situations s’est produite.

Chaque exécution enregistre les outils différés qu’elle a chargés et ce qui les a déclenchés — un appel direct par nom ou une requête search_tools. Ouvrez le flux d’événements de l’exécution (Paramètres → Espace de travail → Agents → l’agent → Journaux → Vue) et cherchez [harness] loaded N deferred tool(s).

  • Rien dans le journal, et l’agent a dit que la capacité manquait — il n’a jamais cherché. C’est un problème de prompt, pas de récupération : l’index différé figure dans son prompt système, avec pour consigne de ne pas déclarer une capacité manquante avant qu’une recherche soit revenue vide.
  • Une recherche journalisée, mais rien de chargé — un véritable échec de récupération. Passez à l’étape 2.
  • Des outils chargés, un mauvais utilisé — les outils trouvés se ressemblent trop. Passez à l’étape 3.

search_tools effectue la correspondance sur les noms, les descriptions et les noms d’arguments. Un échec signifie presque toujours que la description ne contient pas les mots employés par l’agent.

Ajoutez-les. Les descriptions constituent l’index de récupération, alors allongez-les plutôt que de les raccourcir — écrivez le vocabulaire qu’un opérateur saisirait réellement (« affecter », « transmettre », « donner à »), et pas seulement le nom interne. Raccourcir une description pour économiser des jetons vous coûte à la fois la correspondance de recherche et la précision de la sélection, pour une économie presque nulle à côté du schéma.

Si le bon outil existe mais remonte trop bas, c’est à cela que sert l’argument limit ; la description de l’outil indique elle-même à l’agent d’élargir une recherche qui semble proche mais échoue.

Si deux outils ont des descriptions qui se liraient de la même façon pour quelqu’un qui ne connaît pas déjà la différence, le modèle devine. Fusionnez-les en un seul outil doté d’un argument discriminant.

ticket_relation et ticket_pr_link suivent ce modèle : chacun gère la liaison et la suppression de liaison via un argument discriminant. Un choix opéré à l’intérieur d’un appel, avec tout le contexte de la requête, est plus fiable qu’un choix fait entre deux descriptions qui se ressemblent.

Gardez les outils séparés lorsqu’ils exigent des règles d’approbation, un audit ou un rendu différents — autant de raisons pour lesquelles une fusion vous coûterait quelque chose de réel.

Le taux de réussite du cache apparaît dans Observabilité → Aperçu sous la forme Taux de cache. Une conversation longue et saine se maintient haut ; s’il reste durablement bas, quelque chose réécrit le préfixe à chaque tour.

Causes fréquentes, par ordre de probabilité :

  • Tout élément non déterministe au début du prompt. Un horodatage, un identifiant de requête ou un ensemble parcouru dans un ordre variable suffisent.
  • Un changement de modèle ou d’effort en cours de conversation. Les deux modifient le préfixe. Les sous-agents sont particulièrement faciles à mal régler ici : exécuter les enfants à un effort moindre pour économiser peut coûter plus en partage de cache perdu que ce que cela rapporte.
  • Une compaction fréquente. La compaction réécrit l’historique par conception, son coût est donc réel ; la solution est de compacter rarement et en profondeur plutôt que souvent et superficiellement.

Désactiver la mise en différé avec --tool-deferral=off constitue un A/B valable pour isoler si un changement de comportement provient de la surface à deux niveaux. Cela reproduit exactement la requête d’avant la mise en différé ; si le comportement persiste une fois la mise en différé désactivée, la surface n’est pas la cause.