L’initiation à la mémoire a montré la mémoire court-terme : le checkpointer conserve l’historique d’un thread. Mais que se passe-t-il quand l’utilisateur revient demain, dans une nouvelle conversation ? Le thread est différent, le checkpointer ne sait rien. L’agent a tout oublié.

La mémoire long-terme résout cela. Elle vit dans un Store distinct du checkpointer : une base de connaissances persistante, partagée entre tous les threads, où l’agent range ce qu’il doit retenir durablement — préférences, faits, historique épisodique.

Fig.01 · Deux mémoires
état du thread recall · write (sémantique) agent COURT-TERME · checkpointer · thread courant LONG-TERME · Store · transverse aux threads (user, prefs) (user, faits) (org, kb)
Le checkpointer mémorise la conversation en cours (un thread). Le Store mémorise ce qui doit survivre aux conversations — transverse, namespacé, et souvent indexé sémantiquement.

Le Store : namespaces, clés, valeurs

Un Store est un dictionnaire hiérarchique et persistant. On y range des items identifiés par un namespace (un tuple, qui agit comme un dossier) et une clé. La valeur est un dictionnaire JSON.

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

# namespace = (domaine, identifiant) ; clé = nom de l'item
store.put(("users", "alice"), "profil", {"prenom": "Alice", "langue": "fr"})
store.put(("users", "alice"), "fait-1", {"texte": "Préfère les réponses concises."})

# lecture directe
item = store.get(("users", "alice"), "profil")
print(item.value)        # {"prenom": "Alice", "langue": "fr"}

# lister tout un namespace
for it in store.search(("users", "alice")):
    print(it.key, it.value)

Le namespace est la clé de l’isolation : ("users", "alice") et ("users", "bob") sont étanches, exactement comme les threads l’étaient pour le court-terme. On peut aussi structurer plus finement : ("users", user_id, "episodes").

Recherche sémantique : retrouver par le sens

Lister un namespace suffit pour un profil. Mais pour une mémoire qui grossit (des dizaines de faits), on veut retrouver les items pertinents pour la situation courante — pas tous. Le Store supporte la recherche vectorielle : on l’indexe avec un modèle d’embeddings, puis on cherche par requête en langage naturel.

from langchain.embeddings import init_embeddings
from langgraph.store.memory import InMemoryStore

store = InMemoryStore(index={
    "embed": init_embeddings("openai:text-embedding-3-small"),
    "dims": 1536,
    "fields": ["texte"],     # quels champs de la valeur sont vectorisés
})

store.put(("users", "alice"), "f1", {"texte": "Travaille dans la fintech."})
store.put(("users", "alice"), "f2", {"texte": "Allergique aux réponses trop longues."})

# recherche par similarité sémantique
resultats = store.search(("users", "alice"), query="style de réponse souhaité", limit=3)
for r in resultats:
    print(r.value["texte"], r.score)

Accéder au Store depuis un nœud

À l’exécution, LangGraph injecte le Store dans les nœuds qui le demandent — comme pour l’état. Le nœud recharge la mémoire pertinente et l’injecte dans le prompt.

from langgraph.store.base import BaseStore
from langchain_core.runnables import RunnableConfig

def repondre(state: Etat, config: RunnableConfig, *, store: BaseStore) -> dict:
    user_id = config["configurable"]["user_id"]
    ns = ("users", user_id)

    # 1) recall : retrouver les souvenirs pertinents
    souvenirs = store.search(ns, query=state["messages"][-1].content, limit=5)
    contexte = "\n".join(s.value["texte"] for s in souvenirs)

    # 2) injecter dans le prompt
    sys = f"Ce que tu sais sur l'utilisateur :\n{contexte}"
    reponse = modele.invoke([{"role": "system", "content": sys}, *state["messages"]])
    return {"messages": [reponse]}

On branche le Store à la compilation, à côté du checkpointer :

agent = graphe.compile(checkpointer=checkpointer, store=store)
agent.invoke({"messages": [msg]}, {"configurable": {"thread_id": "t1", "user_id": "alice"}})

Écrire la mémoire depuis un outil

Pour que l’agent décide lui-même quoi retenir, on lui donne un outil d’écriture. Le Store s’injecte dans un outil via InjectedStore — invisible pour le modèle, comme vu dans le deep-dive tool calling.

