AnleitungFortgeschritten

Summary Memory : Zusammenfassen, um langfristig zu behalten

28. März 2026
18 Minuten Lesezeit
Equipe Ailog

Umfassender Leitfaden zur Implementierung eines zusammenfassungsbasierten Speichers in einem RAG-System: lange Gespräche zusammenfassen, um den Kontext zu bewahren, ohne die Anzahl der Tokens in die Höhe zu treiben.

Summary Memory : Resumer pour memoriser longtemps

La Summary Memory est une technique avancee de memoire conversationnelle qui condense l'historique des echanges en resumes successifs. Elle permet de maintenir le contexte sur des dizaines, voire des centaines d'echanges, sans exploser le budget tokens. Ce guide explique comment l'implementer efficacement.

Pourquoi la Summary Memory ?

Le probleme des longues conversations

Avec un Buffer Memory classique, chaque message consomme des tokens. Apres 15-20 echanges, on atteint rapidement les limites :

┌─────────────────────────────────────────────────────────────┐
│               PROBLEME DU BUFFER MEMORY                      │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  Echange 1-5:    ~800 tokens    ██░░░░░░░░                  │
│  Echange 1-10:   ~1600 tokens   ████░░░░░░                  │
│  Echange 1-15:   ~2400 tokens   ██████░░░░                  │
│  Echange 1-20:   ~3200 tokens   ████████░░   LIMITE!        │
│  Echange 1-30:   ~4800 tokens   ████████████ DEPASSE!       │
│                                                              │
│  Fenetre GPT-4: ~8000 tokens utilisables                    │
│  (reste du contexte = documents RAG + prompt)               │
│                                                              │
└─────────────────────────────────────────────────────────────┘

La solution : Resume progressif

┌─────────────────────────────────────────────────────────────┐
│               SUMMARY MEMORY                                 │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌────────────────────────────────────────────────────┐     │
│  │ RESUME (200-300 tokens)                             │     │
│  │ "L'utilisateur cherche un laptop gaming budget     │     │
│  │  1500€. Il a refuse le MSI car trop lourd.         │     │
│  │  Interesse par ASUS ROG, questions sur garantie."  │     │
│  └────────────────────────────────────────────────────┘     │
│                            +                                 │
│  ┌────────────────────────────────────────────────────┐     │
│  │ MESSAGES RECENTS (4-6 derniers echanges)           │     │
│  │ User: "Et la carte graphique ?"                    │     │
│  │ AI: "L'ASUS ROG a une RTX 4060..."                 │     │
│  │ User: "C'est compatible avec mon ecran ?"          │     │
│  │ AI: "Oui, le port HDMI 2.1..."                     │     │
│  └────────────────────────────────────────────────────┘     │
│                                                              │
│  TOTAL: ~600 tokens (vs 3000+ en buffer)                    │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Statistique cle : La Summary Memory reduit la consommation de tokens de 70-80% pour les conversations longues, tout en conservant 90%+ du contexte pertinent.

Comparaison des approches

ScenarioBuffer MemorySummary MemoryRecommandation
< 5 echangesOptimalSurdimensionneBuffer
5-15 echangesOK avec fenetreBonBuffer Window
15-50 echangesLimiteOptimalSummary
50+ echangesImpossibleExcellentSummary

Implementation de base

Summary Memory from scratch

