In the post Chain Using LangChain Expression Language With Examples we created a simple chatbot using Streamlit UI. As pointed out there itself Large Language Models (LLMs) are stateless. By design, they do not retain memory of past interactions, treating each API call as a fresh, independent request. In this tutorial we’ll see how to create a chatbot that stores the chat history using MessagesPlaceholder and RunnableWithMessageHistory in LangChain. Sometimes it is required to keep chat history in order to provide contextual information to the model.
MessagesPlaceholder in LangChain
MessagesPlaceholder in LangChain is a prompt component which is used to inject a dynamic list of messages, such as chat history, directly into a ChatPromptTemplate.
- When user interacts with a model, the messages sent by user are classified as human messages whereas the replies from the model are classified as AI messages.
- You can store the previous human/AI messages and inject it as previous chat history, when sending a query to model, using MessagesPlaceholder.
That way you can provide context to the model. You need to pass a key with MessagesPlaceholder by which it can identify which variable to use as messages.
Here is a simple example where some of the messages are manually setup as human and AI messages.
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
[
("system", "You are a helpful assistant."),
MessagesPlaceholder("history"),
("human", "{question}"),
]
)
prompt.invoke(
{
"history": [("human", "what's 5 + 2"), ("ai", "5 + 2 is 7")],
"question": "now multiply that by 4",
}
)
As you can see, MessagesPlaceholder is passed as one of the prompt components. The key passed to it is "history" which is same as the key "history" used in the dictionary passed with prompt.invoke().
So essentially what is the sent to the model is as given below-
ChatPromptValue(messages=[
SystemMessage(content="You are a helpful assistant."),
HumanMessage(content="what's 5 + 2"),
AIMessage(content="5 + 2 is 7"),
HumanMessage(content="now multiply that by 4"),
])
The latest human message is "now multiply that by 4", but previous messages are also sent to the model to retain the context.
RunnableWithMessageHistory in LangChain
RunnableWithMessageHistory class in LangChain is used to manage chat message history for another Runnable. The highlight of using RunnableWithMessageHistory is the session support. It uses a session_id to look up or create a specific chat message history, allowing it to handle multiple concurrent users or conversations.
Configuring RunnableWithMessageHistory
To use RunnableWithMessageHistory, you must provide the following:
- get_session_history: A factory function that takes a single positional argument session_id of type string and returns the chat history instance associated with the passed session_id.
- input_messages_key: It specifies which key contains the current user message. Required if the wrapped chain accepts a dictionary as input;
- history_messages_key: Specifies the key where historical messages should be inserted in the prompt template.
- output_messages_key: it identifies the key containing the model's response, required if the wrapped chain returns a dictionary.
For example-
with_message_history = RunnableWithMessageHistory(
chatbot_chain,
get_session_history,
input_messages_key="user_input",
history_messages_key="chat_history",
)
How to use RunnableWithMessageHistory
When invoking a wrapped chain, you must pass the session ID in the configuration:
with_message_history.invoke(
{"user_input": user_input},
config = {"configurable": {"session_id": "user_123"}}
)
Using chat_message_histories module
chat_message_histories module in LangChain provides standardized way to store history of the message interactions in a chat. This module provides many classes to seamlessly integrate with, file system (FileChatMessageHistory), various databases (Cassandra, Postgres, Redis), In-Memory Storage (ChatMessageHistory).
Chatbot with chat history using LangChain and StreamLit
Using the above mentioned classes of LangChain, it becomes very easy to store conversational history.
In the nutshell role of the classes is as given below-
MessagesPlaceholder- To inject the chat history so that model has the contextual information.
RunnableWithMessageHistory- To manage chat history and provide session support. That way you can have many instances of the chatbot maintaining their own sessions.
ChatMessageHistory- Which acts as a in-memory storage of the chat history. Good for initial demo but for production use a DB backed chatbot.
Chatbot first asks for user’s ID, that is done to use the passed userID as the sessionID.
Streamlit UI related code is kept in a separate file.
app.py
import streamlit as st
from chatbot import generate_response
# Streamlit app to demonstrate the simple chain
st.set_page_config(page_title="Chatbot", layout="centered")
st.title(🤖 Chatbot With Context")
# Initialize session state
if "user_id" not in st.session_state:
st.session_state.user_id = None
if "chat_history" not in st.session_state:
st.session_state.chat_history = []
#Ensure user_id is set before allowing chat interactions
if st.session_state.user_id is None:
user_id_input = st.text_input("Enter your User ID:")
if st.button("Confirm User ID"):
if user_id_input.strip() == "":
st.warning("User ID is required to continue.")
else:
st.session_state.user_id = user_id_input.strip()
st.success(f"User ID set: {st.session_state.user_id}")
if st.session_state.user_id:
for message in st.session_state.chat_history:
with st.chat_message(message["role"]):
st.markdown(message["content"])
user_input = st.chat_input("Enter your query:")
if user_input:
st.session_state.chat_history.append( {"role": "user", "content": user_input})
with st.chat_message("user"):
st.markdown(user_input)
response = generate_response(user_input, st.session_state.user_id)
st.session_state.chat_history.append({"role": "assistant", "content": response})
with st.chat_message("assistant"):
st.markdown(f"**Chatbot Response:** {response}")
else:
st.warning("Please enter a query to get a response.")
chatbot.py
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_core.prompts import MessagesPlaceholder
from langchain_ollama import ChatOllama
from langchain_core.messages import SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# Define system and human message templates
system_message = SystemMessage(content="You are a helpful assistant that responds to user queries.")
# Define the output parser
parser = StrOutputParser()
# In-Memory Store to hold chat histories for different sessions
# (not suitable for production, just for demo purposes)
store = {}
# Function to retrieve chat history for a given session_id, or create a new one if it doesn't exist
def get_session_history(session_id: str):
print(f"Retrieving chat history for session_id: {session_id}")
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
def generate_response(user_input: str, session_id: str) -> str:
session_id = session_id or "default_session"
# Create a ChatPromptTemplate object with MessagePlaceholder for conversation history
prompt = ChatPromptTemplate.from_messages([
system_message,
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template("{user_input}")
])
# Initialize the model
model = ChatOllama(model="llama3.1", temperature=0.1)
# Chain the prompt, model, and parser together using RunnableWithMessageHistory
chatbot_chain = prompt | model | parser
with_message_history = RunnableWithMessageHistory(
chatbot_chain,
get_session_history,
input_messages_key="user_input",
history_messages_key="chat_history",
)
response = with_message_history.invoke(
{"user_input": user_input},
config={"configurable": {"session_id": session_id}}
)
return response
Drawbacks of using MessagesPlaceholder, RunnableWithMessageHistory, ChatMessageHistory
Using MessagesPlaceholder and ChatMessageHistory along with RunnableWithMessageHistory in LangChain provides powerful capability to store chat history, but they come with notable drawbacks regarding complexity, performance, and maintainability.
- Complexity in Debugging: Because the content is inserted dynamically in MessagesPlaceholder, it can be difficult to visualize the final prompt sent to the LLM, making debugging tricky.
- Context Management Overhead: MessagesPlaceholder only acts as a placeholder. It does not automatically manage the history, meaning the developer is responsible for passing the correct history list every time, increasing code complexity.
- Context Window Limits (Token Exhaustion): Without any trimming, chat history grows indefinitely. This leads to exceeding the LLL's token limit (context window) and increasing API costs.
- In-Memory Persistence: By default, in-memory history is lost when the application restarts. Storing conversations in memory can become a performance bottleneck for large-scale applications.
That's all for this topic Chatbot With Chat History - LangChain MessagesPlaceHolder. If you have any doubt or any suggestions to make please drop a comment. Thanks!
Related Topics
You may also like-


No comments:
Post a Comment