Aller au contenu

Contexte des outils et cache de prompts

Deux constats valent pour chaque requête que le harnais intégré émet : les définitions d’outils en sont le coût fixe le plus élevé, et presque aucune ne sera utilisée. Cette page explique ce que Control Center fait à ce sujet, et pourquoi la réponse n’est pas simplement « envoyer moins d’outils ».

Control Center enregistre environ 126 outils. Envoyées intégralement, leurs définitions coûtent environ 23 000 jetons à chaque requête — un coût qu’aucune compaction ne peut entamer, car les définitions d’outils voyagent dans une partie de la requête distincte de la conversation.

La facture en jetons est le reproche évident, et avec le cache de prompts, c’est aussi le moindre. Le vrai coût est la précision de la sélection. Les évaluations publiées sur l’appel d’outils s’accordent : les modèles choisissent le bon outil de façon fiable jusqu’à environ 30–50 outils en contexte, puis se dégradent progressivement au-delà. La cause identifiée n’est pas le nombre de jetons mais le recouvrement sémantique : dès qu’un catalogue contient plusieurs outils dont les descriptions se ressemblent, le modèle choisit entre des quasi-doublons au lieu de se rappeler une capacité.

Avec 126 outils, un agent dépasse largement cette plage à chaque tour — y compris les nombreux tours où il n’avait besoin que de lire un fichier.

Une exécution se voit donc présenter deux choses :

  • un ensemble résident — environ deux douzaines d’outils, envoyés avec leur schéma complet à chaque requête ;
  • un index de noms de tout le reste, regroupé sous des intitulés comme « Tickets and projects » ou « Memory and knowledge », pour quelques centaines de jetons l’ensemble du catalogue.

Mesuré sur le catalogue actuel, cela fait passer le bloc d’outils de ~23,1 k jetons à ~5,0 k, soit environ 78 % de moins, et ramène l’ensemble toujours visible dans la plage où la sélection est fiable.

L’ensemble résident rassemble les outils qu’une exécution utilise dans la plupart des exécutions : le vocabulaire de fichiers et de shell (read, write, edit, search, bash, …), les outils de découverte, la liste de tâches, la recherche en mémoire et le verbe que le mode en cours doit appeler pour produire sa sortie. Le verbe de sortie propre à un mode est toujours résident — une exécution ne doit jamais avoir à chercher l’appel unique qui produit son livrable.

C’est ce qui rend la conception sûre et pas seulement économe. Un outil différé est appelable dès le premier tour. Seul son schéma est retenu.

L’agent voit le nom dans son prompt. S’il appelle ce nom directement, le harnais charge le schéma et exécute l’outil dans la même étape — aucune erreur, aucun aller-retour. Le modèle n’a pas besoin de savoir que l’outil a un jour été « déchargé ».

Quand l’agent connaît la tâche mais pas le nom, search_tools prend une description en langage naturel (« attribuer un ticket à quelqu’un »), classe toute la surface de l’exécution et renvoie les correspondances avec leur schéma déjà chargé, si bien que le tour suivant peut en appeler une. Si une recherche ne trouve rien, il le dit clairement et suggère une autre formulation ou une recherche plus large — délibérément, car un agent qui interprète une recherche vide comme « cette capacité n’existe pas » inventera un substitut au lieu de chercher à nouveau.

search_tools n’existe que dans le harnais intégré. Les clients MCP externes disposent à la place de search_tool_bm25, qui classe le même index mais se contente de décrire les correspondances — ces clients ont déjà reçu tous les schémas via tools/list, il n’y a donc rien à charger.

La mise en différé s’applique après le filtre d’outils du mode, jamais à sa place. Elle masque un schéma ; elle ne peut pas révéler un outil que le mode a refusé. Un outil activé passe toujours la demande d’approbation et les garde-fous d’action exactement comme un outil résident, et un outil ne peut pas charger un nom qui ne figurait pas dès le départ dans la surface de l’exécution.

La surface MCP que voient les clients externes n’est pas touchée. tools/list renvoie toujours le catalogue complet. Une première tentative de la réduire a purement et simplement cassé les agents : les clients MCP valident les noms d’outils par rapport à leur propre liste en cache et refusent tout ce qui n’y figure pas sans jamais interroger le serveur, si bien qu’un outil « caché mais appelable » était tout simplement inatteignable. La mise en différé vit dans le harnais, où charger un outil en cours d’exécution est quelque chose que le harnais peut réellement faire.

--tool-deferral=off (ou CC_SERVER_TOOL_DEFERRAL=off) rend résident à nouveau chaque outil admis, ce qui produit exactement les requêtes que le serveur émettait avant l’existence de la mise en différé. Voir la référence de la CLI cc_server.