DEVELOPERpython
from typing import List, Dict from datetime import datetime class SummaryMemory: """ Memoire conversationnelle avec resume progressif """ def __init__( self, llm, max_messages_before_summary: int = 6, summary_max_tokens: int = 300 ): self.llm = llm self.max_messages = max_messages_before_summary self.summary_max_tokens = summary_max_tokens self.summary: str = "" self.recent_messages: List[Dict] = [] def add_message(self, role: str, content: str) -> None: """Ajoute un message et declenche un resume si necessaire""" self.recent_messages.append({ "role": role, "content": content, "timestamp": datetime.now().isoformat() }) # Si trop de messages, resumer les anciens if len(self.recent_messages) > self.max_messages: self._summarize_old_messages() def _summarize_old_messages(self) -> None: """Resume les messages les plus anciens""" # Diviser: anciens a resumer, recents a garder split_point = len(self.recent_messages) // 2 to_summarize = self.recent_messages[:split_point] self.recent_messages = self.recent_messages[split_point:] # Formatter la conversation a resumer conversation = "\n".join([ f"{m['role'].capitalize()}: {m['content']}" for m in to_summarize ]) # Generer le nouveau resume prompt = f"""Tu dois mettre a jour un resume de conversation. Resume existant: {self.summary if self.summary else "Aucun resume precedent."} Nouveaux echanges a integrer: {conversation} Instructions: - Integre les informations importantes des nouveaux echanges au resume - Garde les decisions prises, preferences exprimees, problemes resolus - Supprime les details redondants ou obsoletes - Le resume doit etre concis (max {self.summary_max_tokens} tokens) - Ecris a la troisieme personne ("L'utilisateur...", "L'assistant...") Resume mis a jour:""" self.summary = self.llm.invoke(prompt) def get_context(self) -> str: """Retourne le contexte complet pour le prompt""" parts = [] if self.summary: parts.append(f"Resume de la conversation precedente:\n{self.summary}") if self.recent_messages: recent = "\n".join([ f"{m['role'].capitalize()}: {m['content']}" for m in self.recent_messages ]) parts.append(f"Echanges recents:\n{recent}") return "\n\n".join(parts) def clear(self) -> None: """Reinitialise la memoire""" self.summary = "" self.recent_messages = []

Integration avec RAG

DEVELOPERpython
class RAGWithSummaryMemory: """ Pipeline RAG complet avec Summary Memory """ def __init__(self, vector_store, llm): self.vector_store = vector_store self.llm = llm self.memory = SummaryMemory(llm, max_messages_before_summary=6) def query(self, user_message: str) -> str: """Execute une requete RAG avec contexte resume""" # 1. Ajouter le message utilisateur self.memory.add_message("user", user_message) # 2. Recuperer les documents pertinents docs = self.vector_store.similarity_search(user_message, k=3) doc_context = "\n\n".join([ f"Document {i+1}:\n{d.page_content}" for i, d in enumerate(docs) ]) # 3. Construire le prompt avec memoire memory_context = self.memory.get_context() prompt = f"""Tu es un assistant utile. Utilise le contexte de la conversation et les documents pour repondre. {memory_context} Documents pertinents: {doc_context} Question actuelle: {user_message} Reponds de maniere precise et contextuelle.""" # 4. Generer et sauvegarder la reponse response = self.llm.invoke(prompt) self.memory.add_message("assistant", response) return response

Techniques avancees

Resume incremental (plus efficace)

Au lieu de regenerer le resume complet, on peut l'enrichir incrementalement :

DEVELOPERpython
class IncrementalSummaryMemory(SummaryMemory): """ Summary Memory avec mise a jour incrementale Plus rapide et moins couteux en tokens """ def _summarize_old_messages(self) -> None: """Mise a jour incrementale du resume""" split_point = len(self.recent_messages) // 2 to_summarize = self.recent_messages[:split_point] self.recent_messages = self.recent_messages[split_point:] conversation = "\n".join([ f"{m['role'].capitalize()}: {m['content']}" for m in to_summarize ]) # Prompt optimise pour mise a jour incrementale if self.summary: prompt = f"""Resume actuel: {self.summary} Nouveaux elements de conversation: {conversation} Mets a jour le resume en: 1. Ajoutant les nouvelles informations importantes 2. Supprimant les informations devenues obsoletes 3. Gardant le resume concis (max 200 mots) Resume mis a jour:""" else: prompt = f"""Resume cette conversation en extrayant: - Le sujet principal - Les decisions prises - Les preferences de l'utilisateur - Les questions en suspens Conversation: {conversation} Resume (max 200 mots):""" self.summary = self.llm.invoke(prompt)

Resume structure

Pour une meilleure organisation, utilisez un resume structure :

