Conversational Chatbot Trained on Own Data: Streamlit and Langchain

Ashish Malhotra
5 min readApr 28, 2024

--

While creating a conversational chatbot, I stumbled upon umpteen videos and blogs. Most of these blogs describe the process as Document Loading →Text Splitting->Embedding->Conversational Retrieval Chain and it stops there. I was curious about how the chat history is loaded in the ConverstaionalRetrivalChain because this becomes the basis of our Streamlit app, where with the help of sessions we will register the chat history and pass it on to the Conversational Retrieval Chain.

The idea of this blog is not to share the process from Document Loading — — — ->Conversation Retrieval but to explain ConversationalRetrivalChain and the construction of our chatbot using Streamlit. If you want to know about the process before Conversation retrieval, please check this blog.

Let’s get started…..

Precursor is Document Loading →Text Splitting->Embedding is already completed and now we want to create a conversation channel with our chatbot. We will firstly be using RetrivalQA to retrieve the relevant parts of the document that will have answers to my question.

#Use retrivalQA for retrieving relevant parts of the data
from langchain.chains import RetrievalQA
retriever = db.as_retriever(search_type="similarity", search_kwargs={"k": 2})
res = retriever.get_relevant_documents("Former name of Kanpur")
res

Once this is done, we will now use ConversationalRetrivalChain to chat with our database. This chain is custombuilt for chatbot. It has two components, “question” and “chat_history”.

#Using conversationalretrivalChain since this has memory inbuilt

from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationalRetrievalChain

chat = ChatOpenAI(model="gpt-3.5-turbo-0125", temperature=0)
#matching_docs = db.similarity_search(query)

chain = ConversationalRetrievalChain.from_llm(llm=chat, retriever=retriever)

response = chain({"question":'Which year did Kanpur come into existance?', "chat_history":[]})
O/P of the above code

Now let’s prepopulate chat_history and ask another question. This way, we will know how “question”,”chat_history” and “answer” are populated.

response = chain({"question":'Which industries are in Kanpur?',"chat_history":[('Which year did Kanpur come into existance?','The city of Kanpur was established in the year 1207 by Raja Kanh Deo of the Kanhpuriya clan of Rajputs.')]})
print(respo
O/P from above code

As you can see chat_history gets populated with the earlier question that we asked. This chain continues as chat_history gets fed the latest question and answer duo.

response = chain({"question":'What is the most famous tourist spots in Kanpur?',"chat_history":[('Which year did Kanpur come into existance?','The city of Kanpur was established in the year 1207 by Raja Kanh Deo of the Kanhpuriya clan of Rajputs.','Which industries are in Kanpur?','Kanpur is known for its leather and textile industries. Additionally, the city has a significant presence in the chemical, fertilizer, and engineering industries.')]})
print(response)
O/P of above code

Now, lets turn our focus on the streamlit app. Below is the UI of our chatbot. User will enter OpenAI API key->uploads the PDF file and then chat with the app about the PDF.

Let’s have a look at the code.

# Chat UI title
st.header("Upload your own file and ask questions like ChatGPT")
st.subheader('File types supported: PDF')

# File uploader in the sidebar on the left
with st.sidebar:
# Input for OpenAI API Key
openai_api_key = st.text_input("OpenAI API Key", type="password")

# Check if OpenAI API Key is provided
if not openai_api_key:
st.info("Please add your OpenAI API key to continue.")
st.stop()

# Set OPENAI_API_KEY as an environment variable
os.environ["OPENAI_API_KEY"] = openai_api_key

# Initialize ChatOpenAI model
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-0125", streaming=True)
......................

with st.sidebar:
uploaded_files = st.file_uploader("Please upload your files", accept_multiple_files=True, type=None)

This is to make sure, user enters OpenAI API key in a password format. Once the openAI key is fed, the app connects to this key and ChatOpenAI is activated.

We are using st.file_uploader to upload the file. accept_multiple is set to True. Our system can accept multiple PDF files.

# Check if files are uploaded or YouTube URL is provided
if uploaded_files:
# Print the number of files uploaded or YouTube URL provided to the console
st.write("file uploaded successfully")

# Load the data and perform preprocessing only if it hasn't been loaded before
if "processed_data" not in st.session_state:
# Load the data from uploaded files
documents = []

if uploaded_files:
for uploaded_file in uploaded_files:
# Get the full file path of the uploaded file
file_path = os.path.join(os.getcwd(), uploaded_file.name)

# Save the uploaded file to disk
with open(file_path, "wb") as f:
f.write(uploaded_file.getvalue())




if file_path.endswith(".pdf"):
# Use UnstructuredFileLoader to load the PDF
loader = UnstructuredFileLoader(file_path)
loaded_documents = loader.load()

# Extend the main documents list with the loaded documents
documents.extend(loaded_documents)


# Chunk the data, create embeddings, and save in vectorstore
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1500, chunk_overlap=150)
document_chunks = text_splitter.split_documents(documents)


embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(document_chunks, embeddings)

# Store the processed data in session state for reuse
st.session_state.processed_data = {
"document_chunks": document_chunks,
"vectorstore": vectorstore,
}

else:
# If the processed data is already available, retrieve it from session state
document_chunks = st.session_state.processed_data["document_chunks"]
vectorstore = st.session_state.processed_data["vectorstore"]

# Initialize Langchain's QA Chain with the vectorstore
qa = ConversationalRetrievalChain.from_llm(llm, vectorstore.as_retriever())

As next steps, we are checking if the file is uploaded. If it is, then we load the file-> split it-> create embeddings -> store in ChromaDB. After this, we are storing document chunks (text splitter data) and vectoreStore (ChromaDB output) in a session. Once this is done, we are feeding data in ConversationalRetrovalChain.

    # Initialize chat history
if "messages" not in st.session_state:
st.session_state.messages = []

# Display chat messages from history on app rerun
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])

# Accept user input
if prompt := st.chat_input("Ask your questions?"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)

# Query the assistant using the latest chat history
history = [
f"{message['role']}: {message['content']}"
for message in st.session_state.messages
]

# Convert the string list to list of tuples
chat_history = [(message.split(": ")[0], message.split(": ")[1]) for message in history]


result = qa({
"question": prompt,
"chat_history": chat_history
})

# Display assistant response in chat message container
with st.chat_message("assistant"):
message_placeholder = st.empty()
full_response = result["answer"]
message_placeholder.markdown(full_response + "|")
message_placeholder.markdown(full_response)
print(full_response)
st.session_state.messages.append({"role": "assistant", "content": full_response})

Here, we are initializing chat history in st.session_state. Once the questions is asked by the user, we are appending the role and the question asked by the user under the “role” and “content” keys (these are message keys, where message is a session state variable). Ultimately we update the chat_history variable by populating role and content keys. Finally, the result (question and chat history) is fed into qa (ConversationalRetrivalChain variable). Response is displayed in the Chat Message Container.

You can find the complete code https://github.com/y-pred/Langchain/blob/main/Conversational%20Chatbot/Conversation_Chatbot_Streamlit_App.ipynb

--

--

Ashish Malhotra

Sales and Alliances professional. AI/ML Enthusiast. Back to coding after 8 years and I'm loving it.