La mémoire est une capacité essentielle pour les agents IA. Avec l'augmentation du nombre et de la profondeur des tours de conversation, savoir comment faire "mémoriser" le contexte passé à un agent IA est crucial pour une compréhension précise et des systèmes d'IA personnalisés. Étant donné les limitations de longueur de contexte des LLM, si la mémoire n'est pas optimisée, les longues conversations peuvent facilement entraîner deux problèmes :
- Oubli des informations antérieures, conduisant à des biais de compréhension
- Consommation excessive de ressources de calcul et de coûts
Bien qu'il existe d'excellents frameworks open-source comme Mem0, il est toujours nécessaire de comprendre les différentes stratégies de mémoire d'un point de vue théorique. Cela aide à évaluer, sélectionner et implémenter la solution de mémoire la plus appropriée dans un projet. Cet article décortique 8 schémas de mémoire courants pour les IA, analyse leurs principes, caractéristiques et scénarios d'application, et utilise du code simulé pour faciliter la compréhension.
- Mémoire Complète : Rien n'est oublié
- Fenêtre Glissante : Troncation à longueur fixe
- Filtrage par Pertinence : Oublier les informations secondaires
- Résumé / Compression : Extraire les informations clés
- Base de Données Vectorielle : Recherche sémantique de la mémoire
- Graphe de Connaissances : Mémoire structurée
- Mémoire Hiérarchique : Combiner court et long terme
- Gestion de Mémoire type OS : Simuler le principe du Swap
01 Mémoire Complète : Rien n'est oublié
Le mode de mémoire complète est la stratégie la plus basique et la plus facile à implémenter. Son idée centrale est de ne rien oublier du contexte historique. Chaque tour de conversation enregistre intégralement l'entrée de l'utilisateur et la réponse de l'agent, et lors des requêtes suivantes, tout le contexte historique est envoyé au LLM pour inférence.
Implémentation de base
Chaque tour de conversation est accumulé séquentiellement dans "l'historique de conversation". Lors de chaque réponse, l'historique complet est fourni comme contexte au modèle. Voici un code de simulation simple :
Pour aider à la compréhension, cet article simule simplement les processus de base "d'ajout de mémoire" et de "récupération de mémoire" pour chaque mode. L'application réelle nécessite une amélioration personnalisée.
historique = []
def ajouter_message(entree_utilisateur, reponse_ia):
tour = {
"utilisateur": entree_utilisateur,
"assistant": reponse_ia
}
historique.append(tour)
def obtenir_contexte(requete):
# Concaténer tout l'historique (simulation)
contexte_complet = ""
for tour in historique:
contexte_complet += f"Utilisateur: {tour['utilisateur']}\nAssistant: {tour['assistant']}\n"
return contexte_complet
Analyse des caractéristiques
- Avantages : Implémentation simple, aucun algorithme complexe requis ; conservation complète de tous les détails, aucune perte d'information.
- Inconvénients : Une conversation légèrement plus longue peut déclencher la limite de longueur du contexte. Le modèle doit traiter de plus en plus de texte, ce qui entraîne des réponses plus lentes et des coûts plus élevés. Une fois la fenêtre de contexte du modèle dépassée, le contenu précoce doit être tronqué, et des informations importantes peuvent être perdues. De plus, conserver de grandes quantités d'informations anciennes non pertinentes sur une longue période peut perturber le jugement du modèle.
Scénarios d'aplication
Applicable uniquement aux scénarios avec très peu de tours de conversatoin ou de contenu court, comme un simple QA ou une question-réponse unique. Dans ces cas, la mémoire complète garantit que l'agent n'omettra rien, même si l'utilisateur mentionne un sujet précédent. Mais il est clair que dans la plupart des applications réelles, cette stratégie n'est pas viable.
02 Fenêtre Glissante : Mémoire à longueur fixe
Pour pallier les inconvénients de la mémoire complète, la plus simple amélioration consiste à limiter la longueur de la mémoire : Dans les conversations humaines, nous avons tendance à ne nous concentrer que sur les informations récentes, et les anciens sujets s'estompent progressivement. La stratégie de fenêtre glissante imite cette caractéristique : elle ne conserve que les quelques derniers tours de conversation, oubliant le contenu plus ancien, afin de contrôler la longueur du contexte.
Principe de base
Maintenez une file d'attente de taille fixe comme fenêtre de conversation. Chaque fois qu'une nouvelle conversation est ajoutée, si elle dépasse la taille de la fenêtre, la plus ancienne entrée est supprimée de la tête de la file. Code de simulation :
memoire_glissante = []
TAILLE_FENETRE = 3 # Conserver au maximum 3 tours de conversation complets
def ajouter_message_glissant(entree_utilisateur, reponse_ia):
tour = {
"utilisateur": entree_utilisateur,
"assistant": reponse_ia
}
memoire_glissante.append(tour)
if len(memoire_glissante) > TAILLE_FENETRE:
memoire_glissante.pop(0) # Supprimer le tour de questions-réponses le plus ancien
def obtenir_contexte_glissant():
# Retourner les derniers tours de conversation (simulation)
contexte = ""
for tour in memoire_glissante:
contexte += f"Utilisateur: {tour['utilisateur']}\nAssistant: {tour['assistant']}\n"
return contexte
Ainsi, quelle que soit la longueur de la conversation, le contexte transmis au modèle est toujours l'enregistrement des N dernières interactions. La fenêtre glisse vers l'avant, le nouveau entrant et l'ancien sortant, réalisant la troncature des conversations passées.
Analyse des caractéristiques
- Avantages : Très simple à implémenter, faible coût, pas besoin de stockage externe ; assure que le contexte du modèle reste toujours dans une taille définie, la vitesse de réponse et le coût sont relativement contrôlables.
- Inconvénients : Forte tendance à l'oubli, une fois la fenêtre glissée, les anciennes informations sont définitivement perdues, ce qui ne permet pas une véritable mémoire à long terme. Si l'utilisateur mentionne à nouveau un contenu plus ancien, l'agent ne pourra pas le corréler car il l'a déjà oublié. De plus, la taille de la fenêtre est difficile à choisir : trop petite, elle oublie l'historique trop tôt ; trop grande, elle réduit l'utilité de la préservation du contexte.
Scénarios d'application
La fenêtre glissante est adaptée aux scénarios de conversation courte ou aux tâches où la dépendance historique est faible. Par exemple, un assistant FAQ, un robot de discussion simple, etc., n'ont pas besoin de se souvenir des sujets précédents pendant longtemps. Essentiellement, vous devez faire un compromis : échanger l'oubli contre la performance et le coût, mais cela ne convient pas aux conversations nécessitant une dépendance à long terme.
03 Filtrage par Pertinence : Oublier les informations secondaires
Les humains se souviennent sélectivement et oublient rapidement les choses sans importance. De même, la mémoire d'un agent peut être sélective : privilégier la conservation des informations importantes et ignorer les détails inutiles. La stratégie de filtrage par pertinence gère la mémoire en fonction de l'importance de l'information, plutôt que de simplement abandonner les anciennes mémoires.
Principe de base
Le système attribue un score d' "importance" ou de "pertinence" à chaque élément de mémoire. La conservation ou la suppression est décidée en fonction de ce score. Lorsque de nouvelles informations entrent et dépassent la capacité, les mémoires dont le score est le plus bas sont automatiquement supprimées.
Le score peut être basé sur plusieurs facteurs : le degré de pertinence par rapport au sujet de conversation actuel, la fréquence à laquelle il a été mentionné récemment, l'importance intrinsèque de l'information (par exemple, une phrase contenant les préférences clés de l'utilisateur reçoit un score élevé), etc.
Pour l'implémentation, on peut utiliser une liste ou une file d'attente prioritaire triée par score. Simulation :
memoire_pertinente = []
MAX_ELEMENTS = 25
def evaluer_pertinence(entree, reponse):
# Simulation: une fonction hypothétique pour évaluer l'importance
# Dans une vraie application, cela pourrait utiliser des embeddings ou des règles
return len(entree) + len(reponse) # Exemple simpliste
def ajouter_message_pertinent(entree_utilisateur, reponse_ia):
score = evaluer_pertinence(entree_utilisateur, reponse_ia)
tour = {
"utilisateur": entree_utilisateur,
"assistant": reponse_ia,
"score": score
}
memoire_pertinente.append(tour)
if len(memoire_pertinente) > MAX_ELEMENTS:
# Trouver l'élément avec le score le plus bas
a_supprimer = min(memoire_pertinente, key=lambda x: x["score"])
memoire_pertinente.remove(a_supprimer)
def obtenir_contexte_pertinent(requete):
# Retourner les mémoires bien notées triées par ordre chronologique (simulation)
contexte = ""
memoires_triees = sorted(memoire_pertinente, key=lambda x: x["timestamp"]) # Supposons un timestamp ajouté
for tour in memoires_triees:
contexte += f"Utilisateur: {tour['utilisateur']}\nAssistant: {tour['assistant']}\n"
return contexte
Analyse des caractéristiques
- Avantages : Garantit que les connaissances clés ne sont pas oubliées, car le contenu important obtient un score plus élevé. Comparé à la troncature aveugle par fenêtre, cette stratégie est plus "intelligente", libérant de l'espace tout en essayant de ne pas perdre d'informations clés.
- Inconvénients : La difficulté réside dans l'évaluation précise de "l'importance", qui peut nécessiter des calculs de similarité sémantique supplémentaires par un modèle ou des règles prédéfinies. Si le mécanisme de notation est imparfait, il peut mal juger l'importance et supprimer des mémoires erronées. De plus, contrairement à la fenêtre glissante, elle n'est pas prévisible, ce qui complique le débogage et la compréhension.
Scénarios d'application
Adapté aux scénarios riches en informations et nécessitant une sélection, tels que les robots de conversation axés sur le savoir ou les outils d'assistance à la recherche. Dans ces applications, l'agent doit faire le tri parmi la grande quantité d'informations fournies par l'utilisateur. Par exemple : Un assistant médical intelligent extrait les points clés de l'historique médical de la longue description d'un patient pour le stockage.
04 Résumé / Compression : Extraire les informations clés
Existe-t-il un moyen de raccourcir la longueur de la conversation sans perdre d'informations importantes ? La stratégie de résumé répond à ce besoin. Son objectif est de résumer la longue conversation en points clés (faits, données clés, passe-temps, etc.) en supprimant les informations inutiles (salutations, bavardages, informations répétées, etc.), comme un humain prenant des notes. Cela permet de conserver les informations essentielles tout en économisant considérablement l'espace de la fenêtre de contexte et en atténuant le problème de la croissance infinie de la mémoire.
Dans l'implémentation réelle, on peut combiner la stratégie de fenêtre glissante : seules les conversations dépassant la fenêtre sont résumées et compressées.
Principe de base
Périodiquement, au cours de la conversation, le contenu des conversations plus anciennes est résumé et compressé, et ce résumé remplace le contenu détaillé d'origine stocké dans la mémoire. Le résumé peut être généré par un LLM. Par exemple, chaque fois que la conversation dépasse une longueur prédéfinie (taille de la fenêtre), les premiers tours sont résumés. Simulation :
memoire_resume = []
resume_global = None
MAX_TOURS = 10 # Conserver au maximum 10 tours de questions-réponses
def resumer_dialogue(dialogues):
# Simulation: une fonction hypothétique pour résumer une liste de tours de dialogue
texte_dialogue = "\n".join([f"Utilisateur: {t['utilisateur']}\nAssistant: {t['assistant']}" for t in dialogues])
# Ici, on simule un résumé simple, en réalité on utiliserait un LLM
return f"Résumé de: {texte_dialogue[:50]}..." # Premier 50 caractères comme résumé
def fusionner_resumes(resume_existant, nouveau_resume):
# Simulation: fusionner deux résumés
if resume_existant is None:
return nouveau_resume
return f"{resume_existant}\n{nouveau_resume}"
def ajouter_message_resume(entree_utilisateur, reponse_ia):
global resume_global
tour = {
"utilisateur": entree_utilisateur,
"assistant": reponse_ia
}
memoire_resume.append(tour)
if len(memoire_resume) > MAX_TOURS:
tours_a_resumer = memoire_resume[:-5] # Résumer les 5 tours les plus anciens
nouveau_resume = resumer_dialogue(tours_a_dupliquer)
resume_global = fusionner_resumes(resume_global, nouveau_resume)
memoire_resume.clear()
# Stocker le résumé et les derniers tours
memoire_resume.append({"resume": resume_global})
memoire_resume.extend(memoire_resume[-5:]) # Conserver les 5 derniers tours originaux
def obtenir_contexte_resume():
# Retourner le résumé + les derniers tours de conversation (simulation)
contexte = ""
for item in memoire_resume:
if "resume" in item:
contexte += f"[Résumé du passé]: {item['resume']}\n"
else:
contexte += f"Utilisateur: {item['utilisateur']}\nAssistant: {item['assistant']}\n"
return contexte
Ceci illustre un mécanisme de résumé en cours d'exécution : accumulation et mise à jour continues d'un résumé pour représenter l'historique des conversations antérieures. Chaque fois que la longueur de la mémoire dépasse la limite, le contenu précédent est résumé et compressé, puis il forme, avec les conversations ultérieures, un nouveau contexte. De cette façon, le contexte du modèle comprend toujours le "résumé des conversations anciennes + conversations récentes".
Analyse des caractéristiques
- Avantages : Réduit considérablement la longueur du contexte, forte capacité de mémoire à long terme - théoriquement, par résumé continu, les points clés des informations antérieures peuvent être conservés indéfiniment. De plus, le contenu du résumé est concis, aidant le modèle à se concentrer sur les informations clés.
- Inconvénients : La qualité du résumé dépend du LLM et peut omettre des détails ou introduire des biais d'information. Si le résumé n'est pas précis, la génération ultérieure de l'agent basée sur le résumé peut être erronée. De plus, la génération du résumé elle-même nécessite des calculs supplémentaires et a un impact sur le délai des conversations en temps réel.
Scénarios d'application
Le résumé de mémoire est adapté aux scénarios de conversation longue nécessitant la conservation des points clés du contexte : l'agent doit mémoriser les informations clés de l'utilisateur (nom, préférences, requêtes, etc.) mais pas nécessairement chaque mot de chaque phrase de l'utilisateur. Par exemple, un assistant de compagnie psychologique par IA. L'IA peut utiliser une stratégie de résumé, résumant les points clés de chaque conversation après sa fin et les stockant. La prochaine fois, elle pourra passer en revue les principaux problèmes et changements d'humeur mentionnés par l'utilisateur grâce au résumé précédent, fournissant ainsi une réponse continue.
05 Base de Données Vectorielle : Recherche sémantique de la mémoire
Pour une mémoire à long terme massive, une solution idéale est de stocker les connaissances dans une base de données externe et de les récupérer lorsque nécessaire. C'est similaire à un humain consultant des notes ou une base de données. La stratégie de mémoire par base de données vectorielle utilise des plongements vectoriels (Embedding) pour stocker le contenu de la conversation dans une base de données vectorielle et rechercher les mémoires pertinentes par recherche sémantique lorsque nécessaire. Son objectif est de surmonter la limitation de la fenêtre de contexte du LLM et de réaliser une mémoire externe à long terme quasi illimitée.
Principe de base
Chaque conversation est intégrée (embedded) puis stockée dans une base de données vectorielle comme Chroma, Pinecone, etc. Lorsque la mémoire est nécessaire, le contenu de la conversation actuelle est également intégré, et les éléments de mémoire les plus pertinents sont recherchés dans la base de données par similarité sémantique. Ces éléments sont ensuite ajoutés au contexte du modèle. Simulation :
# Initialisation du stockage vectoriel
class VectorStore:
def __init__(self):
self.embeddings = []
self.documents = []
self.next_id = 0
def embedder(self, text):
# Simulation simple: utiliser la longueur comme "embedding"
return [len(text)]
def add(self, document):
embedding = self.embedder(document)
self.embeddings.append(embedding)
self.documents.append(document)
doc_id = self.next_id
self.next_id += 1
return doc_id
def search(self, query_text, top_k=3):
query_embedding = self.embedder(query_text)
# Calcul de similarité cosinus simulé (ici, juste la différence absolue)
similarities = []
for i, emb in enumerate(self.embeddings):
# Simulation simpliste de similarité
similarity = -abs(query_embedding[0] - emb[0])
similarities.append((similarity, i))
similarities.sort(key=lambda x: x[0], reverse=True)
results = []
for score, idx in similarities[:top_k]:
results.append(self.documents[idx])
return results
memoire_vectorielle = VectorStore()
def ajouter_message_vectoriel(entree_utilisateur, reponse_ia):
tour = f"Utilisateur: {entree_utilisateur}\nAssistant: {reponse_ia}"
memoire_vectorielle.add(tour)
def obtenir_contexte_vectoriel(requete):
resultats = memoire_vectorielle.search(requete, top_k=3)
contexte = ""
for doc in resultats:
contexte += doc + "\n"
return contexte
Analyse des caractéristiques
- Avantages : Recherche intelligente au niveau sémantique, capable de trouver des mémoires pertinentes basées sur la sémantique du contenu plutôt que sur la correspondance de mots-clés. Grande capacité de stockage, les bases de données vectorielles peuvent s'étendre indéfiniment pour prendre en charge une véritable mémoire à long terme, et l'efficacité de la recherche est élevée.
- Inconvénients : Dépend de la qualité du modèle d'intégration (embedding model) ; si la représentation vectorielle n'est pas bonne, les résultats de recherche peuvent être hors sujet. Le stockage et la recherche vectoriels ont un certain coût de calcul. Lorsque la base de mémoire est grande, chaque calcul de similarité consomme également de la puissance de calcul. De plus, il faut déployer et maintenir un service de base de données supplémentaire, ce qui augmente la complexité du système.
Scénarios d'application
Systèmes de conversation nécessitant une mémoire à long terme, tels que les assistants personnalisés. Ces systèmes doivent souvent mémoriser les informations fournies par l'utilisateur au fil des conversations, et il est également très approprié de stocker des connaissances ou le contexte de l'utilisateur en dehors des discussions (grâce à la base de données vectorielle), permettant ainsi à la recherche de mémoire d'avoir un effet similaire à RAG (Retrieval-Augmented Generation). Par exemple : Un IA de conseil juridique, lorsqu'un utilisateur pose une question juridique complexe, l'IA peut simultanément rechercher les mémoires pertinentes et les connaissances juridiques pour l'enrichissement de la génération.
06 Graphe de Connaissances : Mémoire structurée
Les systèmes de mémoire basés uniquement sur la similarité vectorielle considèrent souvent les connaissances comme un contenu discret, sans comprendre les relations entre les connaissances. La stratégie de mémoire par graphe de connaissances vise à stocker et organiser les informations mémorisées de manière structurée, en améliorant la mémoire structurée à long terme et la capacité de raisonnement de l'agent grâce à des entités, attributs et relations explicites.
Principe de base
L'agent extrait les informations factuelles telles que les entités, attributs et relations mentionnées dans les conversations et les interactions, et construit progressivement un graphe de connaissances. Par exemple, si une conversation contient "Xiao Liu a rejoint Alibaba Company", alors les triplets sont extraits : (Xiao Liu, est employé par, Alibaba Company). En outre, on peut enregistrer le momant où l'événement s'est produit (formant un graphe de connaissances temporel pour gérer les connaissances qui changent avec le temps).
Lorsque la recherche de mémoire est nécessaire, l'agent peut interroger le graphe de connaissances : par exemple, trouver d'abord les nœuds connectés, remonter les informations le long de la chaîne relationnelle, voire effectuer un raisonnement logique sur le chemin, puis ajouter les informations interrogées ou déduites au contexte.
import networkx as nx
class KnowledgeGraph:
def __init__(self):
self.graph = nx.DiGraph()
def add_edge(self, source, target, relation):
self.graph.add_edge(source, target, relation=relation)
def query(self, entity):
# Simulation: trouver tous les voisins et leurs relations
context = []
if entity in self.graph:
for neighbor in self.graph.neighbors(entity):
relation = self.graph.get_edge_data(entity, neighbor)['relation']
context.append(f"({entity}, {relation}, {neighbor})")
# On peut aussi chercher les entités qui pointent vers celle-ci
for pred in self.graph.predecessors(entity):
relation = self.graph.get_edge_data(pred, entity)['relation']
context.append(f"({pred}, {relation}, {entity})")
return context
kg = KnowledgeGraph()
def extraire_triplets_simples(texte):
# Simulation très simple : rechercher des motifs "Entité1 Relation Entité2"
triplets = []
if "joint" in texte and "company" in texte:
# Exemple très basique
parts = texte.split("joint")
if len(parts) == 2:
entite1 = parts[0].strip().split(" ")[-1] # Dernier mot avant 'joint'
entite2 = parts[1].strip().split(" company")[0] # Texte avant ' company'
triplets.append((entite1, "joint", entite2))
return triplets
def ajouter_message_kg(entree_utilisateur, reponse_ia):
texte_complet = f"Utilisateur: {entree_utilisateur}\nAssistant: {reponse_ia}"
triplets = extraire_triplets_simples(texte_complet) # LLM extrait les triplets
for s, r, o in triplets:
kg.add_edge(s.strip(), o.strip(), relation=r.strip())
def obtenir_contexte_kg(requete):
# Extraire les entités de la requête (simulation)
entites_requete = []
if "Alibaba" in requete:
entites_requete.append("Alibaba")
contexte = []
for e in entites_requete:
contexte.extend(kg.query(e)) # Interroger le graphe pour les informations liées à l'entité
return "\n".join(contexte)
Analyse des caractéristiques
- Avantages : En structurant la mémoire, l'agent peut effectuer des recherches et des inférences plus précises. La mémoire structurée permet à l'IA de ne plus simplement trouver des paragraphes par similarité, mais de répondre à des questions complexes basées sur le graphe (par exemple, raisonnement multi-sauts). Le graphe de connaissances est également interprétable, le chemin d'interrogation est clair et traçable, ce qui est précieux dans les applications nécessitant une origine précise.
- Inconvénients : Coût de construction et de maintenance élevé : nécessite un LLM pour extraire les connaissances, ce qui peut être sujet à erreurs ou incomplet ; un grand graphe de connaissances peut également poser des problèmes de performance de recherche et de stockage. De plus, le graphe de connaissances est bon pour le raisonnement factuel explicite, mais pour la correspondance sémantique floue, il peut nécessiter une recherche vectorielle conjointe.
Scénarios d'application
Adapté aux applications riches en connaissances et aux agents nécessitant un raisonnement inter-événements. Par exemple, une IA de support client d'entreprise doit comprendre la corrélation entre les requêtes historiques de l'utilisateur et les informations sur le compte, les commandes, etc., ou une IA d'assistance à la recherche doit clarifier les relations conceptuelles dans les articles.
07 Mémoire Hiérarchique : Combiner court et long terme
La mémoire humaine a une division hiérarchique : certains contenus sont oubliés en un instant (comme une phrase que l'on vient d'entendre), d'autres sont mémorisés à court terme (comme les points clés d'une réunion d'aujourd'hui), et les contenus vraiment importants (comme l'adresse familiale, la date d'anniversaire) sont conservés à long terme.
La stratégie de mémoire hiérarchique vise à construire une structure de mémoire similaire au "cerveau humain" : stocker différents types d'informations et d'importances dans des systèmes de stockage à différents niveaux, permettant à l'agent de "traiter les symptômes avec le bon médicament" face à différents scénarios.
Principe de base
Cette stratégie divise le système de mémoire en plusieurs niveaux :
- Mémoire de travail (court terme) : Conserve les derniers tours de conversation, se met à jour fréquemment, petite capacité, gérée par fenêtre glissante.
- Mémoire à long terme (recherchable) : Stocke les informations importantes après intégration, prend en charge la recherche inter-conversations et à long terme.
- Mécanisme d'amélioration : Par exemple, lorsque l'utilisateur dit des mots-clés comme "Retiens-moi XXX", "Je toujours", "Je suis allergique" dans la conversation (peut également être fait via LLM), le système élève ces informations au niveau de la mémoire à long terme pour une utilisation future.
Lors de la récupération, le système obtient le contexte actuel de la mémoire à court terme, puis recherche la mémoire historique dans la mémoire à long terme basée sur la similarité sémantique, et combine le tout pour former une invite riche à traiter par le LLM.
Cette stratégie est essentiellement une combinaison de fenêtre glissante + base de données vectorielle + jugement d'importance.
# Supposons que SlidingWindow et VectorDatabase sont des classes déjà définies
# comme dans les sections précédentes, avec des méthodes add() et get_context()/search()
class SlidingWindowMemory:
def __init__(self, max_turns=2):
self.max_turns = max_turns
self.history = [] # Stockera des tuples (utilisateur, assistant)
def add(self, user_input, ai_response):
self.history.append((user_input, ai_response))
if len(self.history) > self.max_turns:
self.history.pop(0)
def get_context(self):
return "\n".join([f"Utilisateur: {u}\nAssistant: {a}" for u, a in self.history])
class VectorDatabaseMemory:
def __init__(self, k=2):
self.k = k
self.vector_store = VectorStore() # Utilisation de notre classe de simulation VectorStore
def add(self, embedding, document_text):
# Dans notre simulation, add prend directement le texte
self.vector_store.add(document_text)
def search(self, query_text):
return self.vector_store.search(query_text, top_k=self.k)
# Paramètres pour la mémoire hiérarchique
memoire_court_terme = SlidingWindowMemory(max_turns=2)
memoire_long_terme = VectorDatabaseMemory(k=2)
mots_cles_promotion = ["Retiens", "toujours", "jamais", "allergique", "mon ID est", "j'aime", "je déteste"]
def ajouter_message_hierarchique(entree_utilisateur, reponse_ia):
memoire_court_terme.add(entree_utilisateur, reponse_ia)
# Si l'entrée de l'utilisateur contient des mots-clés suggérant de mémoriser ;
# en réalité, la stratégie peut être plus complexe.
if any(keyword in entree_utilisateur.lower() for keyword in mots_cles_promotion):
# Simulation de résumé pour le stockage long terme
resume = f"Info utilisateur importante: {entree_utilisateur} - {reponse_ia}"
# Dans une vraie implémentation, on ferait un embedding ici
memoire_long_terme.add(None, resume) # L'embedding est géré dans VectorStore simulation
def obtenir_contexte_hierarchique(requete):
# Obtenir le contexte à court terme
recent = memoire_court_terme.get_context()
# Interroger la mémoire à long terme pour le contenu pertinent
# Dans la simulation, le search de VectorStore prend du texte
related = memoire_long_terme.search(requete)
# Assembler le contexte pour l'invite d'entrée
contexte_long_terme_str = "\n".join(related)
return f"【Mémoire à Long Terme】\n{contexte_long_terme_str}\n\n【Contexte Actuel】\n{recent}"
Analyse des caractéristiques
- Avantages : Combine les avantages de la mémoire à court et à long terme, répondant rapidement aux informations récentes et récupérant les informations historiques sur demande ; même si la mémoire à court terme est automatiquement oubliée, la mémoire importante à long terme reste consultable.
- Inconvénients : L'implémentation est plus complexe, impliquant plusieurs modules (gestion de fenêtre, intégration, rappel, etc.) ; le coût d'optimisation est également plus élevé, par exemple, les mots-clés ou le jugement d'importance, la qualité de l'intégration, la précision du rappel, la stratégie de combinaison du contexte nécessitent tous un débogage.
Scénarios d'application
Adapté aux systèmes d'agents nécessitant une perception contextuelle à long terme. Par exemple, un agent de service client d'entreprise, dont les commandes et préférences précédentes de l'utilisateur doivent être mémorisées ; ou un assistant personnel de type IA, mémorisant les arrangements de calendrier, les informations sur les membres de la famille sur plusieurs jours, voire plusieurs mois ; ou encore des scénarios d'enseignement/médicaux nécessitant de revoir les questions-réponses importantes passées, les conseils de diagnostic, les habitudes d'apprentissage, etc.
08 Gestion de Mémoire type OS : Simuler le mémoire Swap
Dans un ordinateur, le système d'exploitation gère efficacement la mémoire physique limitée (RAM) et le disque de grande capacité mais lent grâce à une combinaison de "mémoire principale + disque dur". En s'inspirant de ce mécanisme, on peut également construire un système de mémoire pour les agents qui imite la gestion de mémoire de l'OS : utiliser la fenêtre de contexte limitée comme RAM, et stocker les informations dépassant le contexte dans un stockage externe (Page Out), puis les "échanger" à nouveau si nécessaire (Page In).
Ce mode est un peu similaire au mode de mémoire hiérarchique, mais la différence est :
- La mémoire hiérarchique ne déplace que les informations clés vers une mémoire secondaire ; ici, tout ce qui dépasse la fenêtre est Page Out.
- La mémoire hiérarchique récupère directement la mémoire secondaire ; ici, elle doit être Page In pour devenir une mémoire active.
Principe de base
Cette stratégie comporte deux niveaux :
- Mémoire active : Utilise une fenêtre glissante pour conserver les conversations récentes. L'accès est rapide mais la capacité est limitée.
- Mémoire passive : Lorsque la mémoire active est pleine, le contenu le plus ancien est "échangé" vers le stockage externe. Ces informations, bien que n'étant pas directement dans le contexte actuel du modèle, peuvent toujours être récupérées à tout moment.
Cette stratégie a un mécanisme de "page fault" : lorsque la requête de l'utilisateur contient des mots-clés dont les informations nécessaires ne sont pas dans la RAM actuelle, le système déclenche un "Page Fault", recherche le contenu correspondant dans la mémoire passive, et "page in" le contexte pour le LLM.
Cette stratégie imite essentiellement le principe de gestion de mémoire virtuelle de l'OS, c'est-à-dire l'utilisation contextuelle "données chaudes/froides hiérarchisées". Simulation :
from collections import deque
class OSMemoryManager:
def __init__(self, active_max_len=2):
self.active_memory = deque(maxlen=active_max_len) # Contexte rapide mais petit
self.passive_memory = {} # Stockage persistant de mémoire passive
self.turn_counter = 0 # Compteur unique pour chaque tour de dialogue
def add_message(self, user_input, ai_response):
current_turn_id = self.turn_counter
turn_content = f"Utilisateur: {user_input}\nAssistant: {ai_response}"
# Page Out si la mémoire active est pleine
if len(self.active_memory) >= self.active_memory.maxlen:
old_id, old_content = self.active_memory.popleft()
self.passive_memory[old_id] = old_content # Page out vers le stockage passif
self.active_memory.append((current_turn_id, turn_content))
self.turn_counter += 1
def get_context(self, query):
# Contexte de la mémoire active
active_context = "\n".join([content for _, content in self.active_memory])
paged_in_context = ""
query_words = set(word for word in query.lower().split() if len(word) > 3)
# Simulation du déclenchement de Page In basée sur les mots-clés
for turn_id, turn_content in self.passive_memory.items():
turn_words = set(word for word in turn_content.lower().split() if len(word) > 3)
# Si une partie des mots de la requête est dans le contenu du tour passif
if query_words.intersection(turn_words):
# Ici, on simule le Page In
paged_in_context += f"\n(Paged in from Turn {turn_id}): {turn_content}"
# Dans une vraie implémentation, on pourrait ajouter 'turn_content' à la mémoire active
# et potentiellement le supprimer de passive_memory ou le marquer comme "hot".
return f"### Mémoire Active (RAM):\n{active_context}\n\n### Page In depuis le Disque:\n{paged_in_context}"
memory_manager = OSMemoryManager(active_max_len=2)
# Utilisation :
# memory_manager.add_message("Bonjour", "Bonjour ! Comment puis-je vous aider ?")
# memory_manager.add_message("Quel temps fait-il ?", "Je ne peux pas consulter la météo.")
# memory_manager.add_message("Je veux parler de mes allergies.", "Oh, je suis désolé d'entendre ça. Dites m'en plus.") # Ce tour sera Page Out
# print(memory_manager.get_context("Parlez-moi de mes allergies"))
Analyse des caractéristiques
- Avantages : Le principal avantage de cette stratégie réside dans sa structure claire et sa conformité aux principes informatiques. En divisant la conversation en deux couches - "données chaudes (contexte actuel)" et "données froides (stockage externe)" - elle atténue efficacement la limitation de la fenêtre de contexte. Elle peut également rappeler des informations importantes du passé lorsque nécessaire, améliorant considérablement la flexibilité et l'extensibilité du système de mémoire.
- Inconvénients : L'implémentation nécessite de simuler la logique de "page in/out" et de s'assurer de la précision du moment du déclenchement (par exemple, en fonction de mots-clés ou de similarité vectorielle). Si le mécanisme de déclenchement n'est pas bien conçu, cela peut entraîner un rappel d'informations tardif ou manqué, affectant la cohérence de la conversation. De plus, le mécanisme de "pagination" exige une logique de concaténation de contexte bien conçue.
Scénarios d'application
Applicable aux systèmes d'agents dont la fenêtre de contexte est limitée mais qui nécessitent une mémoire à long terme. Elle permet de conserver une grande quantité d'informations historiques tout en maintenant la vitesse de réponse de la conversation. Elle convient aux assistants de tâches à faible latence et à longue période, ainsi qu'aux scénarios nécessitant de revenir à d'anciennes informations : lorsque l'utilisateur pose une question impliquant du contenu passé, le système peut rapidement "réveiller" la mémoire "échangée", réalisant une gestion de mémoire efficace et économe en ressources.