DEVELOPERpython
import json class StructuredSummaryMemory(SummaryMemory): """ Summary Memory avec resume structure en JSON Permet un acces plus precis aux informations """ def __init__(self, llm, **kwargs): super().__init__(llm, **kwargs) self.structured_summary: Dict = { "main_topic": "", "user_preferences": [], "decisions_made": [], "pending_questions": [], "key_facts": [] } def _summarize_old_messages(self) -> None: """Genere un resume structure""" split_point = len(self.recent_messages) // 2 to_summarize = self.recent_messages[:split_point] self.recent_messages = self.recent_messages[split_point:] conversation = "\n".join([ f"{m['role'].capitalize()}: {m['content']}" for m in to_summarize ]) prompt = f"""Analyse cette conversation et mets a jour le resume structure. Resume actuel: {json.dumps(self.structured_summary, ensure_ascii=False, indent=2)} Nouveaux echanges: {conversation} Retourne un JSON avec la structure suivante: {{ "main_topic": "sujet principal de la conversation", "user_preferences": ["liste des preferences exprimees"], "decisions_made": ["liste des decisions prises"], "pending_questions": ["questions non resolues"], "key_facts": ["faits importants a retenir"] }} JSON mis a jour:""" response = self.llm.invoke(prompt) try: self.structured_summary = json.loads(response) except json.JSONDecodeError: # Fallback sur resume textuel self.summary = response def get_context(self) -> str: """Formate le resume structure pour le prompt""" parts = [] if self.structured_summary.get("main_topic"): parts.append(f"Sujet: {self.structured_summary['main_topic']}") if self.structured_summary.get("user_preferences"): prefs = ", ".join(self.structured_summary["user_preferences"]) parts.append(f"Preferences utilisateur: {prefs}") if self.structured_summary.get("decisions_made"): decisions = ", ".join(self.structured_summary["decisions_made"]) parts.append(f"Decisions prises: {decisions}") if self.structured_summary.get("pending_questions"): questions = ", ".join(self.structured_summary["pending_questions"]) parts.append(f"Questions en suspens: {questions}") summary_text = "\n".join(parts) if parts else "" if self.recent_messages: recent = "\n".join([ f"{m['role'].capitalize()}: {m['content']}" for m in self.recent_messages ]) return f"Contexte:\n{summary_text}\n\nEchanges recents:\n{recent}" return f"Contexte:\n{summary_text}"

Implementation avec LangChain

LangChain fournit une implementation native :

DEVELOPERpython
from langchain.memory import ConversationSummaryMemory from langchain.chains import ConversationChain from langchain_openai import ChatOpenAI # LLM pour generation et pour resume llm = ChatOpenAI(model="gpt-4", temperature=0.7) # Summary Memory native LangChain memory = ConversationSummaryMemory( llm=llm, memory_key="history", return_messages=True ) # Chaine conversationnelle conversation = ConversationChain( llm=llm, memory=memory, verbose=True ) # Utilisation - le resume se fait automatiquement response = conversation.predict(input="Je cherche un laptop gaming") response = conversation.predict(input="Budget max 1500 euros") response = conversation.predict(input="J'ai deja regarde le MSI mais trop lourd") # ... apres plusieurs echanges, la memoire contient un resume # Inspecter le resume print(memory.buffer) # Affiche le resume actuel

Combinaison Buffer + Summary

La meilleure approche combine les deux :

DEVELOPERpython
from langchain.memory import ConversationSummaryBufferMemory # Garde les messages recents ET un resume des anciens memory = ConversationSummaryBufferMemory( llm=llm, max_token_limit=1000, # Resume quand on depasse 1000 tokens memory_key="history", return_messages=True ) # Plus efficace que summary pure pour le contexte recent

Implementation avec LlamaIndex

DEVELOPERpython
from llama_index.core.memory import ChatSummaryMemoryBuffer from llama_index.core.chat_engine import CondensePlusContextChatEngine from llama_index.llms.openai import OpenAI # LLM llm = OpenAI(model="gpt-4", temperature=0.7) # Memory avec resume automatique memory = ChatSummaryMemoryBuffer.from_defaults( llm=llm, token_limit=1500, # Limite avant resume summarize_prompt=( "Resume la conversation precedente en gardant:\n" "- Le sujet principal\n" "- Les decisions prises\n" "- Le contexte important\n\n" "Conversation:\n{conversation}\n\n" "Resume:" ) ) # Chat engine avec memoire chat_engine = CondensePlusContextChatEngine.from_defaults( retriever=index.as_retriever(similarity_top_k=4), memory=memory, llm=llm ) # Conversation longue - resume automatique for question in user_questions: response = chat_engine.chat(question) print(response)

Bonnes pratiques

1. Choisir le bon seuil de resume

DEVELOPERpython
# Recommandations selon le cas d'usage THRESHOLDS = { "support_client": 6, # Resume frequent, contexte recent important "consultation": 10, # Plus de contexte avant resume "tutoriel": 4, # Resume tres frequent "conversation_libre": 8 # Equilibre } memory = SummaryMemory( llm=llm, max_messages_before_summary=THRESHOLDS["support_client"] )

2. Valider la qualite du resume

