LangChain Core Module Learning: Memory
Most LLM applications have a conversation interface. An important part of a conversation is the ability to cite information that was previously described in the conversation. At the very least, a conversation system should be able to directly access windows of some past messages. More complex systems will need to have a constantly updated world model that enables it to maintain information about entities and their relationships.
We call the ability to store past interactive information "memory".
LangChain provides many practical tools for adding Memory to applications/systems. These tools can be used alone or seamlessly integrated into the chain.
A memory system needs to support two basic operations:Read (READ) and Write (WRITE)。
Each chain defines some core execution logic and expects certain inputs. Some of these inputs come directly from the user, but some may come from Memory.
In a single run of a typical Chain, it will interact with its Memory System at least twice:
1. After receiving the initial user input, the chain will read and augment the user input from its Memory before executing the core logic.
2. After executing the core logic but before returning the answer, a chain will write the input and output of the currently running to the Memory so that they can be referenced in future runs.
BaseMemory Class
Class inheritance relationship:
## Suitable for simple language models
BaseMemory --> BaseChatMemory --> <name>Memory # Examples: ZepMemory, MotorheadMemory
# Define a base class called BaseMemory
class BaseMemory(Serializable, ABC):
"""Abstract base class for memory in Chains.
The memory here refers to the state in Chains. Memory can be used to store information about Chain's past execution,
And inject this information into the input of future execution of Chain. For example, for session-type Chains, memory can be used to
Store sessions and automatically add them to future model prompts so that the model has the necessary context to coherently
Respond to the latest input. """
# Define a subclass called Config
class Config:
"""Configure this pydantic object.
Pydantic is a Python library for data verification and setup management, mainly based on Python type prompts.
"""
# Allows any type to be used in pydantic models. This is often used to allow complex data types.
arbitrary_types_allowed = True
# Here are some methods that must be implemented by subclasses:
# Define a property, which is an abstract method. Any subclass derived from BaseMemory needs to implement this method.
# This method should return the string key that the memory class will be added to the chain input.
@property
@abstractmethod
def memory_variables(self) -> List[str]:
"""Get the string key that this memory class will be added to the chain input."""
# Define an abstract method. Any subclass derived from BaseMemory needs to implement this method.
# This method returns key-value pairs based on the given chain input.
@abstractmethod
def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
"""Return key-value pairs based on text input to the chain."""
# Define an abstract method. Any subclass derived from BaseMemory needs to implement this method.
# This method saves the context in which this chain runs to memory.
@abstractmethod
def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
"""Save the context of this chain running to memory.""""
# Define an abstract method. Any subclass derived from BaseMemory needs to implement this method.
# This method clears the memory contents.
@abstractmethod
def clear(self) -> None:
"""Clear memory content."""
BaseChatMessageHistory Class
Class inheritance relationship:
## For chat model
BaseChatMessageHistory --> <name>ChatMessageHistory # Example: ZepChatMessageHistory
# Define a base class called BaseChatMessageHistory
class BaseChatMessageHistory(ABC):
"""Abstract base class for chat message history."""
# List of messages stored in memory
messages: List[BaseMessage]
# Define an add_user_message method, which is a convenient method for adding human message strings to the storage area.
def add_user_message(self, message: str) -> None:
"""A convenient way to add a human message string to store.
parameter:
message: String content of human message.
"""
self.add_message(HumanMessage(content=message))
# Define an add_ai_message method, which is a convenient method for adding AI message strings to the storage area.
def add_ai_message(self, message: str) -> None:
"""A convenient way to add an AI message string to storage.
parameter:
message: The string content of the AI message.
"""
self.add_message(AIMessage(content=message))
# Abstract method needs to be implemented by subclasses that inherit this base class.
@abstractmethod
def add_message(self, message: BaseMessage) -> None:
"""Add Message object to the store.
parameter:
message: The BaseMessage object to be stored.
"""
raise NotImplementedError()
# Abstract method needs to be implemented by subclasses that inherit this base class.
@abstractmethod
def clear(self) -> None:
"""Delete all messages from storage"""
ConversationChain and ConversationBufferMemory
ConversationBufferMemory
Can be used to store messages and extract messages into a variable.
from langchain_openai import OpenAI
from import ConversationChain
from import ConversationBufferMemory
llm = OpenAI(temperature=0)
conversation = ConversationChain(
llm=llm,
verbose=True,
memory=ConversationBufferMemory()
)
> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
Current conversation:
Human: hello!
AI:
[INFO][2025-02-06 16:35:18.141] :304 [t:3812]: successfully refresh token
> Finished chain.
ConversationBufferWindowMemory
ConversationBufferWindowMemory
A list of interactions for conversations is kept on the timeline. It uses only the last K interactions. This is very useful for maintaining sliding windows for the most recent interactions to avoid oversized buffers.
from import ConversationBufferWindowMemory
conversation_with_summary = ConversationChain(
llm=OpenAI(temperature=0, max_tokens=1000),
# We set a low k=2, to only keep the last 2 interactions in memory
memory=ConversationBufferWindowMemory(k=2),
verbose=True
)
conversation_with_summary.predict(input="Hi, how are you doing recently?")
ConversationSummaryBufferMemory
ConversationSummaryBufferMemory
The most recent interaction buffers are preserved in memory, but not just to completely clear old interactions, but to compile them into digests and use them simultaneously. Unlike previous implementations, it uses token length instead of interactions to determine when to clear the interaction.
from import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(llm=llm, max_token_limit=10)
memory.save_context({"input": "Hi, how are you doing recently?"}, {"output": " Hi! I've been doing well recently, thank you for asking. I've been learning new knowledge recently, And trying to improve my performance. I'm also trying to communicate more to better understand the way humans think. "})
memory.save_context({"input": "What new knowledge have you learned recently?"}, {"output": " Recently I learned about natural language processing and how to better understand human language. I Also learned about machine learning and how to use it to improve your own performance. "})
memory.load_memory_variables({})