À 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.

Fig.01 · Gérer la fenêtre
brut Historique croît sans fin
borne Gestion trim / résumé / filtre
net Contexte taille maîtrisée
appel Modèle moins cher, plus net
Entre l'historique brut et le modèle, une étape de gestion : tronquer, résumer ou filtrer. C'est elle qui borne le coût et préserve la qualité.

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 ToolMessage de 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 (voir content_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.

LevierPrincipeGain typique
Routage de modèlesModèle bon marché pour le routage/la classification, modèle fort pour le raisonnement difficileÉlevé
Cache de promptRé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éesRéduire les tours et la taille du schéma d’outilsMoyen

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

Bonnes pratiques
  • 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.
Pièges à éviter
  • 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.

#langgraph#contexte#couts#tokens#production