from typing_extensions import Annotated
from langchain_core.tools import tool, InjectedToolCallId
from langgraph.prebuilt import InjectedStore
from langgraph.store.base import BaseStore

@tool
def memoriser(
    fait: str,
    config: RunnableConfig,
    store: Annotated[BaseStore, InjectedStore],
) -> str:
    """Mémorise durablement un fait important sur l'utilisateur."""
    user_id = config["configurable"]["user_id"]
    cle = f"fait-{uuid4().hex[:8]}"
    store.put(("users", user_id), cle, {"texte": fait})
    return "Mémorisé."

L’agent appelle memoriser("Préfère le tutoiement") quand il juge l’information durable. Au prochain thread, le nœud de recall la retrouvera.

Fig.02 · Cycle recall / write
thread message entrant
long-terme recall store.search()
LLM répond contexte injecté
long-terme write store.put()
À chaque tour : on recharge les souvenirs pertinents (recall) avant de répondre, et on écrit les nouveaux faits durables (write). Le Store fait le pont entre les conversations.

Quand écrire : hot path ou arrière-plan

Deux stratégies, avec un compromis net.

  • Dans le hot path — l’agent décide de mémoriser pendant la conversation (via l’outil). Simple, immédiat, mais ajoute de la latence et des tokens à chaque tour.
  • En arrière-plan — un processus séparé analyse les conversations terminées et en extrait les faits à retenir. Aucune latence pour l’utilisateur, mémoire plus réfléchie, mais infrastructure supplémentaire et mémoire « en retard ».

Production : le PostgresStore

InMemoryStore perd tout au redémarrage. En production, on utilise un backend durable — PostgresStore (avec pgvector pour la recherche sémantique) partage la même API.

from langgraph.store.postgres import PostgresStore

with PostgresStore.from_conn_string(DB_URI) as store:
    store.setup()       # crée les tables (et l'index vectoriel) — une seule fois
    agent = graphe.compile(checkpointer=checkpointer, store=store)

RAG vs mémoire long-terme

Les deux récupèrent du contexte par recherche sémantique — la confusion est fréquente. La distinction est d’usage : le RAG puise dans une base de connaissances partagée et figée (la doc produit) ; la mémoire long-terme écrit et relit des faits propres à un utilisateur, qui évoluent. Techniquement proches, ils répondent à des besoins différents et coexistent souvent dans le même agent.

Bonnes pratiques & pièges

Bonnes pratiques
  • Structurez les namespaces par portée : (users, id), (users, id, episodes), (org, kb).
  • Faites du recall ciblé par recherche sémantique (limit faible), pas un dump de toute la mémoire.
  • Laissez l'agent décider quoi mémoriser via un outil — mais validez/dédupliquez les faits.
  • Backend durable (PostgresStore + pgvector) en production ; InMemoryStore pour les tests.
  • Prévoyez l'effacement (store.delete) et le cloisonnement : c'est de la donnée personnelle.
Pièges à éviter
  • Confondre Store et checkpointer : l'un est transverse, l'autre lié au thread_id.
  • Injecter toute la mémoire dans le prompt : explosion de tokens et bruit pour le modèle.
  • Indexer des champs non sémantiques (ids, dates) : la recherche vectorielle devient inutile.
  • Mémoriser sans dédupliquer : la même préférence répétée 20 fois pollue le recall.
  • Oublier que la mémoire long-terme stocke des données personnelles (RGPD, effacement).

Ce qu’il faut retenir

  • La mémoire long-terme vit dans un Store, distinct du checkpointer et transverse aux threads.
  • Items rangés par namespace (tuple) + clé ; isolation par namespace.
  • La recherche sémantique (store.search(query=…)) retrouve les souvenirs pertinents au lieu de tout charger.
  • Le Store s’injecte dans les nœuds (*, store) et les outils (InjectedStore).
  • Écrire dans le hot path pour démarrer ; en arrière-plan à l’échelle.
  • En production : PostgresStore, et une vraie politique de données personnelles.

Une mémoire qui grandit, des outils qui agissent, des flux qui se ramifient : plus un agent devient capable, plus il faut mesurer s’il fait bien son travail. C’est l’objet du deep-dive sur l’évaluation avec LangFuse.

#langgraph#memoire#store#rag#production