Aller au contenu

Contexte des outils et cache de prompts

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

Le problème, c’est l’attention, pas les octets

Section intitulée “Le problème, c’est l’attention, pas les octets”

Control Center enregistre environ 126 outils. Envoyées au complet, leurs définitions coûtent environ 23 000 jetons à chaque requête — un coût qu’aucun compactage ne peut toucher, parce que les définitions d’outils voyagent dans une partie de la requête distincte de la conversation.

La facture de jetons est la plainte évidente et, avec le cache de prompts, c’est aussi la plus petite. Le coût réel 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 nommée n’est pas le nombre de jetons, mais le chevauchement sémantique : dès qu’un catalogue contient plusieurs outils dont les descriptions se ressemblent, le modèle choisit entre des quasi-jumeaux au lieu de se rappeler d’une capacité.

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

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

  • un ensemble résident — environ deux douzaines d’outils, envoyé avec ses schémas complets à chaque requête ;
  • un index de noms de tout le reste, regroupé sous des rubriques comme « Tickets and projects » ou « Memory and knowledge », pour quelques centaines de jetons au total.

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

L’ensemble résident, ce sont 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 courant doit appeler pour livrer 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.

Voici la partie qui rend la conception sûre et pas seulement économique. 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 — sans erreur, sans aller-retour. Le modèle n’a pas besoin de savoir que l’outil a déjà été « déchargé ».

Quand l’agent connaît la tâche mais pas le nom, search_tools accepte une description en langage naturel (« assigner un ticket à quelqu’un »), classe toute la surface de l’exécution et renvoie les correspondances avec leurs schémas déjà chargés, de sorte que le tour suivant puisse en appeler une. Si une recherche ne trouve rien, elle le dit clairement et propose une formulation différente ou une recherche plus large — délibérément, parce qu’un agent qui interprète une recherche vide comme « cette capacité n’existe pas » inventera un substitut au lieu de chercher de nouveau.

search_tools n’existe que dans le harnais intégré. Les clients MCP externes disposent plutôt de search_tool_bm25, qui classe le même index mais ne fait que décrire les correspondances — ces clients ont déjà reçu tous les schémas de 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 cache un schéma ; elle ne peut pas révéler un outil que le mode a refusé. Un outil activé passe toujours par 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 au 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 tentative antérieure de le réduire a cassé les agents d’emblée : les clients MCP valident les noms d’outils contre 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 de nouveau résident chaque outil admis, produisant exactement les requêtes que le serveur faisait 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 asymétriques : ils envoient d’énormes prompts et reçoivent de courtes 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 de cache coûte environ un dixième d’une lecture fraîche. Le taux de réussite du cache est donc une propriété de premier ordre du harnais plutôt qu’une optimisation — et chez Anthropic, il achète aussi du débit, parce que les jetons mis en cache ne comptent pas dans la limite d’entrée par minute.

La mise en cache repose sur une correspondance exacte de préfixe, ce qui donne une seule règle directrice.

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é”

Tout 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 pour les schémas qu’elle vient de charger.

La boucle élague aussi la sortie d’outils désuète 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 vaille la peine, de sorte qu’un élagage est une réécriture profonde et rare plutôt qu’un raté de cache continu.

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
Queue du tour précédent Historique jusqu’à la dernière requête 5 min
Queue du tour courant L’historique complet 5 min

Les deux derniers forment une paire mobile, et la raison d’être de cette paire est subtile : une consultation de cache cherche vers l’arrière, dans un nombre borné de blocs, quelque chose qu’une requête antérieure a stocké. Un seul tour qui exécute plusieurs outils en parallèle peut émettre plus de blocs que cette fenêtre ; à ce moment, un marqueur de queue rate discrètement et la requête paie le plein prix 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 reçoit la longue durée de vie parce qu’il est constant pour toute l’exécution, qu’il est partagé par chaque exécution et sous-agent de même forme, et parce que l’horloge démarre quand une requête commence — un tour qui diffuse en continu pendant plusieurs minutes a déjà épuisé la majeure partie d’une fenêtre courte avant que la requête suivante ne se forme.

Les sous-agents du même type produisent des préfixes de prompt identiques octet pour octet, ils devraient donc lire une seule entrée de cache plutôt que d’écrire chacun la sienne. Mais une entrée ne devient lisible qu’une fois que la requête qui l’a écrite commence à répondre ; un éventail lancé d’un seul coup fait donc payer à chaque enfant la prime 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 frères et sœurs dès qu’il commence à diffuser. L’attente est bornée : un premier enfant lent coûte un succès de cache, jamais l’éventail lui-même. Pour la même raison, chaque sous-agent reçoit le même ensemble d’outils résidents, peu importe son profil — le faire varier par profil fragmenterait le préfixe partagé sans gain, puisque le profil a déjà décidé ce que l’enfant peut appeler.

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

  • L’explorateur de contexte d’une conversation indique les outils résidents, l’index différé et ce que coûterait chaque schéma retenu, à partir du même chemin de code qu’utilise une exécution réelle.
  • Chaque exécution consigne les outils différés qu’elle a chargés et ce qui l’a déclenché, de sorte qu’une recherche qui ne trouve rien est visible comme un raté de récupération plutôt que de disparaître dans un agent qui fait discrètement quelque chose de connexe.
  • L’observabilité rapporte le Taux de cache comme les lectures de cache sur tous les jetons de prompt traités, écritures de cache comprises — une exécution qui ne cesse de rebâtir son préfixe obtient un score faible, au lieu d’obtenir un score comme celle qui n’a jamais eu besoin d’un cache.