Les charges de travail des agents sont déséquilibrées : ils envoient d’énormes prompts et reçoivent de petites réponses, tour après tour, avec le même préfixe à chaque fois. Les fournisseurs mettent ce préfixe en cache, et une lecture du cache coûte environ un dixième d’une lecture fraîche. Le taux de succès du cache est donc une propriété de premier ordre du harnais plutôt qu’une optimisation — et chez Anthropic, il apporte aussi du débit, car les jetons mis en cache ne sont pas comptés dans la limite d’entrée par minute.

La mise en cache repose sur une correspondance exacte de préfixe, d’où une règle qui gouverne tout.

Ne jamais réécrire ce qui a déjà été envoyé

Section intitulée « Ne jamais réécrire ce qui a déjà été envoyé »

Le moindre octet qui change invalide le cache à partir de ce point. Le harnais ne fait donc qu’ajouter : les nouveaux messages à la fin, et les schémas d’outils nouvellement activés après le bloc d’outils mis en cache plutôt que fusionnés dedans. Cet ordre est la raison pour laquelle l’activation est peu coûteuse — le préfixe mis en cache reste identique et l’exécution ne paie que les schémas qu’elle vient de charger.

La boucle élague aussi les sorties d’outils obsolètes des tours plus anciens pour récupérer du contexte. Elle attend qu’il y ait assez à récupérer pour que la réécriture en vaille la peine, si bien qu’un élagage est une réécriture profonde et rare plutôt qu’un échec de cache permanent.

Un fournisseur autorise un nombre limité de marqueurs de cache, et Control Center les dépense délibérément :

Marqueur Couvre Durée de vie
Dernier outil résident Le bloc d’outils 1 heure
Dernier bloc système Outils + prompt système 1 heure
Fin du tour précédent Historique jusqu’à la dernière requête 5 min
Fin du tour en cours L’historique complet 5 min

Les deux derniers forment une paire glissante, et la raison de cette paire est subtile : une recherche dans le cache remonte un nombre borné de blocs à la recherche de ce qu’une requête antérieure a stocké. Un seul tour qui exécute de nombreux outils en parallèle peut émettre plus de blocs que cette fenêtre, et à ce moment-là un marqueur de fin rate silencieusement sa cible, la requête payant alors le prix plein sans que rien dans la réponse ne le signale. Marquer l’endroit exact où la requête précédente s’est arrêtée rend le succès structurel plutôt qu’aléatoire.

Le préfixe outils-et-système bénéficie de la longue durée de vie parce qu’il est constant pendant toute l’exécution, qu’il est partagé par toutes les exécutions et tous les sous-agents de même forme, et parce que le compte à rebours démarre quand une requête commence — un tour qui diffuse pendant plusieurs minutes a déjà consommé la majeure partie d’une fenêtre courte avant que la requête suivante ne se forme.

Les sous-agents d’un même type produisent des préfixes de prompt identiques octet pour octet, ils devraient donc lire une seule entrée de cache au lieu d’en écrire chacun une. Mais une entrée ne devient lisible qu’une fois que la requête qui l’a écrite commence à répondre, si bien qu’un déploiement en éventail lancé d’un seul coup voit chaque enfant payer le surcoût d’un préfixe qu’ils partagent tous.

Le harnais laisse démarrer le premier enfant d’une forme donnée, puis libère ses pairs dès qu’il commence à diffuser. L’attente est bornée : un premier enfant lent coûte un succès de cache, jamais le déploiement en éventail lui-même. Pour la même raison, chaque sous-agent reçoit le même ensemble d’outils résidents quel que soit son profil — le faire varier par profil fragmenterait le préfixe partagé sans rien apporter, puisque le profil a déjà décidé ce que l’enfant peut appeler.

Les deux moitiés sont observables plutôt que supposées :

  • L’explorateur de contexte d’une conversation affiche les outils résidents, l’index différé et ce que chaque schéma retenu coûterait, à partir du même chemin de code qu’une exécution réelle.
  • Chaque exécution enregistre les outils différés qu’elle a chargés et ce qui les a déclenchés, si bien qu’une recherche qui ne trouve rien est visible comme un échec de récupération au lieu de disparaître dans un agent qui fait discrètement quelque chose d’approchant.
  • Observabilité affiche le Taux de cache comme le rapport des lectures de cache sur tous les jetons de prompt traités, écritures de cache comprises — une exécution qui ne cesse de reconstruire son préfixe obtient un score faible, au lieu d’être notée comme une exécution qui n’a jamais eu besoin de cache.