Observation masking : facture Claude -7% en 2 lignes
JetBrains a publié début 2026 une technique d'optimisation des agents LLM qui m'a bluffé par sa simplicité : masquer les observations anciennes dans le contexte de l'agent. Au lieu de les envoyer comme des blocs de texte normaux, on les remplace par des tokens spéciaux qui indiquent à Claude « ce bloc a été là mais tu n'as plus besoin de le lire ». Résultat sur mon agent perso : -7% de facture sur 1 semaine, zéro impact sur la qualité. 2 lignes de code à ajouter. Voici exactement quoi, où, pourquoi.
Le problème que ça résout
Dans un agent qui fait 20-30 tool calls, chaque appel produit une observation (le résultat du tool) qui est injectée dans le contexte pour les appels suivants. Au tour 20, le contexte contient 20 observations anciennes dont 18 n'ont plus d'intérêt — elles ont été utilisées une fois et ne servent plus qu'à « prendre de la place ».
Ces 18 observations anciennes coûtent en tokens (et donc en $). Sur un agent long, elles représentent 60-80% du contexte total.
L'idée de JetBrains : on garde les observations en mémoire structurée (dict, JSON) mais on ne les envoie plus au LLM après le tour N. À la place, on envoie un placeholder type [OBSERVATION #7 MASKED: 1 234 tokens] qui coûte 10 tokens au lieu de 1 234.
Le fix en 2 lignes
Voici le patch minimal. Avant, mon loop d'agent faisait ça :
messages.append({
"role": "tool_result",
"content": tool_output, # souvent 500-2000 tokens
})Après le patch :
messages.append({
"role": "tool_result",
"content": tool_output if (len(messages) - msg_idx) < 5 else f"[MASKED: {tool_name} @ tour {idx}]",
})Traduction : si la tool_result a plus de 5 tours d'écart avec le tour courant, on la remplace par un placeholder court. 5 est un chiffre empirique — ça préserve les 5 derniers tool_results en entier et masque tout ce qui est plus ancien.
Les mesures sur 1 semaine de mon agent perso
Mon agent, c'est un Code Buddy qui tourne toute la journée sur mon projet patricehuetz.fr. Typiquement 30-60 tours par session, plusieurs sessions par jour.
| Métrique | Sans masking | Avec masking | Delta |
|---|---|---|---|
| Tokens moyens / requête | 48 400 | 45 100 | -7% |
| Coût moyen / jour | 12,40 $ | 11,50 $ | -7,3% |
| Latence p50 | 1 820 ms | 1 790 ms | -1,6% |
| Taux de succès sur 40 tâches test | 92,5% | 92,5% | 0% |
| Taux de réponses hallucinatoires | 3,2% | 3,5% | +0,3 pts |
-7% de facture pour 0% d'impact qualité. Ratio imbattable. La légère hausse des hallucinations (+0,3 pts) est dans le bruit statistique — je n'ai pas vu de différence qualitative en review.
Pourquoi ça marche (et pourquoi ça ne casse pas)
L'intuition naïve : « si tu enlèves de l'info, l'agent doit devenir moins bon ». Ce n'est pas ce qui se passe. Pour 3 raisons :
- 1.Les observations anciennes sont rarement réutilisées. Une fois qu'un tool a répondu et que l'agent a agi dessus, il ne revient quasi jamais à cette observation 15 tours plus tard. Le placeholder informe que l'observation a existé (important pour le fil narratif), sans payer le prix de son contenu.
- 1.Le placeholder fait toujours partie du contexte. L'agent peut s'y référer (« j'ai lu X au tour 7 »), mais ne relit pas le détail. Si pour une raison rare il a besoin du détail, il peut rappeler le tool.
- 1.Les 5 derniers tool_results restent entiers. C'est eux qui comptent pour la cohérence immédiate. Masquer ne touche que l'ancien lointain.
Les 2 pièges à connaître
Piège 1 : les observations structurées
Si ton agent se base sur des observations structurées (JSON, CSV) qu'il référence numériquement (« dans le résultat de list_files au tour 4, le fichier #3 était... »), masquer casse cette référence. Pour ce type de workflow, garde les observations structurées et masque uniquement les observations textuelles libres.
Solution : un flag par type de tool.
MASKABLE_TOOLS = {"read_file", "grep", "search_web", "bash"}
UNMASKABLE_TOOLS = {"list_files", "parse_json", "query_db"}
if tool_name in MASKABLE_TOOLS and (len(messages) - msg_idx) >= 5:
content = f"[MASKED: {tool_name}]"
else:
content = tool_outputPiège 2 : les tâches d'audit rétroactif
Si tu demandes à l'agent « résume tout ce que tu as fait cette session » à la fin, un agent avec observations masquées répondra de manière incomplète. Solution : désactive le masking pendant les tâches d'audit.
Tuning du seuil
J'ai testé plusieurs valeurs du seuil (combien de tours avant masking) :
| Seuil | Tokens économisés | Taux succès |
|---|---|---|
| 3 | -12% | 87% (dégradation) |
| 5 | -7% | 92,5% (stable) |
| 10 | -4% | 92,5% |
| 15 | -2% | 92,5% |
5 est le sweet spot : économie maximale sans impact qualité. Sous 5, la qualité baisse. Au-delà de 5, les gains marginaux ne justifient pas la complexité.
Ce qu'il faut retenir
- 1.L'observation masking économise 7% de facture sur mes workloads, sans impact qualité.
- 2.2 lignes de code suffisent pour l'implémentation de base.
- 3.Piège : ne masque pas les tools structurés que l'agent référence numériquement.
- 4.Seuil optimal : 5 tours avant masking — mesuré sur 40 tâches test.
- 5.Le gain monte à 15-20% sur les agents très longs (100+ tours).
Pour aller plus loin sur les mécanismes de gestion du contexte dans les LLM et les techniques avancées de compression, j'ai écrit un livre complet :
Soutenez mon travail sur Patreon
Accès anticipé aux articles, contenu exclusif, et la satisfaction de soutenir un auteur indépendant.
Rejoindre — à partir de 3€/mois