Spaces:
Sleeping
Sleeping
Syed Junaid Iqbal
commited on
Commit
β’
5887a43
1
Parent(s):
1534aac
Upload app.py
Browse files
app.py
ADDED
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import subprocess
|
2 |
+
|
3 |
+
import streamlit as st
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
6 |
+
from langchain.vectorstores import FAISS
|
7 |
+
from langchain.embeddings import FastEmbedEmbeddings # General embeddings from HuggingFace models.
|
8 |
+
from langchain.memory import ConversationBufferMemory
|
9 |
+
from langchain.callbacks.manager import CallbackManager
|
10 |
+
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
|
11 |
+
from htmlTemplates import css, bot_template, user_template
|
12 |
+
from langchain.llms import LlamaCpp # For loading transformer models.
|
13 |
+
from langchain.document_loaders import PyPDFLoader, TextLoader, CSVLoader
|
14 |
+
from langchain.chains import RetrievalQA
|
15 |
+
from langchain.prompts import PromptTemplate
|
16 |
+
from langchain import hub
|
17 |
+
import os
|
18 |
+
import glob
|
19 |
+
import shutil
|
20 |
+
|
21 |
+
os.environ['FAISS_NO_AVX2'] = '1'
|
22 |
+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
23 |
+
|
24 |
+
|
25 |
+
def load_document_text():
|
26 |
+
"""
|
27 |
+
input : path to the document
|
28 |
+
output: list of loaded document
|
29 |
+
"""
|
30 |
+
|
31 |
+
documents = []
|
32 |
+
|
33 |
+
for dox in os.listdir(path= "./documents/"):
|
34 |
+
dir = os.path.join("./documents/", dox)
|
35 |
+
|
36 |
+
if dox.endswith(".pdf"):
|
37 |
+
documents.extend( PyPDFLoader(dir).load() )
|
38 |
+
|
39 |
+
elif dox.endswith(".txt"):
|
40 |
+
documents.extend( TextLoader(dir).load() )
|
41 |
+
|
42 |
+
elif dox.endswith(".csv"):
|
43 |
+
documents.extend( CSVLoader(dir).load() )
|
44 |
+
|
45 |
+
return documents
|
46 |
+
|
47 |
+
def get_text_chunks(documents):
|
48 |
+
"""
|
49 |
+
For the compute purpose we will split the document into multiple smaller chunks.
|
50 |
+
|
51 |
+
IMPORTANT : If the chunks too small we will miss the context and if its too large we will have longer compute time
|
52 |
+
"""
|
53 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
54 |
+
chunk_size=1024,
|
55 |
+
chunk_overlap=100,
|
56 |
+
)
|
57 |
+
|
58 |
+
st.session_state.text_chunks = text_splitter.split_documents(documents)
|
59 |
+
|
60 |
+
|
61 |
+
def get_vectorstore():
|
62 |
+
"""
|
63 |
+
given the chunks, we will embed them into vector stores
|
64 |
+
"""
|
65 |
+
|
66 |
+
if len(glob.glob("./vectordb/*.faiss")) == 0:
|
67 |
+
st.session_state.vectorstore = FAISS.from_documents(documents= st.session_state.text_chunks,
|
68 |
+
embedding= st.session_state.embeddings)
|
69 |
+
# save the file
|
70 |
+
st.session_state.vectorstore.save_local("./vectordb")
|
71 |
+
else:
|
72 |
+
st.session_state.vectorstore = FAISS.load_local("./vectordb/",
|
73 |
+
st.session_state.embeddings)
|
74 |
+
|
75 |
+
|
76 |
+
def get_conversation_chain():
|
77 |
+
"""
|
78 |
+
This is a langchain model where we will be binding the runner to infer data from LLM
|
79 |
+
"""
|
80 |
+
model_path = st.session_state.model
|
81 |
+
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])
|
82 |
+
|
83 |
+
llm = LlamaCpp(model_path= model_path,
|
84 |
+
n_ctx=4000,
|
85 |
+
max_tokens= 4000,
|
86 |
+
fp = 50,
|
87 |
+
n_batch = 512,
|
88 |
+
callback_manager = callback_manager,
|
89 |
+
verbose=True)
|
90 |
+
|
91 |
+
memory = ConversationBufferMemory(
|
92 |
+
memory_key='chat_history', return_messages=False)
|
93 |
+
|
94 |
+
|
95 |
+
prompt_template = """You are a personal HR Bot assistant for answering any questions about Companies policies
|
96 |
+
You are given a question and a set of documents.
|
97 |
+
If the user's question requires you to provide specific information from the documents, give your answer based only on the examples provided below. DON'T generate an answer that is NOT written in the provided examples.
|
98 |
+
If you don't find the answer to the user's question with the examples provided to you below, answer that you didn't find the answer in the documentation and propose him to rephrase his query with more details.
|
99 |
+
Use bullet points if you have to make a list, only if necessary.
|
100 |
+
|
101 |
+
QUESTION: {question}
|
102 |
+
|
103 |
+
DOCUMENTS:
|
104 |
+
=========
|
105 |
+
{context}
|
106 |
+
=========
|
107 |
+
Finish by proposing your help for anything else.
|
108 |
+
"""
|
109 |
+
|
110 |
+
rag_prompt_custom = PromptTemplate.from_template(prompt_template)
|
111 |
+
|
112 |
+
prompt = hub.pull("rlm/rag-prompt")
|
113 |
+
|
114 |
+
conversation_chain = RetrievalQA.from_chain_type(
|
115 |
+
llm,
|
116 |
+
retriever= st.session_state.vectorstore.as_retriever(),
|
117 |
+
chain_type_kwargs={"prompt": prompt},
|
118 |
+
)
|
119 |
+
conversation_chain.callback_manager = callback_manager
|
120 |
+
conversation_chain.memory = ConversationBufferMemory()
|
121 |
+
|
122 |
+
return conversation_chain
|
123 |
+
|
124 |
+
|
125 |
+
def handle_userinput():
|
126 |
+
|
127 |
+
clear = False
|
128 |
+
|
129 |
+
# Add clear chat button
|
130 |
+
if st.button("Clear Chat history"):
|
131 |
+
clear = True
|
132 |
+
st.session_state.messages = []
|
133 |
+
|
134 |
+
if "messages" not in st.session_state:
|
135 |
+
st.session_state.messages = [{"role": "assistant", "content": "How can I help you?"}]
|
136 |
+
|
137 |
+
for msg in st.session_state.messages:
|
138 |
+
st.chat_message(msg["role"]).write(msg["content"])
|
139 |
+
|
140 |
+
if prompt := st.chat_input():
|
141 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
142 |
+
st.chat_message("user").write(prompt)
|
143 |
+
if clear:
|
144 |
+
st.session_state.conversation.clean()
|
145 |
+
|
146 |
+
msg = st.session_state.conversation.run(prompt)
|
147 |
+
print(msg)
|
148 |
+
st.session_state.messages.append({"role": "assistant", "content": msg})
|
149 |
+
st.chat_message("assistant").write(msg)
|
150 |
+
|
151 |
+
|
152 |
+
|
153 |
+
# Function to apply rounded edges using CSS
|
154 |
+
def add_rounded_edges(image_path="./randstad_featuredimage.png", radius=30):
|
155 |
+
st.markdown(
|
156 |
+
f'<style>.rounded-img{{border-radius: {radius}px; overflow: hidden;}}</style>',
|
157 |
+
unsafe_allow_html=True,)
|
158 |
+
st.image(image_path, use_column_width=True, output_format='auto')
|
159 |
+
|
160 |
+
|
161 |
+
# Delete our vector DB
|
162 |
+
def delete_db(directory_path = './vectordb/'):
|
163 |
+
|
164 |
+
# Check if the directory exists
|
165 |
+
if os.path.exists(directory_path) and len(os.listdir(directory_path)) > 0:
|
166 |
+
# Iterate over all files in the directory and remove them
|
167 |
+
for filename in os.listdir(directory_path):
|
168 |
+
file_path = os.path.join(directory_path, filename)
|
169 |
+
try:
|
170 |
+
if os.path.isfile(file_path) or os.path.islink(file_path):
|
171 |
+
os.unlink(file_path)
|
172 |
+
elif os.path.isdir(file_path):
|
173 |
+
shutil.rmtree(file_path)
|
174 |
+
except Exception as e:
|
175 |
+
print(f"Error deleting {file_path}: {e}")
|
176 |
+
else:
|
177 |
+
print(f"The directory {directory_path} does not exist.")
|
178 |
+
|
179 |
+
|
180 |
+
|
181 |
+
def save_uploaded_file(uploaded_file):
|
182 |
+
save_directory = "./documents/"
|
183 |
+
file_path = os.path.join(save_directory, uploaded_file.name)
|
184 |
+
with open(file_path, "wb") as f:
|
185 |
+
f.write(uploaded_file.getvalue())
|
186 |
+
return file_path
|
187 |
+
|
188 |
+
|
189 |
+
def load_dependencies():
|
190 |
+
# append documents to a list
|
191 |
+
doc_list = load_document_text()
|
192 |
+
|
193 |
+
# get the text chunks
|
194 |
+
get_text_chunks(doc_list)
|
195 |
+
|
196 |
+
# create vector store
|
197 |
+
get_vectorstore()
|
198 |
+
|
199 |
+
# create conversation chain
|
200 |
+
st.session_state.conversation = get_conversation_chain()
|
201 |
+
|
202 |
+
|
203 |
+
def main():
|
204 |
+
load_dotenv()
|
205 |
+
st.set_page_config(page_title="Chat with multiple Files",
|
206 |
+
page_icon=":books:")
|
207 |
+
st.write(css, unsafe_allow_html=True)
|
208 |
+
|
209 |
+
|
210 |
+
if "conversation" not in st.session_state:
|
211 |
+
st.session_state.conversation = None
|
212 |
+
if "chat_history" not in st.session_state:
|
213 |
+
st.session_state.chat_history = None
|
214 |
+
|
215 |
+
st.title("π¬ Randstad HR Chatbot")
|
216 |
+
st.subheader("π A HR powered by Generative AI")
|
217 |
+
|
218 |
+
# default model
|
219 |
+
st.session_state.model = "./models/mistral-7b-instruct-v0.2.Q5_K_M.gguf"
|
220 |
+
|
221 |
+
|
222 |
+
# Embedding Model
|
223 |
+
st.session_state.embeddings = FastEmbedEmbeddings( model_name= "BAAI/bge-small-en-v1.5",
|
224 |
+
cache_dir="./embedding_model/")
|
225 |
+
|
226 |
+
with st.sidebar:
|
227 |
+
|
228 |
+
# calling a
|
229 |
+
add_rounded_edges()
|
230 |
+
|
231 |
+
st.subheader("Select Your Embedding Model Model")
|
232 |
+
st.session_state.model = st.selectbox( 'Models', tuple( glob.glob('./models/*.gguf') ) )
|
233 |
+
|
234 |
+
|
235 |
+
st.subheader("Your documents")
|
236 |
+
|
237 |
+
# Space to Upload a Document
|
238 |
+
docs = st.file_uploader(
|
239 |
+
"Upload File (pdf,text,csv...) and click 'Process'", accept_multiple_files=True)
|
240 |
+
|
241 |
+
# Define a process button
|
242 |
+
if st.button("Process"):
|
243 |
+
|
244 |
+
# delete the old embeddings
|
245 |
+
delete_db()
|
246 |
+
|
247 |
+
# then Embedd new documents
|
248 |
+
with st.spinner("Processing"):
|
249 |
+
|
250 |
+
|
251 |
+
# iterate over updated files and save them to the local directory (i.e. "Documents") using a helper function
|
252 |
+
for file in docs:
|
253 |
+
save_uploaded_file(file)
|
254 |
+
|
255 |
+
"""
|
256 |
+
using the helper function below lets load our dependencies
|
257 |
+
Step 1 : Load the documents
|
258 |
+
Step 2 : Break them into Chunks
|
259 |
+
Step 3 : Create Embeddings and save them to Vector DB
|
260 |
+
Step 4 : Get our conversation chain
|
261 |
+
"""
|
262 |
+
load_dependencies()
|
263 |
+
|
264 |
+
# Load our model
|
265 |
+
if len(glob.glob("./vectordb/*.faiss")) == 0:
|
266 |
+
load_dependencies()
|
267 |
+
get_vectorstore()
|
268 |
+
else:
|
269 |
+
get_vectorstore()
|
270 |
+
st.session_state.conversation = get_conversation_chain()
|
271 |
+
|
272 |
+
handle_userinput()
|
273 |
+
|
274 |
+
# # load dependencies -> chaunks of documents -> Embeddings -> Inference
|
275 |
+
# load_dependencies()
|
276 |
+
|
277 |
+
|
278 |
+
|
279 |
+
if __name__ == '__main__':
|
280 |
+
|
281 |
+
command = 'CMAKE_ARGS="-DLLAMA_CUBLAS=on" FORCE_CMAKE=1 pip install llama-cpp-python --no-cache-dir'
|
282 |
+
|
283 |
+
# Run the command using subprocess
|
284 |
+
try:
|
285 |
+
subprocess.run(command, shell=True, check=True)
|
286 |
+
print("Command executed successfully.")
|
287 |
+
except subprocess.CalledProcessError as e:
|
288 |
+
print(f"Error: {e}")
|
289 |
+
|
290 |
+
# Run the apps
|
291 |
+
main()
|