Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from sentence_transformers import SentenceTransformer
|
| 3 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
| 4 |
+
import re
|
| 5 |
+
from huggingface_hub import InferenceClient
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
st.set_page_config(layout="wide")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
my_initial_rag_text = f"""This is a RAG (Retrieval-Augmented Generation) chatbot application built with Streamlit that combines document context with LLM responses. Here's a breakdown of its main components:
|
| 14 |
+
|
| 15 |
+
1. Initial Setup:
|
| 16 |
+
- Uses Streamlit for the web interface
|
| 17 |
+
- Employs SentenceTransformer for generating embeddings
|
| 18 |
+
- Uses HuggingFace's InferenceClient for LLM interaction
|
| 19 |
+
- Has a default text about a GRNET training module on LLMs
|
| 20 |
+
|
| 21 |
+
2. State Management:
|
| 22 |
+
- Maintains several session state variables for:
|
| 23 |
+
- RAG text (the context document)
|
| 24 |
+
- LLM model selection
|
| 25 |
+
- Chat message history
|
| 26 |
+
- Embeddings and sentences
|
| 27 |
+
- HuggingFace client
|
| 28 |
+
|
| 29 |
+
3. Interface Layout:
|
| 30 |
+
- Split into two columns (1:2 ratio):
|
| 31 |
+
- Left column: Model selection dropdown and RAG text input
|
| 32 |
+
- Right column: Chat interface and message history
|
| 33 |
+
|
| 34 |
+
4. Core Functionality:
|
| 35 |
+
- RAG Implementation:
|
| 36 |
+
- Splits the context document into sentences
|
| 37 |
+
- When a user asks a question, it:
|
| 38 |
+
- Converts the question into embeddings
|
| 39 |
+
- Finds the 3 most relevant sentences from the context
|
| 40 |
+
- Adds these relevant pieces as context to the prompt
|
| 41 |
+
|
| 42 |
+
- Chat Interface:
|
| 43 |
+
- Streams responses from the LLM
|
| 44 |
+
- Maintains a chat history
|
| 45 |
+
- Shows message history and augmented prompts
|
| 46 |
+
- Uses different avatars for user and AI messages
|
| 47 |
+
|
| 48 |
+
5. Model Options:
|
| 49 |
+
- Offers three LLM choices:
|
| 50 |
+
- Mistral-7B-Instruct-v0.3 (default)
|
| 51 |
+
- Qwen2.5-72B-Instruct
|
| 52 |
+
- Zephyr-7b-beta
|
| 53 |
+
|
| 54 |
+
The application essentially creates an intelligent chatbot that can answer questions while taking into account the context provided in the RAG text, making it particularly useful for domain-specific Q&A scenarios.
|
| 55 |
+
|
| 56 |
+
A disclaimer at the bottom reminds users about potential LLM inaccuracies and the need for verification of responses.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
# Check if the LLM model is not already in the session state
|
| 60 |
+
if "my_llm_model" not in st.session_state:
|
| 61 |
+
# Set the default LLM model to "mistralai/Mistral-7B-Instruct-v0.3"
|
| 62 |
+
st.session_state["my_llm_model"] = "mistralai/Mistral-7B-Instruct-v0.3"
|
| 63 |
+
# Check if the SPACE_ID environment variable is not already in the session state
|
| 64 |
+
if "my_space" not in st.session_state:
|
| 65 |
+
st.session_state["my_space"] = os.environ.get("SPACE_ID")
|
| 66 |
+
|
| 67 |
+
# Function to update the LLM model client
|
| 68 |
+
def update_llm_model():
|
| 69 |
+
if st.session_state["my_space"]:
|
| 70 |
+
# Initialize the client with the model if SPACE_ID is available
|
| 71 |
+
st.session_state["client"] = InferenceClient(st.session_state["my_llm_model"])
|
| 72 |
+
else:
|
| 73 |
+
# Initialize the client with the model and token if SPACE_ID is not available
|
| 74 |
+
st.session_state["client"] = InferenceClient(st.session_state["my_llm_model"], token=os.getenv("HF_TOKEN"))
|
| 75 |
+
|
| 76 |
+
# Check if the client is not already in the session state
|
| 77 |
+
if "client" not in st.session_state:
|
| 78 |
+
update_llm_model()
|
| 79 |
+
|
| 80 |
+
# Check if the embeddings model is not already in the session state
|
| 81 |
+
if "embeddings_model" not in st.session_state:
|
| 82 |
+
# We will use the all-MiniLM-L6-v2 model for embeddings
|
| 83 |
+
st.session_state["embeddings_model"] = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
|
| 84 |
+
|
| 85 |
+
MAXIMUM_TOKENS = 512
|
| 86 |
+
|
| 87 |
+
my_system_instructions = "You are a helpful assistant. Be brief and concise. Provide your answers in 100 words or less."
|
| 88 |
+
|
| 89 |
+
first_message = "Hello, how can I help you today?"
|
| 90 |
+
|
| 91 |
+
# Check if the chat messages are not already in the session state
|
| 92 |
+
if "my_chat_messages" not in st.session_state:
|
| 93 |
+
# Initialize the chat messages list in the session state
|
| 94 |
+
st.session_state["my_chat_messages"] = []
|
| 95 |
+
# Add the system instructions to the chat messages
|
| 96 |
+
st.session_state["my_chat_messages"].append({"role": "system", "content": my_system_instructions})
|
| 97 |
+
|
| 98 |
+
def delete_chat_messages():
|
| 99 |
+
for key in st.session_state.keys():
|
| 100 |
+
if key != "my_rag_text":
|
| 101 |
+
del st.session_state[key]
|
| 102 |
+
|
| 103 |
+
augmented_prompt = ""
|
| 104 |
+
|
| 105 |
+
# Create two columns with a 1:2 ratio
|
| 106 |
+
column_1, column_2 = st.columns([1, 2])
|
| 107 |
+
|
| 108 |
+
# In the first column
|
| 109 |
+
with column_1:
|
| 110 |
+
# Display a disclaimer about the potential inaccuracies of Large Language Models
|
| 111 |
+
st.expander("Disclaimer", expanded=False).markdown("""This application and code (hereafter referred to as the 'Software') is a proof of concept at an experimental stage and is not intended to be used as a production environment. The Software is provided as is, wihtout any warranties of any kind, expressed or implied and the user assumes full responsibility for its use, implementation, and legal compliance.
|
| 112 |
+
|
| 113 |
+
The developers of the Software shall not be liable for any damages, losses, claims, or liabilities arising from the Software, including but not limited to the usage of artificial intelligence and machine learning, related errors, third-party tool failures, security breaches, intellectual property violations, legal or regulatory non-compliance, deployment risks, or any indirect, incidental, or consequential damages.
|
| 114 |
+
|
| 115 |
+
Large Language Models may provide wrong answers. Please verify the answers and comply with applicable laws and regulations.
|
| 116 |
+
|
| 117 |
+
The user agrees to indemnify and hold harmless the developers of the Software from any related claims or disputes arising from the utilization of the Software by the user.
|
| 118 |
+
|
| 119 |
+
By using the Software, you agree to the terms and conditions of the disclaimer.""")
|
| 120 |
+
|
| 121 |
+
# Add a selectbox for model selection
|
| 122 |
+
st.selectbox("Select the model to use:",
|
| 123 |
+
["mistralai/Mistral-7B-Instruct-v0.3",
|
| 124 |
+
"Qwen/Qwen2.5-72B-Instruct",
|
| 125 |
+
"HuggingFaceH4/zephyr-7b-beta"],
|
| 126 |
+
key="my_llm_model", on_change=update_llm_model)
|
| 127 |
+
|
| 128 |
+
# Add a text area for RAG text input
|
| 129 |
+
st.text_area(label="Please enter your RAG text here:", value=my_initial_rag_text, height=500, key="my_rag_text", on_change=delete_chat_messages)
|
| 130 |
+
|
| 131 |
+
# Check if the sentences are not already in the session state
|
| 132 |
+
if "my_sentences" not in st.session_state:
|
| 133 |
+
my_sentences_split = st.session_state["my_rag_text"].split(".")
|
| 134 |
+
st.session_state["my_sentences"] = []
|
| 135 |
+
for my_sentence in my_sentences_split:
|
| 136 |
+
if my_sentence.strip():
|
| 137 |
+
st.session_state["my_sentences"].append(my_sentence.strip())
|
| 138 |
+
|
| 139 |
+
# Check if the embeddings are not already in the session state
|
| 140 |
+
if "my_embeddings" not in st.session_state:
|
| 141 |
+
st.session_state["my_embeddings"] = st.session_state["embeddings_model"].encode(st.session_state["my_sentences"])
|
| 142 |
+
|
| 143 |
+
with column_2:
|
| 144 |
+
# Create a container for the messages with a specified height
|
| 145 |
+
messages_container = st.container(height=500)
|
| 146 |
+
|
| 147 |
+
# Display the first message from the assistant
|
| 148 |
+
messages_container.chat_message("ai", avatar=":material/robot_2:").markdown(first_message)
|
| 149 |
+
|
| 150 |
+
# Iterate through the chat messages stored in the session state
|
| 151 |
+
for message in st.session_state["my_chat_messages"]:
|
| 152 |
+
if message["role"] == "user":
|
| 153 |
+
# Display user messages with a specific avatar - https://fonts.google.com/icons
|
| 154 |
+
messages_container.chat_message(message["role"], avatar=":material/psychology_alt:").markdown(message["content"])
|
| 155 |
+
elif message["role"] == "assistant":
|
| 156 |
+
# Display assistant messages with a specific avatar
|
| 157 |
+
messages_container.chat_message(message["role"], avatar=":material/robot_2:").markdown(message["content"])
|
| 158 |
+
|
| 159 |
+
# Check if there is a new prompt from the user
|
| 160 |
+
if prompt := st.chat_input("you may ask here your questions"):
|
| 161 |
+
|
| 162 |
+
# Encode the user's prompt to get its embedding
|
| 163 |
+
my_question_embedding = st.session_state.embeddings_model.encode([prompt])
|
| 164 |
+
|
| 165 |
+
# Calculate the cosine similarity between the prompt embedding and stored embeddings
|
| 166 |
+
similarity_to_question = cosine_similarity(my_question_embedding, st.session_state.my_embeddings).flatten()
|
| 167 |
+
|
| 168 |
+
# Number of sentences to keep based on similarity
|
| 169 |
+
nof_keep_sentences = 3
|
| 170 |
+
|
| 171 |
+
# Get the indices of the top similar sentences
|
| 172 |
+
sorted_indices = similarity_to_question.argsort()[::-1][:nof_keep_sentences]
|
| 173 |
+
|
| 174 |
+
# Retrieve the top similar sentences
|
| 175 |
+
sorted_sentences = [st.session_state.my_sentences[i] for i in sorted_indices]
|
| 176 |
+
|
| 177 |
+
# Construct the augmented prompt with the similar sentences
|
| 178 |
+
augmented_prompt = "Here is the context:"
|
| 179 |
+
for sentence in sorted_sentences:
|
| 180 |
+
augmented_prompt += "\n\n" + 20*"-" + f"\n\n{sentence}"
|
| 181 |
+
augmented_prompt += "\n\n" + 20*"-" + "\n\n" + "The user said:" + f"\n\n{prompt}"
|
| 182 |
+
|
| 183 |
+
# Display the user's prompt in the chat container with a specific avatar
|
| 184 |
+
messages_container.chat_message("user", avatar=":material/psychology_alt:").markdown(prompt)
|
| 185 |
+
# Append the augmented prompt to the chat messages in the session state
|
| 186 |
+
st.session_state["my_chat_messages"].append({"role": "user", "content": augmented_prompt})
|
| 187 |
+
# Create an empty container for the streaming response from the assistant
|
| 188 |
+
with messages_container.chat_message("ai", avatar=":material/robot_2:"):
|
| 189 |
+
response_placeholder = st.empty()
|
| 190 |
+
response = ""
|
| 191 |
+
# Stream the response from the assistant and update the placeholder
|
| 192 |
+
for chunk in st.session_state["client"].chat.completions.create(messages=st.session_state["my_chat_messages"], stream=True, max_tokens=512):
|
| 193 |
+
if chunk.choices[0].delta.content:
|
| 194 |
+
response += chunk.choices[0].delta.content
|
| 195 |
+
# Use markdown to update the response placeholder with the streamed content
|
| 196 |
+
response_placeholder.markdown(response)
|
| 197 |
+
|
| 198 |
+
# Remove the last message from the chat messages in the session state
|
| 199 |
+
st.session_state["my_chat_messages"].pop()
|
| 200 |
+
# Append the user's original prompt to the chat messages in the session state
|
| 201 |
+
st.session_state["my_chat_messages"].append({"role": "user", "content": prompt})
|
| 202 |
+
# Append the assistant's response to the chat messages in the session state
|
| 203 |
+
st.session_state["my_chat_messages"].append({"role": "assistant", "content": response})
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
# Display the chat messages history
|
| 207 |
+
st.write("Messages History:")
|
| 208 |
+
st.json(st.session_state["my_chat_messages"], expanded=False)
|
| 209 |
+
# Display the augmented prompt used for generating the response
|
| 210 |
+
st.write("Augmented prompt:")
|
| 211 |
+
st.json({"augmented_prompt": augmented_prompt}, expanded=False)
|