ConversationBufferMemory: Simple History
Implement ConversationBufferMemory in LangChain for multi-turn conversations. Manage history, integrate with LCEL chains, persist across sessions, and handle context limits.
ConversationBufferMemory
ConversationBufferMemory stores every conversation turn verbatim. It's the simplest memory type — use it when your conversations are short enough to fit in the context window.
from langchain.memory import ConversationBufferMemory
from langchain_core.messages import HumanMessage, AIMessage
# Create memory
memory = ConversationBufferMemory(
return_messages=True, # Return Message objects (vs concatenated string)
memory_key="chat_history", # Variable name to inject into prompts
input_key="input", # Key for user input in save_context()
output_key="output", # Key for model output in save_context()
)
# Manually add turns
memory.save_context(
inputs={"input": "What is warfarin?"},
outputs={"output": "Warfarin is an anticoagulant that inhibits vitamin K recycling."},
)
memory.save_context(
inputs={"input": "What is the typical dose for AFib?"},
outputs={"output": "For atrial fibrillation, the typical warfarin dose starts at 2-5mg daily."},
)
# Retrieve history
history = memory.load_memory_variables({})
print(history["chat_history"])
# [HumanMessage("What is warfarin?"), AIMessage("..."), HumanMessage("What is the typical dose..."), AIMessage("...")]
# Check message count
print(len(memory.chat_memory.messages)) # 4 (2 human + 2 AI)Integration with LCEL (Modern Approach)
The modern way to use memory is explicit history management in LCEL:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import HumanMessage, AIMessage
model = ChatOpenAI(model="gpt-4o", temperature=0)
parser = StrOutputParser()
# Prompt with history placeholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are a clinical pharmacist. Answer questions about medications."),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{question}"),
])
chain = prompt | model | parser
# Manage history manually (explicit — preferred in LCEL)
def chat_with_history(question: str, history: list) -> tuple[str, list]:
"""Invoke chain and update history. Returns (response, updated_history)."""
response = chain.invoke({
"question": question,
"chat_history": history,
})
# Append to history
updated_history = history + [
HumanMessage(content=question),
AIMessage(content=response),
]
return response, updated_history
# Have a conversation
history = []
r1, history = chat_with_history("What is warfarin?", history)
r2, history = chat_with_history("What dose for AFib?", history)
r3, history = chat_with_history("What monitoring is needed?", history)
print(f"Turn 1: {r1[:80]}")
print(f"Turn 2: {r2[:80]}")
print(f"Turn 3: {r3[:80]}")
print(f"History length: {len(history)} messages")Using ConversationChain (Legacy but Common)
The ConversationChain class wraps memory management automatically:
from langchain.chains import ConversationChain
llm = ChatOpenAI(model="gpt-4o", temperature=0)
memory = ConversationBufferMemory(return_messages=True)
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=False, # Set True to see memory injection
)
# Conversation — history is automatically managed
r1 = conversation.predict(input="What is warfarin?")
r2 = conversation.predict(input="What dose for AFib?") # Model knows context
r3 = conversation.predict(input="What are the main interactions?")
# Inspect what memory contains
print(memory.buffer_as_str) # Complete history as string
for msg in memory.chat_memory.messages:
role = "User" if msg.type == "human" else "Bot"
print(f"{role}: {msg.content[:60]}")Session Management: Multiple Users
In production, each user needs their own memory instance:
from typing import Optional
class ClinicalChatbot:
"""Multi-user clinical pharmacist chatbot."""
def __init__(self, max_history_tokens: int = 4000):
self.model = ChatOpenAI(model="gpt-4o", temperature=0)
self.parser = StrOutputParser()
self.max_history_tokens = max_history_tokens
self.sessions: dict[str, list] = {} # session_id → message history
self.prompt = ChatPromptTemplate.from_messages([
("system",
"You are a clinical pharmacist. "
"Provide evidence-based drug information. "
"Reference previous turns of the conversation when relevant."),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{question}"),
])
self.chain = self.prompt | self.model | self.parser
def get_session(self, session_id: str) -> list:
"""Get or create session history."""
if session_id not in self.sessions:
self.sessions[session_id] = []
return self.sessions[session_id]
def _trim_history(self, history: list) -> list:
"""Keep history within token budget (rough char estimate)."""
max_chars = self.max_history_tokens * 4
total_chars = sum(len(m.content) for m in history)
while total_chars > max_chars and len(history) >= 2:
# Remove oldest pair (human + ai)
removed = history.pop(0) # Remove oldest human
total_chars -= len(removed.content)
if history:
removed = history.pop(0) # Remove oldest ai
total_chars -= len(removed.content)
return history
def chat(self, session_id: str, question: str) -> dict:
"""Process a chat turn."""
history = self.get_session(session_id)
response = self.chain.invoke({
"question": question,
"chat_history": history,
})
# Update session history
history.extend([
HumanMessage(content=question),
AIMessage(content=response),
])
self.sessions[session_id] = self._trim_history(history)
return {
"response": response,
"session_id": session_id,
"turn_number": len(history) // 2,
}
def reset_session(self, session_id: str) -> None:
"""Clear history for a session."""
self.sessions.pop(session_id, None)
def get_session_summary(self, session_id: str) -> dict:
history = self.get_session(session_id)
return {
"turns": len(history) // 2,
"total_messages": len(history),
}
# Usage
bot = ClinicalChatbot()
r1 = bot.chat("session_001", "What is warfarin?")
r2 = bot.chat("session_001", "What dose for AFib?")
r3 = bot.chat("session_002", "What is metformin?") # Separate session
print(bot.get_session_summary("session_001")) # {"turns": 2, "total_messages": 4}
print(bot.get_session_summary("session_002")) # {"turns": 1, "total_messages": 2}Persisting Memory to Database
For production: store history in a database, not in-memory:
import json
import redis
from langchain_core.messages import messages_to_dict, messages_from_dict
class RedisConversationMemory:
"""Redis-backed conversation history."""
def __init__(self, redis_url: str = "redis://localhost:6379", ttl: int = 86400):
self.client = redis.from_url(redis_url, decode_responses=True)
self.ttl = ttl # 24 hours
def _key(self, session_id: str) -> str:
return f"chat_history:{session_id}"
def load(self, session_id: str) -> list:
"""Load history for a session."""
data = self.client.get(self._key(session_id))
if not data:
return []
return messages_from_dict(json.loads(data))
def save(self, session_id: str, history: list) -> None:
"""Save history to Redis."""
serialized = json.dumps(messages_to_dict(history))
self.client.setex(self._key(session_id), self.ttl, serialized)
def clear(self, session_id: str) -> None:
self.client.delete(self._key(session_id))
# Use with chat function
redis_memory = RedisConversationMemory()
def persistent_chat(session_id: str, question: str, chain) -> str:
history = redis_memory.load(session_id)
response = chain.invoke({"question": question, "chat_history": history})
history.extend([HumanMessage(content=question), AIMessage(content=response)])
redis_memory.save(session_id, history)
return responseWhen to Switch Away from Buffer Memory
| Trigger | Symptom | Switch to | |---|---|---| | Over 4000 tokens in history | API errors or slow responses | Window or Summary memory | | Conversations lasting hours | History too long for context | SummaryBuffer memory | | User re-explains old context | Lost early context | Entity or Vector memory | | Multiple users | Memory leaks between users | Per-session instances | | Server restart loses memory | Sessions disappear | Redis-backed persistence |
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.