DEVELOPERpython
class ValidatedSummaryMemory(SummaryMemory): """Summary Memory avec validation du resume""" def _summarize_old_messages(self) -> None: super()._summarize_old_messages() # Verifier que le resume n'est pas vide ou trop court if len(self.summary.split()) < 20: # Regenerer avec un prompt plus explicite self._force_detailed_summary() def _force_detailed_summary(self) -> None: """Regenere un resume plus detaille si necessaire""" prompt = f"""Le resume precedent etait trop court. Developpe davantage en incluant: - Qui est l'utilisateur et ce qu'il cherche - Les criteres importants mentionnes - Les options discutees - L'etat actuel de la conversation Resume actuel: {self.summary} Resume developpe:""" self.summary = self.llm.invoke(prompt)

3. Gerer les changements de sujet

DEVELOPERpython
class TopicAwareSummaryMemory(SummaryMemory): """Detecte et gere les changements de sujet""" def add_message(self, role: str, content: str) -> None: # Detecter un changement de sujet if role == "user" and self._is_topic_change(content): # Finaliser le resume du sujet precedent self._finalize_current_topic() super().add_message(role, content) def _is_topic_change(self, message: str) -> bool: """Detecte si le message change de sujet""" indicators = [ "autre chose", "nouveau sujet", "question differente", "j'aimerais aussi", "par ailleurs" ] return any(ind in message.lower() for ind in indicators) def _finalize_current_topic(self) -> None: """Archive le sujet actuel avant de changer""" if self.summary: self.archived_topics.append({ "topic": self.summary, "timestamp": datetime.now().isoformat() }) self.summary = ""

Quand utiliser Summary Memory ?

Cas d'usage ideaux

  • Support client approfondi : Troubleshooting multi-etapes
  • Consultations : Conseils personnalises sur la duree
  • Onboarding : Accompagnement sur plusieurs sessions
  • Conversations complexes : Negociations, configurations detaillees

Quand eviter

SituationPourquoiAlternative
Conversations < 5 echangesOverhead inutileBuffer Memory
Besoin de precision exacteLe resume perd des detailsBuffer Window
Latence critiqueLe resume ajoute un appel LLMBuffer avec limite

FAQ

Pour une conversation de 30 echanges, le Buffer Memory consommerait environ 4500-6000 tokens. La Summary Memory reduit cela a 500-800 tokens (resume + derniers messages), soit une economie de 70-85%. L'economie augmente avec la longueur de la conversation.
Inevitablement, oui. Le resume condense l'information et certains details sont perdus. Pour minimiser ce risque : utilisez des prompts de resume bien structures, gardez toujours les 4-6 derniers messages en plus du resume, et implementez une validation de qualite du resume. Pour les conversations ou chaque detail compte, preferez le Buffer Memory avec fenetre.
Deux approches : base sur le nombre de messages (toutes les 6-8 messages) ou base sur les tokens (quand on depasse 1500-2000 tokens). La deuxieme approche est plus precise mais necessite un comptage de tokens. ConversationSummaryBufferMemory de LangChain combine les deux avec max_token_limit.
Implementez une detection de changement de sujet (mots-cles comme "autre chose", "nouveau sujet"). Quand un changement est detecte, finalisez le resume du sujet precedent et archivez-le. Commencez un nouveau resume pour le nouveau sujet. Cela evite de melanger des contextes non lies.
Oui, c'est meme un cas d'usage ideal. Sauvegardez le resume entre les sessions dans une base de donnees. A la reprise, rechargez le dernier resume comme contexte initial. L'utilisateur peut reprendre ou il s'etait arrete sans avoir a tout re-expliquer, meme apres des jours d'inactivite.

Pour aller plus loin


Summary Memory avec Ailog

Implementer une Summary Memory robuste demande une attention particuliere a la qualite des resumes. Avec Ailog, beneficiez d'une gestion de memoire optimisee :

  • Resume automatique avec seuils adaptatifs selon la conversation
  • Detection de changement de sujet pour des resumes pertinents
  • Resume structure pour un acces precis aux informations
  • Persistence multi-session avec reprise du contexte
  • Analytics sur la qualite des resumes et la retention d'information

Testez Ailog gratuitement et deployez un chatbot avec memoire longue duree.

Tags

RAGmemorysummaryresumelong-termcompressionLangChainLlamaIndex

Verwandte Artikel

Ailog Assistant

Ici pour vous aider

Salut ! Pose-moi des questions sur Ailog et comment intégrer votre RAG dans vos projets !