LangChain · Memory and message history
Chat models are stateless; RunnableWithMessageHistory stores and re-sends past messages per session, with trimming to keep the history within limits.
A chat model is stateless. Every call only sees the messages you send in that call. “Memory” is application code that:
- stores the conversation so far, per session
- inserts it into the prompt (the
MessagesPlaceholderfrom the templates post) - appends the new question and answer after each turn
RunnableWithMessageHistory wraps a chain and does exactly this, using a get_session_history(session_id) function. Long histories eventually exceed the context window or cost too much, so trim_messages keeps only the most recent part.
To show what really reaches the model, I used a tiny model that just reports how many messages it received.
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.runnables import RunnableLambda
from langchain_core.messages import AIMessage, trim_messages
def echo_model(prompt_value):
msgs = prompt_value.to_messages()
return AIMessage(f"I received {len(msgs)} messages; last: {msgs[-1].content!r}")
prompt = ChatPromptTemplate.from_messages([("system", "Be brief."), MessagesPlaceholder("history"), ("human", "{q}")])
chain = prompt | RunnableLambda(echo_model)
store = {}
def get_history(session_id):
return store.setdefault(session_id, InMemoryChatMessageHistory())
chat = RunnableWithMessageHistory(chain, get_history, input_messages_key="q", history_messages_key="history")
for q in ["My name is Sahaj.", "I study ICT.", "What is my name?"]:
print("alice:", chat.invoke({"q": q}, config={"configurable": {"session_id": "alice"}}).content)
print("bob :", chat.invoke({"q": "What is my name?"}, config={"configurable": {"session_id": "bob"}}).content)
print("stored messages:", {k: len(v.messages) for k, v in store.items()})
trimmed = trim_messages(store["alice"].messages, max_tokens=2, token_counter=len, strategy="last")
print("trimmed to last 2:", [m.content for m in trimmed])
Output:
alice: I received 2 messages; last: 'My name is Sahaj.'
alice: I received 4 messages; last: 'I study ICT.'
alice: I received 6 messages; last: 'What is my name?'
bob : I received 2 messages; last: 'What is my name?'
stored messages: {'alice': 6, 'bob': 2}
trimmed to last 2: ['What is my name?', "I received 6 messages; last: 'What is my name?'"]Sources
- LangChain reference: RunnableWithMessageHistory — LangChain
- LangChain reference: trim_messages — LangChain
- LangChain docs: Short-term memory — LangChain