DS DevShelfHub Projects · AI tools
Tutorials / LangChain / Memory
LangChain Intermediate · 11 min read Page 5 of 20

LangChain Memory: Add Conversation History to Your Chatbot

By DevShelfHub

How to give a chain conversation history — buffer, summary, and token-window strategies — and when not to use memory at all.

Series progress5 / 20
LangChain memory strategies for adding conversation history to a chatbot

Why memory?

LLMs are stateless by default — every call is independent. If you ask "What did I just say?", the model has no idea. Memory lets you inject previous conversation turns into the prompt so the model can refer back to them.

The core idea: before every LLM call, prepend a summary or transcript of the conversation so far. The model "remembers" because it reads its own history.

ConversationBufferMemory

The simplest approach — store the full message history and replay it every time. Works well for short conversations.

python
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")
memory = ConversationBufferMemory()

conversation = ConversationChain(llm=llm, memory=memory)

conversation.invoke("Hi, my name is Alice.")
conversation.invoke("What's my name?")  # → "Your name is Alice."

Every message is appended to the buffer. On the next call, the full history is injected into the prompt before the new message. Simple — but the prompt grows unboundedly.

The modern LCEL approach — RunnableWithMessageHistory

The ConversationChain pattern is legacy. With LCEL, you attach history via RunnableWithMessageHistory — cleaner and more flexible.

python
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

store = {}  # session_id → ChatMessageHistory

def get_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}"),
])

chain = prompt | llm | StrOutputParser()

with_history = RunnableWithMessageHistory(
    chain,
    get_history,
    input_messages_key="input",
    history_messages_key="history",
)

cfg = {"configurable": {"session_id": "user-123"}}
with_history.invoke({"input": "My name is Alice."}, config=cfg)
with_history.invoke({"input": "What's my name?"}, config=cfg)

Each session gets its own history store. You can swap the in-memory store for Redis, DynamoDB, or any persistent backend by implementing the same interface.

ConversationSummaryMemory

Instead of storing every message, summarise the conversation so far using another LLM call. The prompt stays short regardless of conversation length.

python
from langchain.memory import ConversationSummaryMemory

memory = ConversationSummaryMemory(llm=llm)

conversation = ConversationChain(llm=llm, memory=memory, verbose=True)

conversation.invoke("I'm planning a trip to Japan.")
conversation.invoke("I want to visit Kyoto and Tokyo.")
conversation.invoke("What did we discuss?")

Pros

  • Prompt length stays constant
  • Works for very long conversations

Cons

  • Extra LLM call per turn (cost + latency)
  • Summarisation loses detail

ConversationTokenBufferMemory

A sliding window that keeps as many recent messages as will fit within a token budget. When the limit is reached, old messages are dropped.

python
from langchain.memory import ConversationTokenBufferMemory

memory = ConversationTokenBufferMemory(
    llm=llm,
    max_token_limit=500,   # keep the most recent messages that fit
)

conversation = ConversationChain(llm=llm, memory=memory)

Good middle ground — predictable prompt size without the extra LLM call that summary memory requires. Older context is lost, but recent turns are always preserved.

Choosing a memory strategy

Strategy Best for Watch out for
BufferMemory Short conversations, demos Prompt grows without bound
TokenBufferMemory Predictable cost, recent context matters Early context is silently dropped
SummaryMemory Long conversations, big-picture recall Extra LLM call; detail loss
RunnableWithMessageHistory Production LCEL apps, multi-user Need a persistent backend for scale

When not to use memory

Memory is not always the right tool. Before reaching for it, consider these cases where you should skip it entirely.

Single-shot pipelines

If your chain processes one document or one request with no back-and-forth, memory adds overhead with zero benefit. Summarisation chains, classification pipelines, and batch jobs don't need it.

The context window is large enough

Modern models (GPT-4o, Claude 3.5) have context windows of 128k–200k tokens. For many use cases, you can just pass the full conversation without any memory management and it fits fine.

You need exact recall, not fuzzy summarisation

If precise past details matter (contract terms, exact numbers), summary memory will lose them. Use a database and retrieve the exact record instead of relying on the LLM to remember it.

Stateless API endpoints

If your backend handles many users, in-process memory (like ConversationBufferMemory) won't survive server restarts and can't scale across multiple instances. Use a proper persistent store or pass history from the client.

Quick summary

  • LLMs are stateless — memory works by prepending conversation history to every prompt
  • BufferMemory is simplest; TokenBufferMemory caps cost; SummaryMemory handles long conversations
  • For LCEL, use RunnableWithMessageHistory with a persistent backend in production
  • Skip memory for single-shot pipelines, large-context models, exact-recall needs, and stateless APIs

LangChain Memory FAQ

What is memory in LangChain?

LLMs are stateless — every call is independent and the model cannot recall earlier turns. Memory in LangChain works by prepending a transcript or summary of the conversation to each prompt, so the model can refer back to what was said before.

What is the difference between buffer, summary, and token buffer memory?

ConversationBufferMemory stores the full message history and replays it, so the prompt grows without bound. ConversationTokenBufferMemory keeps only the most recent messages that fit a token budget. ConversationSummaryMemory uses an extra LLM call to compress the conversation into a short summary, keeping the prompt small for long chats.

How do I add memory to an LCEL chain?

Wrap your chain in RunnableWithMessageHistory, supply a function that returns a ChatMessageHistory per session_id, and set input_messages_key and history_messages_key. Add a MessagesPlaceholder to the prompt so the history is injected. Each session gets its own store, which you can back with Redis or DynamoDB.

Is ConversationChain deprecated in LangChain?

The ConversationChain and classic Memory classes are legacy. The recommended modern approach is to compose your chain with LCEL and attach history using RunnableWithMessageHistory, which is cleaner, more flexible, and works with any persistent backend.

When should I not use memory in LangChain?

Skip memory for single-shot pipelines like summarisation or classification, when the model's context window is large enough to pass the full conversation, when you need exact recall (use a database instead of fuzzy summaries), and for stateless API endpoints where in-process memory cannot scale across instances.

Quick jump: API Reference