Aller au contenu principal

Changements d'outils en cours de conversation

Avancé

Aussi longtemps que Claude a eu l'usage d'outils, le tableau tools a été gelé pour la vie d'une conversation — ou, plus précisément, gelé pour la vie d'une entrée de cache. Changez-le et le cache de prompt fond.

C'est parce que le prompt caching hashe le préfixe de requête dans un ordre fixe : toolssystemmessages. La liste d'outils est plus tôt que tout ce que vous envoyez d'autre. Ajoutez un outil, renommez une description, et chaque tour en cache après ce point rate. Sur une longue session agentique avec des centaines de milliers de tokens d'entrée en cache, cette "petite édition" peut vous coûter du vrai argent et un cold start frais de plusieurs secondes.

Les changements d'outils en cours de conversation sont le pendant tableau-d'outils des messages système en cours de conversation. Vous déclarez encore l'univers complet des outils dans tools une fois, en avant. Mais vous décidez maintenant quel sous-ensemble est réellement offert au modèle sur un tour donné en ajoutant des blocs tool_addition et tool_removal à l'intérieur d'un message role: "system". Le tableau tools lui-même ne change jamais, donc le préfixe en cache reste octet-identique.

What you'll learn
  • Pourquoi éditer tools[] faisait exploser tout le cache, pas juste la section outils
  • Comment defer_loading, tool_addition et tool_removal séparent la déclaration de la disponibilité
  • Les règles exactes de placement du message système qui porte ces blocs (ils héritent des règles des messages système en cours de conversation)
  • Comment référencer les outils MCP individuellement (mcp_tool_reference) ou comme serveur entier (mcp_toolset_reference)
  • Quand cette beta bat les alternatives — sous-agents avec leur propre tool_choice, renvoi par tour, ou un routeur externe

★ Insight ───────────────────────────────────── Deux choses rendent cette fonctionnalité discrètement importante. Premièrement, sur Opus 5, le prompt minimum cacheable est passé de 1 024 à 512 tokens, donc les sessions plus petites bénéficient du cache — ce qui signifie que les petites sessions souffrent maintenant aussi quand vous l'invalidez. Deuxièmement, tools étant avant system dans le hash signifie qu'aujourd'hui, quand vous utilisez mid-conversation-system-messages pour glisser une nouvelle instruction, vous payez encore le prix plein le jour où vous avez besoin d'introduire un nouvel outil. Cette beta bouche le dernier trou. ─────────────────────────────────────────────────

Le problème de hash de cache en une image

La clé de cache d'une requête est un hash roulant du préfixe, dans cet ordre :

[ tools ][ system ][ messages…, up to the breakpoint ]

Un hit de cache exige que chaque octet avant le breakpoint corresponde à une requête récente. Donc :

Ce que vous changezCe qui hit encore le cacheCe que vous re-payez
Ajouter un nouveau tour user à la finTout le préfixe jusqu'à ce tourSeulement le nouveau tour
Ajouter un nouveau message system en cours de conversationTout avant luiLe nouveau message système
Éditer le champ system de niveau supérieurSeulement toolssystem + chaque message
Ajouter un nouvel outil à toolsRiensystem + chaque message

Cette dernière ligne est celle que Mid-Conversation Tool Changes réécrit.

Les trois pièces mobiles

1. defer_loading: true — sur une déclaration d'outil dans tools, cela garde l'outil déclaré mais retenu du modèle. Il est toujours hashé dans le préfixe de cache (c'est tout l'intérêt), mais Claude ne le voit jamais comme appelable jusqu'à ce que vous le fassiez surface.

2. tool_addition — un bloc de contenu à l'intérieur d'un message role: "system". Fait surface d'un outil defer_loading de ce tour en avant. Re-offre aussi un outil qu'un tool_removal précédent avait retiré.

3. tool_removal — le miroir. Rétracte un outil actuellement offert de ce tour en avant. Chaque tour suivant hit le cache, mais l'outil n'est plus dans l'ensemble de choix de Claude.

tool_addition et tool_removal référencent tous deux un outil via un champ tool. Trois formes de référence sont légales :

  • {"type": "tool_reference", "name": "get_forecast"} — un outil normal déclaré dans tools.
  • {"type": "mcp_tool_reference", "server_name": "linear", "name": "create_issue"} — un seul outil du connecteur MCP.
  • {"type": "mcp_toolset_reference", "server_name": "linear"} — chaque outil exposé par un serveur MCP, en un bloc.

Référencer un nom qui n'est pas déclaré dans tools retourne un 400.

Exemple de travail minimal

