LangChain Hands-on · Module 1 · Lab 05

Memory & Chat History

⏱ 20 min + hands-on🎯 Beginner🖼 1 diagram🧪 2 live demos🐍 Python code

Ask an LLM “my name is Ajeet”, then “what is my name?”, and it has no idea. Every call starts fresh: LLMs are stateless. “Memory” simply means sending the right old messages again. In this lab you build that by hand, then with LangChain helpers that keep a separate memory for every user, save it to a file and trim it to control cost.

In this session

  • Why the LLM forgets
  • Manual memory with a list
  • InMemoryChatMessageHistory
  • RunnableWithMessageHistory: memory per session_id
  • FileChatMessageHistory survives restarts
  • trim_messages and the 4 memory types

01The big idea: the doctor reads your file every visit

🩺

Think of a clinic. The doctor sees 50 patients a day and remembers none of them. Before your turn, the nurse puts your file on the table: past visits, medicines, allergies. LLM memory works the same way: the model forgets, so your app puts the old messages (the file) in front of it on every call.

Demo · Without memory vs with memory
get_session_historysession_id → historyPromptsystem + history + inputLLMsees everythingSaveadd human + ai to history
RunnableWithMessageHistory does these four steps on every call, for every user separately.

02Memory types and cost

Every message you resend costs tokens. Long chats need a strategy:

TypeIdeaGood forLangChain v1 way
Bufferkeep all messagesshort chatshistory list / checkpointer
Windowkeep last k messageslong chats, low costtrim_messages(strategy="last")
SummaryLLM summarises old messagesvery long chatsSummarizationMiddleware (Lab 14)
Vector / long-termsave facts in a databasepersonal assistantsLangGraph Store
turn 1
2 messages
turn 5
10 messages
turn 20
40 messages
trimmed (last 6)
6 messages

Messages sent on each call with buffer memory: the system message, all old turns and the new question (2 × turn number). Cost grows with the chat; trimming keeps it flat.

Version note (LangChain 1.x): RunnableWithMessageHistory and InMemoryChatMessageHistory still work but now print a deprecation warning. For new agents the recommended memory is a checkpointer + thread_id (Lab 13 and Lab 17). Learn this lab’s version too: you will see it in most existing code.

03Hands-on: run it here

The stand-in LLM can only “remember” what is inside the messages it receives, exactly like a real model. Watch Ajeet’s session remember and Priya’s session start empty.

lab05_memory.py
from standins import RuleModel
from langchain.messages import SystemMessage, HumanMessage, AIMessage, trim_messages
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory

class EchoMemory(RuleModel):
    """Stand-in that can only 'remember' what is inside the messages it receives - exactly like a real LLM."""
    def _pick(self, messages):
        seen = " ".join(str(m.content) for m in messages[:-1])
        q = str(messages[-1].content).lower()
        if "name" in q or "summarise" in q:
            facts = [f for f in ["Ajeet", "Pune", "3-day trip", "Rs 15,000", "mountains"] if f.lower() in seen.lower()]
            return "You told me: " + ", ".join(facts) if facts else "Sorry, I don't know that - you haven't told me yet."
        return "Noted!"
llm = EchoMemory()

print("no memory :", llm.invoke("What is my name and where do I live?").content)

chat_history = [SystemMessage("You are a friendly assistant. Keep answers short.")]
def chat(user_input):
    chat_history.append(HumanMessage(user_input))
    response = llm.invoke(chat_history)
    chat_history.append(AIMessage(response.content))
    return response.content
chat("Hi, my name is Ajeet and I live in Pune.")
print("manual    :", chat("What is my name and where do I live?"), f"({len(chat_history)} messages sent)")

prompt = ChatPromptTemplate.from_messages([("system", "You are a helpful travel assistant."), MessagesPlaceholder("history"), ("human", "{input}")])
store = {}
def get_session_history(session_id):
    if session_id not in store: store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]
chatbot = RunnableWithMessageHistory(prompt | llm, get_session_history, input_messages_key="input", history_messages_key="history")
cfg = {"configurable": {"session_id": "ajeet"}}
chatbot.invoke({"input": "I want a 3-day trip from Pune with Rs 15,000 budget."}, config=cfg)
chatbot.invoke({"input": "I like mountains."}, config=cfg)
print("ajeet     :", chatbot.invoke({"input": "Summarise my requirements."}, config=cfg).content)
print("priya     :", chatbot.invoke({"input": "Summarise my requirements."}, config={"configurable": {"session_id": "priya"}}).content)
print("store     :", {k: len(v.messages) for k, v in store.items()})

trimmed = trim_messages(store["ajeet"].messages, max_tokens=12, strategy="last", token_counter=lambda ms: sum(len(str(m.content).split()) for m in ms), start_on="human")
print("trimmed   :", len(store["ajeet"].messages), "->", len(trimmed), [m.content[:30] for m in trimmed])
no memory : Sorry, I don't know that - you haven't told me yet. manual : You told me: Ajeet, Pune (5 messages sent) ajeet : You told me: Pune, 3-day trip, Rs 15,000, mountains priya : Sorry, I don't know that - you haven't told me yet. store : {'ajeet': 6, 'priya': 2} trimmed : 6 -> 2 ['Summarise my requirements.', 'You told me: Pune, 3-day trip,']
Runs here without any API key. A small stand-in plays the LLM (RuleModel or ScriptedModel from standins.py), so every output below is from a real run of this code. In Colab you swap one line: llm = ChatGroq(model="openai/gpt-oss-20b").

04Hands-on: Ajeetkumar’s Colab notebook

Colab setup (same in every notebook): click 🔑 Secrets in the Colab sidebar, add groq_key with your free key from console.groq.com, switch on Notebook access, then run the cells from the top.
Demo · Notebook player · Lab 05

05Practice tasks

  1. Build a “Doctor appointment” bot that remembers name, age and symptoms.
  2. Keep only the last 4 messages using trim_messages inside the chain.
  3. Show that two session ids keep separate memories.

Try these in the same Colab notebook after you finish the lab. Change one thing at a time and look at the output again.

06Summary

07Quick quiz

1. Why does the LLM forget your name between calls?

2. What gives each user their own memory?

3. How to stop costs growing in a long chat?

4. Recommended memory for agents in LangChain 1.x?

5. Which survives a Colab restart?

08References

Created by Ajeetkumar