When building a chatbot with LangChain, it is easy to assume that the language model itself remembers previous conversations. In reality, this is not true. A language model is stateless; it does not retain any information between calls. What makes a chatbot appear conversational is the careful handling of chat history at the application level.
LangChain makes this process explicit, predictable, and developer-controlled. Let us understand how this works, step by step, with code examples.
1. The Language Model Has No Memory
Consider the following code:
from langchain_groq import ChatGroq
from langchain_core.messages import HumanMessage
model = ChatGroq(model="gemma2-9b-it")
response = model.invoke(
HumanMessage(content="Hello, my name is Alex.")
)
print(response.content)
This produces a valid response. However, if you make another call like this:
response = model.invoke(
HumanMessage(content="What is my name?")
)
The model will not know the answer, because it has no awareness of the previous interaction. Each invocation is independent.
This demonstrates the core problem:
without explicitly passing prior context, memory does not exist.
2. Conversations Are Stored as Message Objects
LangChain represents conversations as lists of message objects, not plain strings.
Example:
from langchain_core.messages import HumanMessage, AIMessage
messages = [
HumanMessage(content="Hello, my name is Alex."),
AIMessage(content="Nice to meet you, Alex."),
HumanMessage(content="What is my name?")
]
response = model.invoke(messages)
print(response.content)
Here, the model does remember the name because we manually supplied the previous messages. The memory exists only because we passed it again.
This is the foundation of all chat memory in LangChain.
3. ChatMessageHistory: Where Memory Is Stored
Manually managing message lists quickly becomes impractical. LangChain provides ChatMessageHistory, which stores messages in order.
from langchain_community.chat_message_histories import ChatMessageHistory
history = ChatMessageHistory()
history.add_user_message("Hello, my name is Alex.")
history.add_ai_message("Nice to meet you, Alex.")
Internally, this object maintains a list like:
history.messages
Every new message is appended automatically. This object is the memory.
4. Session IDs Separate Conversations
To support multiple conversations, LangChain uses session IDs. Each session ID maps to its own ChatMessageHistory.
store = {}
from langchain_core.chat_history import BaseChatMessageHistory
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
If two users have different session IDs, they get completely separate histories. This is how LangChain prevents conversation leakage between users.
5. RunnableWithMessageHistory: Automatic Memory Handling
The key abstraction that ties everything together is RunnableWithMessageHistory.
from langchain_core.runnables import RunnableWithMessageHistory
chatbot = RunnableWithMessageHistory(
model,
get_session_history
)
This wrapper ensures that:
- Past messages are loaded automatically
- New messages are saved automatically
- You don’t manually manage history lists
6. Passing the Session ID at Runtime
The session ID is passed via configuration, not via the prompt.
config = {
"configurable": {
"session_id": "chat_1"
}
}
This keeps memory handling cleanly separated from conversation content.
7. Chatting with Memory (Example)
response = chatbot.invoke(
HumanMessage(content="Hello, my name is Alex."),
config=config
)
print(response.content)
Ask a follow-up:
response = chatbot.invoke(
HumanMessage(content="What is my name?"),
config=config
)
print(response.content)
Output:
Your name is Alex.
The model remembers because LangChain:
- Retrieved the history for
chat_1 - Injected it into the model call
- Stored the new response back into history
8. Changing the Session Resets Memory
Now change the session ID:
new_config = {
"configurable": {
"session_id": "chat_2"
}
}
response = chatbot.invoke(
HumanMessage(content="What is my name?"),
config=new_config
)
print(response.content)
Output:
I don't know your name yet.
This proves an important point: Memory is tied to the session, not the model.
9. Why This Design Is Powerful
This explicit design has several advantages:
- Memory is predictable
- Sessions are isolated
- Storage can be swapped (RAM → Redis → DB)
- Debugging is straightforward
- Scaling is safe
Most importantly, it aligns with how LLMs actually work.
10. Final Mental Model
session_id ↓ get_session_history(session_id) ↓ ChatMessageHistory (list of messages) ↓ RunnableWithMessageHistory ↓ LLM invocation
Memory is not magic. It is messages + session ID + disciplined reinjection.
11. Example
import os
from dotenv import load_dotenv
from langchain_groq import ChatGroq
from langchain_core.messages import HumanMessage
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables import RunnableWithMessageHistory
# ----------------------------------------------------
# Load environment variables
# ----------------------------------------------------
load_dotenv()
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
# ----------------------------------------------------
# Initialize LLM
# ----------------------------------------------------
model = ChatGroq(
api_key=GROQ_API_KEY,
model="llama-3.1-8b-instant"
)
# ----------------------------------------------------
# In-memory store for chat histories
# ----------------------------------------------------
store = {}
# ----------------------------------------------------
# Session history retriever
# ----------------------------------------------------
def get_session_history(session_id: str) -> BaseChatMessageHistory:
"""
Returns chat history for a given session ID.
Creates a new history if session does not exist.
"""
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# ----------------------------------------------------
# Wrap model with message history support
# ----------------------------------------------------
chatbot = RunnableWithMessageHistory(
model,
get_session_history
)
# ----------------------------------------------------
# Session configurations
# ----------------------------------------------------
session_one = {
"configurable": {
"session_id": "chat_session_1"
}
}
session_two = {
"configurable": {
"session_id": "chat_session_2"
}
}
# ----------------------------------------------------
# Conversation: Session 1
# ----------------------------------------------------
print("\n--- Session 1 ---")
response = chatbot.invoke(
HumanMessage(content="Hello, my name is Alex and I work as a software architect."),
config=session_one
)
print("AI:", response.content)
response = chatbot.invoke(
HumanMessage(content="What is my name and what do I do?"),
config=session_one
)
print("AI:", response.content)
# ----------------------------------------------------
# Conversation: Session 2 (fresh memory)
# ----------------------------------------------------
print("\n--- Session 2 ---")
response = chatbot.invoke(
HumanMessage(content="What is my name?"),
config=session_two
)
print("AI:", response.content)
response = chatbot.invoke(
HumanMessage(content="My name is John."),
config=session_two
)
print("AI:", response.content)
response = chatbot.invoke(
HumanMessage(content="Do you remember my name now?"),
config=session_two
)
print("AI:", response.content)
# ----------------------------------------------------
# Back to Session 1 (memory retained)
# ----------------------------------------------------
print("\n--- Back to Session 1 ---")
response = chatbot.invoke(
HumanMessage(content="Do you still remember who I am?"),
config=session_one
)
print("AI:", response.content)
Output
--- Session 1 --- AI: Hello Alex, nice to meet you. Working as a software architect can be a challenging yet rewarding job. What specific areas of software architecture are you focused on, such as cloud computing, microservices, or enterprise software development? AI: Your name is Alex and you work as a software architect. --- Session 2 --- AI: I don't have information about your name as our conversation has just started. I'm happy to chat with you, though. What's on your mind? Would you like to tell me your name? AI: Nice to meet you, John. Is there anything specific you'd like to talk about or any questions you have on your mind? I'm here to help with any information or just have a conversation. AI: John, yes, I remember your name now. We established it at the beginning of our conversation. I can recall it for the rest of our chat. --- Back to Session 1 --- AI: Yes, I remember that you are Alex, a software architect. We were discussing your profession and areas of expertise.
