jawakja commited on
Commit
1312805
·
verified ·
1 Parent(s): f5e0c7a

Upload chat_pdf.py

Browse files
Files changed (1) hide show
  1. chat_pdf.py +283 -0
chat_pdf.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import streamlit as st
4
+ from streamlit_chat import message
5
+ from langchain_community.vectorstores import Chroma
6
+ from langchain.prompts import PromptTemplate
7
+ from langchain_core.output_parsers import StrOutputParser
8
+ from langchain_ollama.chat_models import ChatOllama
9
+ from langchain.retrievers.multi_query import MultiQueryRetriever
10
+ from langchain.retrievers import ContextualCompressionRetriever
11
+ from langchain.retrievers.document_compressors.flashrank_rerank import FlashrankRerank
12
+ from flashrank import Ranker, RerankRequest
13
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
14
+ from langchain_ollama import OllamaEmbeddings
15
+ from langchain_community.embeddings import FastEmbedEmbeddings
16
+ from langchain_community.document_loaders import PyMuPDFLoader
17
+ from langchain.document_loaders.pdf import PyPDFDirectoryLoader
18
+ from langchain_community.document_loaders import WebBaseLoader
19
+ from langchain.prompts import ChatPromptTemplate
20
+ from langchain_core.runnables import RunnablePassthrough
21
+ from langchain.retrievers import BM25Retriever, EnsembleRetriever
22
+ from torch import cuda
23
+ from langchain_community.llms import LlamaCpp
24
+
25
+ device = "cuda" if cuda.is_available() else "cpu"
26
+
27
+ st.set_page_config(page_title="Chatbot", layout="wide")
28
+
29
+ class ChatPDF:
30
+ def __init__(self):
31
+ self.vector_db = None
32
+ self.llm = ChatOllama(model="hf.co/bartowski/Llama-3.2-3B-Instruct-GGUF:IQ4_XS")
33
+ self.llm = LlamaCpp(
34
+ model_path="/home/chatbot/.cache/huggingface/hub/models--bartowski--Meta-Llama-3.1-8B-Instruct-GGUF/snapshots/bf5b95e96dac0462e2a09145ec66cae9a3f12067/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf",
35
+ temperature=0.75, #to maintain randomness of generated text
36
+ n_gpu_layers=-1, #all layers of the model are loaded on the GPU if available
37
+ max_tokens=2000, #Sets the maximum number of tokens the model will generate for each inference
38
+ n_ctx=2048,
39
+ top_p=1,
40
+ # verbose=True, # Verbose is required to pass to the callback manager
41
+ )
42
+ self.llm2 = LlamaCpp(
43
+ model_path="/home/chatbot/.cache/huggingface/hub/models--bartowski--Llama-3.2-3B-Instruct-GGUF/snapshots/5ab33fa94d1d04e903623ae72c95d1696f09f9e8/Llama-3.2-3B-Instruct-IQ4_XS.gguf",
44
+ temperature=0.75,
45
+ n_gpu_layers=-1,
46
+ max_tokens=2000,
47
+ n_ctx=2048,
48
+ top_p=1,
49
+ )
50
+ # self.llm2 = LlamaCpp(
51
+ # model_path="/home/kja/project/models--bartowski--Llama-3.2-3B-Instruct-GGUF/snapshots/5ab33fa94d1d04e903623ae72c95d1696f09f9e8/Llama-3.2-3B-Instruct-IQ4_XS.gguf",
52
+ # temperature=0.75,
53
+ # n_gpu_layers=-1,
54
+ # max_tokens=2000,
55
+ # n_ctx=2048,
56
+ # top_p=1,
57
+ # )
58
+ self.chain = None
59
+ self.processed_files = []
60
+
61
+ def ingest(self, file_path=None, file_name=None, webpage_url=None):
62
+ self.vector_db = None
63
+ self.processed_files.clear()
64
+
65
+ if webpage_url:
66
+ loader = WebBaseLoader(webpage_url)
67
+ data = loader.load()
68
+ st.success(f"Data from {webpage_url} loaded successfully!")
69
+ else:
70
+ self.processed_files.append(file_name)
71
+ loader = PyMuPDFLoader(file_path=file_path)
72
+ data = loader.load()
73
+ st.success(f"{file_name} loaded successfully!")
74
+
75
+ #breaks the document in chunks of 1000 characters.the last 200 characters of last chunk is repeated in current chunk to maintain context continuity
76
+ #Overlapping preserves continuity, ensuring the chatbot understands full context when retrieving information.
77
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
78
+ chunks = text_splitter.split_documents(data)
79
+
80
+ self.vector_db = Chroma.from_documents(
81
+ documents=chunks,
82
+ embedding=FastEmbedEmbeddings(),
83
+ collection_metadata={'hnsw': "cosine"},
84
+ persist_directory='chromadbtest'
85
+ )
86
+ st.success("Vector database created successfully!")
87
+
88
+ QUERY_PROMPT = PromptTemplate(
89
+ input_variables=["question"],
90
+ template="""You are an AI language model assistant. Your task is to generate three \
91
+ different versions of the given user question to retrieve relevant documents from a vector \
92
+ database. By generating multiple perspectives on the user question, your goal is to help\
93
+ the user overcome some of the limitations of the distance-based similarity search. \
94
+ Provide these alternative questions separated by newlines. Original question: {question}""",
95
+ )
96
+
97
+ retriever = MultiQueryRetriever.from_llm(
98
+ self.vector_db.as_retriever(),
99
+ self.llm2,
100
+ #self.llm,
101
+ prompt=QUERY_PROMPT
102
+ )
103
+
104
+ keyword_retriever = BM25Retriever.from_documents(documents=chunks)
105
+ main_retriever = EnsembleRetriever(retrievers=[retriever, keyword_retriever], weights=[0.5, 0.5])
106
+ FlashrankRerank.model_rebuild()
107
+ compressor = FlashrankRerank()
108
+ compression_retriever = ContextualCompressionRetriever(
109
+ base_compressor=compressor, base_retriever=main_retriever
110
+ )
111
+
112
+ chat_prompt = ChatPromptTemplate.from_template(
113
+ """
114
+ You are an expert assistant designed to answer questions based on the provided information.
115
+ Use the context below to respond accurately and concisely to the query.
116
+ While giving response, don't explicitly mention the document name or metadata unless otherwise asked.
117
+ If the context does not contain the necessary information, state, 'The provided context does not contain enough information to answer the question'.
118
+
119
+ Context:
120
+ {context}
121
+
122
+ Answer the question based on the above context:
123
+ Question: {question}
124
+
125
+ """
126
+ )
127
+
128
+ self.chain = (
129
+ {"context": compression_retriever, "question": RunnablePassthrough() }
130
+ | chat_prompt
131
+ | self.llm2
132
+ #|self.llm
133
+ | StrOutputParser()
134
+ )
135
+
136
+ def ask(self, question):
137
+ if not self.chain:
138
+ return "Please upload your files first."
139
+ return self.chain.invoke(question)
140
+
141
+ def clear(self):
142
+ self.vector_db = None
143
+ self.chain = None
144
+ self.processed_files.clear()
145
+
146
+
147
+ def display_messages():
148
+
149
+ for i, (msg, is_user) in enumerate(st.session_state["messages"]):
150
+ message(msg, is_user=is_user, key=str(i))
151
+ st.session_state["thinking_spinner"] = st.empty()
152
+
153
+
154
+ def process_input():
155
+ if st.session_state["user_input"].strip():
156
+ user_text = st.session_state["user_input"].strip()
157
+ with st.session_state["thinking_spinner"], st.spinner("Thinking..."):
158
+ agent_text = st.session_state["assistant"].ask(user_text)
159
+
160
+ st.session_state["messages"].append((user_text, True))
161
+ st.session_state["messages"].append((agent_text, False))
162
+ st.session_state["user_input"] = ""
163
+
164
+
165
+ def read_and_save_file():
166
+ st.session_state["assistant"].clear()
167
+ st.session_state["messages"] = []
168
+ st.session_state["user_input"] = ""
169
+
170
+ for file in st.session_state["file_uploader"]:
171
+ with tempfile.NamedTemporaryFile(delete=False) as tf:
172
+ tf.write(file.getbuffer())
173
+ file_path = tf.name
174
+
175
+ with st.session_state["ingestion_spinner"], st.spinner(f"Ingesting {file.name}..."):
176
+ st.session_state["assistant"].ingest(file_path=file_path, file_name=file.name)
177
+ os.remove(file_path)
178
+
179
+
180
+ def ingest_webpage():
181
+ st.session_state["assistant"].clear()
182
+ st.session_state["messages"] = []
183
+ st.session_state["user_input"] = ""
184
+
185
+ webpage_url = st.session_state["webpage_url"]
186
+ with st.session_state["ingestion_spinner"], st.spinner(f"Ingesting data from {webpage_url}..."):
187
+ st.session_state["assistant"].ingest(webpage_url=webpage_url)
188
+
189
+
190
+ def page():
191
+ if "messages" not in st.session_state:
192
+ st.session_state["messages"] = []
193
+ st.session_state["assistant"] = ChatPDF()
194
+
195
+ st.markdown(
196
+ """
197
+ <style>
198
+ .title-container {
199
+ display: flex;
200
+ justify-content: center;
201
+ align-items: center;
202
+ font-size: 40px;
203
+ font-weight: bold;
204
+ color: #1E90FF; # Customize the title color
205
+ }
206
+ </style>
207
+ <div class="title-container">
208
+ Chatbot - AI Assistant🌟<br><br>
209
+
210
+
211
+ </div>
212
+ """,
213
+ unsafe_allow_html=True
214
+ )
215
+
216
+ st.markdown(
217
+ """
218
+ <style>
219
+ .welcome-text {
220
+ font-size: 20px; /* Slightly larger text for emphasis */
221
+ font-weight: bold;
222
+ color: #20C997; /* Vibrant teal for attention */
223
+ margin-bottom: 20px;
224
+ }
225
+ .sub-heading {
226
+ font-size: 20px; /* Smaller size for sub-heading */
227
+ font-weight: bold;
228
+ color: #FFD700; /* Golden yellow for contrast */
229
+ margin-top: 30px;
230
+ margin-bottom: 10px;
231
+ }
232
+ .instructions {
233
+ font-size: 16px; /* Smaller text for instructions */
234
+ color: #FFFFFF; /* White text for black background themes */
235
+ line-height: 1.6;
236
+ }
237
+ </style>
238
+
239
+ <div class="welcome-text">
240
+ Welcome to your AI-Powered Document Assistant!!
241
+ Chat with your documents and ask questions effortlessly.
242
+ </div>
243
+
244
+ <div class="sub-heading">Get Started!!</div>
245
+ <div class="instructions">
246
+ 1. <b>Upload Your Documents</b>: The system accepts multiple PDF files at once, analyzing the content to provide comprehensive insights. <br>
247
+ 2. <b>Ask a Question</b>: After processing the documents, ask any question related to the content of your uploaded documents for a precise answer. <br><br><br>
248
+ </div>
249
+ """,
250
+ unsafe_allow_html=True
251
+ )
252
+
253
+
254
+
255
+
256
+
257
+
258
+ with st.sidebar:
259
+ st.header("Options")
260
+ st.subheader("📄 Upload Documents")
261
+ st.file_uploader(
262
+ "Upload PDF documents",
263
+ type=["pdf"],
264
+ key="file_uploader",
265
+ on_change=read_and_save_file,
266
+ accept_multiple_files=True,
267
+ )
268
+ st.subheader("Source Status")
269
+ if st.session_state["assistant"].processed_files:
270
+ st.write(f"""Uploaded {len(st.session_state["assistant"].processed_files)} document(s)""")
271
+ else:
272
+ st.write("No documents uploaded yet.")
273
+
274
+
275
+ st.session_state["ingestion_spinner"] = st.empty()
276
+ display_messages()
277
+ st.text_input("Ask a question ✍", key="user_input", on_change=process_input, placeholder="Type your question here...")
278
+
279
+
280
+
281
+
282
+ if __name__ == "__main__":
283
+ page()