À chaque tour, l’agent renvoie tout l’historique au modèle. Une conversation de trente échanges, c’est trente échanges de tokens à chaque appel. Le coût croît, la latence croît, et — moins évident — la qualité baisse : noyé dans un contexte trop long, le modèle perd le fil, oublie les consignes, se contredit. C’est le context rot.
Gérer le contexte, ce n’est donc pas qu’une question de facture : c’est une condition de fiabilité. Et c’est le principal levier de coût d’un agent.
Le problème, en chiffres
Sans gestion, le coût d’une conversation est quadratique : chaque nouveau tour renvoie tous les précédents. Dix tours de 500 tokens, ce n’est pas 5 000 tokens facturés, mais la somme cumulée — plusieurs dizaines de milliers. La courbe « tokens par requête » de LangFuse le montre crûment : elle monte à chaque tour.
Stratégie 1 — Tronquer (trimming)
La plus simple : ne garder que les N derniers messages (ou les derniers K tokens),
en conservant toujours le message système. LangChain fournit trim_messages.
from langchain_core.messages import trim_messages
from langchain_core.messages.utils import count_tokens_approximately
def noeud_modele(state: Etat) -> dict:
fenetre = trim_messages(
state["messages"],
max_tokens=4000,
strategy="last", # garde les plus récents
token_counter=count_tokens_approximately,
include_system=True, # conserve toujours le system
start_on="human", # commence sur un tour utilisateur propre
)
return {"messages": [modele.invoke(fenetre)]}
Note : on tronque ce qu’on envoie au modèle, pas forcément l’état persisté. L’historique complet peut rester dans le checkpointer ; seule la vue passée au LLM est bornée.
Stratégie 2 — Résumer (summarization)
Plutôt que de jeter les anciens tours, on les condense. Un nœud résume périodiquement l’historique ancien en quelques phrases, qui remplacent les messages d’origine. On garde la substance, on perd le volume.
from langchain_core.messages import RemoveMessage
def resumer_si_long(state: Etat) -> dict:
msgs = state["messages"]
if len(msgs) <= 12:
return {}
anciens = msgs[:-6] # tout sauf les 6 derniers
resume = modele.invoke(
[{"role": "user", "content": f"Résume cette conversation :\n{rendre(anciens)}"}]
)
# supprime les anciens messages et injecte le résumé à leur place
suppressions = [RemoveMessage(id=m.id) for m in anciens]
return {"messages": [*suppressions,
{"role": "system", "content": f"Résumé des échanges : {resume.content}"}]}
RemoveMessage (avec le reducer add_messages) supprime des messages de l’état par
leur id. C’est l’outil propre pour réécrire l’historique persisté, pas
seulement la vue.
Stratégie 3 — Filtrer et déléguer à la mémoire
Tout n’a pas à vivre dans l’historique. Deux compléments :
- Filtrer les sorties d’outils volumineuses — un
ToolMessagede 4 000 tokens de documents bruts n’a pas à rester dans le contexte des tours suivants. Renvoyez un résumé au modèle et l’artefact côté programme (voircontent_and_artifact). - Déléguer à la mémoire long-terme — les faits durables n’appartiennent pas à l’historique de conversation mais au Store, d’où on les rappelle à la demande plutôt que de les traîner en permanence.
Les leviers de coût
Au-delà du contexte, quatre leviers gouvernent la facture d’un agent.
| Levier | Principe | Gain typique |
|---|---|---|
| Routage de modèles | Modèle bon marché pour le routage/la classification, modèle fort pour le raisonnement difficile | Élevé |
| Cache de prompt | Réutiliser le préfixe stable (system, outils) entre appels | Élevé sur longs prompts |
| Contexte borné | Troncature / résumé (ci-dessus) | Élevé sur longues conversations |
| Moins d’outils, boucles bornées | Réduire les tours et la taille du schéma d’outils | Moyen |
Routage de modèles
Le levier le plus rentable : ne pas payer un modèle haut de gamme pour des tâches triviales. On classe la difficulté, puis on route.
def router_modele(state: Etat) -> Literal["petit", "grand"]:
difficulte = classifieur.invoke(state["messages"][-1].content) # appel bon marché
return "grand" if difficulte.complexe else "petit"
g.add_conditional_edges("triage", router_modele, {"petit": "modele_mini", "grand": "modele_pro"})
C’est un cas direct du pattern de routing, appliqué à l’économie : la majorité du trafic part vers le petit modèle, seule la minorité difficile mobilise le grand.
Mesurer pour décider
On n’optimise pas ce qu’on ne mesure pas. LangFuse rend chaque levier chiffrable : coût moyen par requête, tokens d’entrée/sortie par nœud, latence P95. Toute optimisation se valide sur ces courbes — et se surveille pour détecter les dérives.
Bonnes pratiques & pièges
- Bornez la vue envoyée au modèle (trim_messages), pas forcément l'état persisté.
- Résumez l'historique ancien plutôt que de le jeter quand un détail du début compte.
- Maintenez un résumé courant + derniers messages : coût borné, mémoire préservée.
- Routez les modèles : petit pour le trivial, grand pour le difficile — le levier roi.
- Sortez les données volumineuses du contexte (artifacts, Store) ; rappelez à la demande.
- Mesurez chaque levier dans LangFuse et validez contre le dataset d'évaluation.
- Renvoyer tout l'historique à chaque tour : coût quadratique et qualité qui se dégrade.
- Tronquer brutalement une conversation où un détail ancien compte encore.
- Laisser des ToolMessage volumineux (documents bruts) traîner dans le contexte.
- Payer un modèle haut de gamme pour de la classification ou du routage.
- Optimiser le coût sans mesurer la qualité : on économise en cassant la fidélité.
- Ignorer le cache de prompt sur des préfixes système et schémas d'outils stables.
Ce qu’il faut retenir
- L’historique non géré rend le coût quadratique et dégrade la qualité (context rot).
- Tronquer (
trim_messages) borne la vue du modèle ; résumer (RemoveMessage+ résumé) préserve la substance. - Sortez le volumineux du contexte : artifacts et mémoire long-terme.
- Le routage de modèles est le levier de coût le plus rentable ; complétez avec le cache de prompt.
- Mesurez dans LangFuse et validez chaque optimisation contre l’évaluation — économiser ne doit jamais casser la qualité.
Vous tenez maintenant la chaîne complète de l’ingénierie agentique : concevoir, outiller, orchestrer, mémoriser, persister, sécuriser, observer, évaluer, restituer et optimiser. La théorie au service de la pratique — qui vous attend dans les cas pratiques.