RunnableWithMessageHistory (In-Memory)
The last lesson called format_messages, invoked the model, and appended to history on every turn. RunnableWithMessageHistory wraps prompt | model and does that load/save work inside invoke. Pass a session_id in config. Here the store is a Python dict — one InMemoryChatMessageHistory per id.
Plain loop vs wrapper
Same ChatPromptTemplate as before. Only the per-turn history steps move inside the wrapper.
Previous lesson — plain loop
format_messages(history=…) model.invoke(messages) history.add_user_message(…) history.add_ai_message(…)
RunnableWithMessageHistory
chain_with_history.invoke(
{"input": question},
config={"configurable": {
"session_id": "demo-1"
}},
)
# load, run, save inside invokeinvoke loads history, runs the chain, and saves the new turn — no manual append loop.Each invoke
session_id picks the history object. The wrapper loads it, runs the chain, then saves the new question and reply.
Steps inside one invoke (session_id=demo-1):
config
session_id → get_session_history
load
InMemoryChatMessageHistory.messages
chain
prompt | model
save
add_user_message + add_ai_message
session_id on the next invoke reuses the stored turns.The store
get_session_history looks up or creates one InMemoryChatMessageHistory per session_id.
In-memory store — one history per session_id
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]session_id to its own InMemoryChatMessageHistory. History is lost when the process exits.from langchain_core.chat_history import InMemoryChatMessageHistory
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]Persistent backends
In-memory data is gone when the script exits. Later lessons swap get_session_history for Redis, PostgreSQL, or MongoDB — the wrapper and chain stay the same:
Redis Chat Message History
Fast cache, optional TTL
PostgreSQL Chat Message History
Durable SQL table
MongoDB Chat Message History
Document store, Atlas
get_session_history — the wrapper and chain stay the same.Wrap and invoke
Attach the wrapper to your chain, then call invoke with the question and session_id.
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)config = {"configurable": {"session_id": "demo-1"}}
reply = chain_with_history.invoke(
{"input": question},
config=config,
)The demo script
runnable_with_message_history_demo.py runs the same two HTML <a> questions with session_id="demo-1".
Download the code
runnable_with_message_history_demo.py
Two HTML questions — one session_id
InMemoryChatMessageHistory from langchain_core — no database required. Venv from Project Setup.Run it
Activate the venv from Project Setup, then:
python runnable_with_message_history_demo.pysession_id.Quick reference
RunnableWithMessageHistory— wrap an LCEL chain; loads and saves history on eachinvoke.InMemoryChatMessageHistory— one object persession_idin a Python dict.session_id— set inconfig["configurable"]; keeps one session's turns separate from another.input_messages_key/history_messages_key— must match the names in your prompt template.- Persistent backends: Redis, PostgreSQL, MongoDB.
- Docs: Message history how-to · RunnableWithMessageHistory.
What's Next
Next: Redis Chat Message History — store sessions in Redis with optional TTL.