La beta exige le header mid-conversation-tool-changes-2026-07-01 et l'un de Fable 5, Mythos 5, Opus 4.8 ou Opus 5. Ci-dessous : déclarez à la fois un outil "read" et un outil "write" en avant, retenez delete_file, et faites-le surface seulement après que l'utilisateur ait confirmé une intention destructrice.

import anthropic

client = anthropic.Anthropic()

TOOLS = [
{
"name": "read_file",
"description": "Read a file from disk.",
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
{
# Declared but withheld. Hashed into the cache prefix so we can
# surface it later without invalidating anything.
"name": "delete_file",
"description": "Permanently delete a file from disk.",
"defer_loading": True,
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
]

messages = [
{"role": "user", "content": "Read notes.md and summarize it."},
# ...several tool_use / tool_result turns...
{"role": "user", "content": "OK, I confirm: delete notes.md."},
# Surface delete_file from this point onward. The cached prefix
# (tools + all earlier turns) still matches byte-for-byte.
{
"role": "system",
"content": [
{
"type": "tool_addition",
"tool": {"type": "tool_reference", "name": "delete_file"},
}
],
},
]

response = client.beta.messages.create(
model="claude-opus-5",
max_tokens=1024,
betas=["mid-conversation-tool-changes-2026-07-01"],
cache_control={"type": "ephemeral"},
tools=TOOLS,
messages=messages,
)

La prochaine requête va :

  1. Hasher tools (inchangé) → hit de cache.
  2. Hasher chaque tour antérieur (inchangé) → hit de cache.
  3. Seuls le nouveau tour user + le bloc tool_addition role system sont de la nouvelle entrée.

Contrastez cela avec la "vieille façon" — laisser tomber delete_file dans tools seulement à ce moment. Cette seule mutation aurait invalidé le préfixe entier.

L'adopter dans une boucle agentique

Guided walkthrough1 of 6
  1. Incluez les outils que vous prévoyez de faire surface plus tard, mais réglez defer_loading: true sur eux. L'intérêt est de geler la section outils du préfixe de cache maintenant.

Patterns de référence

Retenir les outils destructeurs jusqu'à ce que l'utilisateur confirme

system:
<tool_addition tool={type: "tool_reference", name: "delete_project"}>

Only append this after a user turn where the user explicitly confirmed destruction.
Never place before a "clarify what you want to delete?" turn.

Phaser des ensembles d'outils pour une boucle plan → exécute → revue

Phase 1 (plan): tools[] visible = { read_repo, search_web } — everything else defer_loading: true.
Phase 2 (execute): append system-role tool_addition for { edit_file, run_tests }.
Phase 3 (review): append system-role tool_removal for { edit_file }, tool_addition for { post_review_comment }.

The tools[] array never changes; only the offered set does. Cache is preserved across all three phases.

Retirer un serveur MCP après un rate limit

On 429 from the Linear MCP connector, append:

system:
<tool_removal tool={type: "mcp_toolset_reference", server_name: "linear"}>

One block retires every tool that server exposed. Re-offer with a matching tool_addition once your backoff window expires.

Sandbox : donner à un sous-agent un sous-ensemble strict

When you dispatch a subagent, do NOT create a new conversation with a smaller tools[]. Instead reuse the same tools[] (cache hit!) and open the subagent turn with a system-role tool_removal for every capability that subagent should not touch. The parent conversation can restore them on return with a matching tool_addition.

Règles de placement (elles comptent — beaucoup)

Le message role: "system" qui porte les blocs tool_addition / tool_removal est un message système en cours de conversation régulier et hérite de ses règles de placement :

  • Jamais premier. Un message system ne peut pas être la première entrée dans messages ; déclarez l'ensemble d'outils initial dans le champ system de niveau supérieur et tools.
  • Doit suivre un tour user ou un tour assistant server-tool. Un message user portant des blocs tool_result compte — c'est exactement le slot pour réagir à ce qu'un outil vient de retourner.
  • Doit précéder un tour assistant ou être la dernière entrée.
  • Jamais entre un tool_use et son tool_result correspondant. C'est un 400.

Les messages system consécutifs sont légaux et sont traités comme une section. Vous pouvez mélanger tool_addition, tool_removal et des blocs text simples dans le même tableau content.

Comment cela interagit avec le prompt caching

  • Activez le caching explicitement. Un champ cache_control quelque part est requis ; le caching automatique au niveau supérieur est le plus simple.
  • Cachez le préfixe stable comme d'habitude — à travers le dernier bloc qui ne change pas entre requêtes.
  • Parce que le message système ajouté vient après le préfixe en cache, il ne change pas le hash du préfixe.
  • Une fois que le message système est dans la conversation, il devient de l'historique stable et est cacheable au prochain tour.
  • Chaque outil dans tools, y compris les outils defer_loading: true, compte vers la longueur minimum cacheable du prompt — 512 tokens sur Opus 5, 1 024 sur la plupart des autres modèles.

★ Insight ───────────────────────────────────── Ce design pousse les auteurs d'agents vers une discipline spécifique : déclarez l'ambition de la session en avant, et utilisez les signaux runtime pour moduler l'accès. C'est plus proche de la façon dont les capacités de processus OS sont modélisées (capacités que vous avez vs capacités que vous pouvez actuellement exercer) que de la façon dont les API classiques de function-calling sont façonnées. Si vous architecturez un agent autour de cela, "quels outils cet agent a-t-il ?" devient une question avec deux réponses — l'univers déclaré et le sous-ensemble offert — et le cache reste chaud. ─────────────────────────────────────────────────

Ce qu'il ne fait pas

  • Il ne vous laisse pas introduire un outil qui n'était pas dans tools du tout. Chaque outil que le modèle peut jamais être offert doit exister dans tools depuis la première requête. C'est une fonctionnalité, pas une limitation — c'est précisément ce qui garde le hash stable.
  • Il ne vous laisse pas changer l'input_schema ou la description d'un outil en cours de conversation. L'une ou l'autre est une mutation de tools et déclenche un cache miss. Si le schéma d'un outil doit évoluer, déclarez deux outils avec des noms différents.
  • Il ne s'applique pas à Claude Sonnet 5 aujourd'hui. Sonnet 5 ne supporte pas les messages système en cours de conversation du tout, donc cette beta ne peut pas rouler par-dessus. Routez les tours de niveau Sonnet à travers un routeur externe si vous avez besoin d'ensembles d'outils dynamiques là.

Comment d'autres fournisseurs gèrent le même problème

FournisseurEnsemble d'outils dynamique sans re-traitement complet du préfixe ?
Anthropic Claude Opus/Fable/MythosOui, via cette beta.
Anthropic Claude Sonnet 5Non — renvoyez tools (cache miss) ou routez à travers un superviseur externe.
OpenAI GPT-5/6Pratiquement non. Changer le tableau tools dans l'API Responses/Chat Completions est un changement de préfixe ; vous comptez sur la correspondance de préfixe du caching automatique pour casser à la liste d'outils. Contournement courant : agents parent/enfant où l'enfant a un tableau tools restreint.
Google Gemini 3Similaire à OpenAI. La config tools fait partie de la requête ; le pattern pragmatique est des ensembles de Function Declaration par phase, acceptant le coût de la redéclaration.
Serveurs MCP en généralCertains hôtes (Claude Code, Cursor) implémentent le "chargement d'outils à la demande" à l'intérieur de l'hôte, mais c'est au niveau du transport : le modèle sous-jacent reçoit encore une liste d'outils renvoyée jusqu'à ce que cette beta atterrisse côté fournisseur.

Si vous construisez un harnais cross-modèle, structurez votre code pour que le comportement d'"outil dynamique" soit une capacité que vous détectez par modèle plutôt qu'une chose que vous supposez partout.

Modes d'échec courants

  • Vous avez oublié le header beta. La requête est acceptée, les blocs tool_addition / tool_removal sont traités comme du contenu inconnu dans un message système, et le comportement est indéfini — souvent le bloc est silencieusement ignoré et Claude ne voit jamais le nouvel outil.
  • Vous avez mis le message système entre tool_use et tool_result. 400 invalid_request_error. Déplacez-le après le tour user suivant qui porte le tool_result.
  • Vous avez référencé un outil non déclaré dans tools. 400. Déclarez-le avec defer_loading: true et réessayez.
  • Vous avez édité la description de l'outil "juste pour ajouter une clarification". Cache miss pour toute la conversation. Pour faire évoluer un outil en cours de session, ajoutez un outil v2 sous un nouveau nom et utilisez tool_removal sur v1 et tool_addition sur v2.
  • Vous êtes sur Sonnet 5 en vous demandant pourquoi cela ne marche pas. Cela ne marche pas, sur Sonnet 5. Utilisez un tier différent ou un routeur externe.
Aucune carte pour l'instant — ajoutez-en pour commencer à réviser. 🃏

Check yourself

0/5
  1. Pourquoi ajouter un nouvel outil à tools[] en cours de conversation invalide-t-il chaque tour en cache ?
  2. Que fait réellement defer_loading: true ?
  3. Où doit être placé le message role: system portant les blocs tool_addition ?
  4. Quelle est la bonne façon de faire évoluer l'input_schema d'un outil en cours de conversation sans un cache miss complet ?
  5. Quel modèle Claude ne supporte PAS cette fonctionnalité aujourd'hui ?

Sources et lectures complémentaires