Implementing Persistent Memory in LangChain for Multi-Session User Conversations
ConversationBufferMemory is a trap for anyone moving beyond a basic demo because it wipes everything the moment the Python process restarts. To build a production-ready bot that remembers a user across different sessions or devices, you have to decouple the memory storage from the application state.I've spent the last few weeks migrating a multi-user support bot from in-memory storage to Redis, and the biggest realization was that ChatMessageHistory is the actual primitive you need to focus on, not the high-level Memory wrappers.
The core strategy is to use a persistent backend like Redis or PostgreSQL to store the message history, keyed by a unique session_id. This prevents "memory leak" where User A's context bleeds into User B's conversation.
Here is the setup I'm using with RedisChatMessageHistory. First, install the integration:
pip install langchain-community redisThen, instead of letting the chain handle memory internally, I instantiate the history object dynamically based on the user's session ID:
from langchain_community.chat_message_histories import RedisChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# Setup the LLM and prompt
model = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant with long-term memory."),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
])
chain = prompt | model
# This function fetches the history for a specific session from Redis
def get_chat_history(session_id: str):
return RedisChatMessageHistory(session_id, url="redis://localhost:6379")
# Wrap the chain to handle session-based history automatically
with_message_history = RunnableWithMessageHistory(
chain,
get_chat_history,
input_messages_key="input",
history_messages_key="history",
)
# Now you can invoke it with a config specifying the session_id
config = {"configurable": {"session_id": "user_1234_session_abc"}}
response = with_message_history.invoke(
{"input": "My name is Alex and I love Rust."},
config=config
)A few hard-learned productivity tips for this setup:
The Token Explosion Gotcha: Persistent memory is a double-edged sword. If a user has a 50-turn conversation, you'll hit the context window limit or blow through your API budget. Don't just store everything. Use a trimming strategy. I recommend wrapping the history in a trim_messages function or using ConversationSummaryBufferMemory if you need the "gist" of old conversations without the raw token overhead.
Session Keying Strategy: Never use raw user IDs as session keys. Use a combination of user_id:conversation_id. This allows you to implement a "New Chat" button that simply generates a new UUID, effectively clearing the context for the user without deleting their historical data from your database.
Serialization Performance: If you're using a SQL backend instead of Redis, you'll notice a lag in response times because of the DB read/write on every turn. I found that implementing a local LRU cache for the most active sessions reduced my latency by about 200ms per turn.
Consistency over Convenience: Avoid using the legacy ConversationChain class. The newer LCEL (LangChain Expression Language) approach with RunnableWithMessageHistory is much more transparent. It lets you see exactly when the history is being fetched and injected, which makes debugging "why did the AI forget X?" significantly easier.
All Replies (0)
No replies yet — be the first!
