Tesneem commited on
Commit
9502853
·
verified ·
1 Parent(s): 5e02b10

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +565 -440
app.py CHANGED
@@ -1,465 +1,590 @@
1
- #############################################################################################################################
2
- # Filename : app.py
3
- # Description: A Streamlit application to showcase how RAG works.
4
- # Author : Georgios Ioannou
5
- #
6
- # Copyright © 2024 by Georgios Ioannou
7
- #############################################################################################################################
8
- # Import libraries.
9
  import os
 
10
  import streamlit as st
 
 
 
 
11
 
12
- from dotenv import load_dotenv, find_dotenv
13
- from huggingface_hub import InferenceClient
14
  from langchain.prompts import PromptTemplate
15
  from langchain.schema import Document
16
- from langchain.schema.runnable import RunnablePassthrough, RunnableLambda
17
- # from langchain_community.embeddings import HuggingFaceInferenceAPIEmbeddings
18
- from langchain.embeddings import OpenAIEmbeddings
19
- from langchain_community.vectorstores import MongoDBAtlasVectorSearch
20
- from pymongo import MongoClient
21
- from pymongo.collection import Collection
22
- from typing import Dict, Any
23
- from langchain.chat_models import ChatOpenAI
24
-
25
-
26
-
27
- #############################################################################################################################
28
-
29
-
30
- class RAGQuestionAnswering:
31
- def __init__(self):
32
- """
33
- Parameters
34
- ----------
35
- None
36
-
37
- Output
38
- ------
39
- None
40
-
41
- Purpose
42
- -------
43
- Initializes the RAG Question Answering system by setting up configuration
44
- and loading environment variables.
45
-
46
- Assumptions
47
- -----------
48
- - Expects .env file with MONGO_URI and HF_TOKEN
49
- - Requires proper MongoDB setup with vector search index
50
- - Needs connection to Hugging Face API
51
-
52
- Notes
53
- -----
54
- This is the main class that handles all RAG operations
55
- """
56
- self.load_environment()
57
- self.setup_mongodb()
58
- self.setup_embedding_model()
59
- self.setup_vector_search()
60
- self.setup_rag_chain()
61
-
62
- def load_environment(self) -> None:
63
- """
64
- Parameters
65
- ----------
66
- None
67
-
68
- Output
69
- ------
70
- None
71
-
72
- Purpose
73
- -------
74
- Loads environment variables from .env file and sets up configuration constants.
75
-
76
- Assumptions
77
- -----------
78
- Expects a .env file with MONGO_URI and HF_TOKEN defined
79
-
80
- Notes
81
- -----
82
- Will stop the application if required environment variables are missing
83
- """
84
-
85
- load_dotenv(find_dotenv())
86
- self.MONGO_URI = os.getenv("MONGO_URI")
87
- # self.HF_TOKEN = os.getenv("HF_TOKEN")
88
- self.OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
89
-
90
-
91
- if not self.MONGO_URI or not self.OPENAI_API_KEY:
92
- st.error("Please ensure MONGO_URI and OPENAI_API_KEY are set in your .env file")
93
- st.stop()
94
-
95
- # MongoDB configuration.
96
- self.DB_NAME = "txts"
97
- self.COLLECTION_NAME = "txts_collection"
98
- self.VECTOR_SEARCH_INDEX = "vector_index"
99
-
100
- def setup_mongodb(self) -> None:
101
- """
102
- Parameters
103
- ----------
104
- None
105
-
106
- Output
107
- ------
108
- None
109
-
110
- Purpose
111
- -------
112
- Initializes the MongoDB connection and sets up the collection.
113
-
114
- Assumptions
115
- -----------
116
- - Valid MongoDB URI is available
117
- - Database and collection exist in MongoDB Atlas
118
-
119
- Notes
120
- -----
121
- Uses st.cache_resource for efficient connection management
122
- """
123
-
124
- @st.cache_resource
125
- def init_mongodb() -> Collection:
126
- cluster = MongoClient(self.MONGO_URI)
127
- return cluster[self.DB_NAME][self.COLLECTION_NAME]
128
-
129
- self.mongodb_collection = init_mongodb()
130
-
131
- def setup_embedding_model(self) -> None:
132
- """
133
- Parameters
134
- ----------
135
- None
136
-
137
- Output
138
- ------
139
- None
140
-
141
- Purpose
142
- -------
143
- Initializes the embedding model for vector search.
144
-
145
- Assumptions
146
- -----------
147
- - Valid Hugging Face API token
148
- - Internet connection to access the model
149
-
150
- Notes
151
- -----
152
- Uses the all-mpnet-base-v2 model from sentence-transformers
153
- """
154
-
155
- # @st.cache_resource
156
- # def init_embedding_model() -> HuggingFaceInferenceAPIEmbeddings:
157
- # return HuggingFaceInferenceAPIEmbeddings(
158
- # api_key=self.HF_TOKEN,
159
- # model_name="sentence-transformers/all-mpnet-base-v2",
160
- # )
161
-
162
- @st.cache_resource
163
- def init_embedding_model() -> OpenAIEmbeddings:
164
- return OpenAIEmbeddings(model="text-embedding-3-small", openai_api_key=self.OPENAI_API_KEY)
165
-
166
- self.embedding_model = init_embedding_model()
167
-
168
- def setup_vector_search(self) -> None:
169
- """
170
- Parameters
171
- ----------
172
- None
173
-
174
- Output
175
- ------
176
- None
177
-
178
- Purpose
179
- -------
180
- Sets up the vector search functionality using MongoDB Atlas.
181
-
182
- Assumptions
183
- -----------
184
- - MongoDB Atlas vector search index is properly configured
185
- - Valid embedding model is initialized
186
-
187
- Notes
188
- -----
189
- Creates a retriever with similarity search and score threshold
190
- """
191
-
192
- @st.cache_resource
193
- def init_vector_search() -> MongoDBAtlasVectorSearch:
194
- return MongoDBAtlasVectorSearch.from_connection_string(
195
- connection_string=self.MONGO_URI,
196
- namespace=f"{self.DB_NAME}.{self.COLLECTION_NAME}",
197
- embedding=self.embedding_model,
198
- index_name=self.VECTOR_SEARCH_INDEX,
199
- )
200
-
201
- self.vector_search = init_vector_search()
202
- self.retriever = self.vector_search.as_retriever(
203
- search_type="similarity", search_kwargs={"k": 10, "score_threshold": 0.85}
204
- )
205
-
206
- def format_docs(self, docs: list[Document]) -> str:
207
- """
208
- Parameters
209
- ----------
210
- **docs:** list[Document] - List of documents to be formatted
211
-
212
- Output
213
- ------
214
- str: Formatted string containing concatenated document content
215
-
216
- Purpose
217
- -------
218
- Formats the retrieved documents into a single string for processing
219
-
220
- Assumptions
221
- -----------
222
- Documents have page_content attribute
223
-
224
- Notes
225
- -----
226
- Joins documents with double newlines for better readability
227
- """
228
-
229
- return "\n\n".join(doc.page_content for doc in docs)
230
-
231
- # def generate_response(self, input_dict: Dict[str, Any]) -> str:
232
- # """
233
- # Parameters
234
- # ----------
235
- # **input_dict:** Dict[str, Any] - Dictionary containing context and question
236
-
237
- # Output
238
- # ------
239
- # str: Generated response from the model
240
-
241
- # Purpose
242
- # -------
243
- # Generates a response using the Hugging Face model based on context and question
244
-
245
- # Assumptions
246
- # -----------
247
- # - Valid Hugging Face API token
248
- # - Input dictionary contains 'context' and 'question' keys
249
-
250
- # Notes
251
- # -----
252
- # Uses Qwen2.5-1.5B-Instruct model with controlled temperature
253
- # """
254
- # hf_client = InferenceClient(api_key=self.HF_TOKEN)
255
- # formatted_prompt = self.prompt.format(**input_dict)
256
-
257
- # response = hf_client.chat.completions.create(
258
- # model="Qwen/Qwen2.5-1.5B-Instruct",
259
- # messages=[
260
- # {"role": "system", "content": formatted_prompt},
261
- # {"role": "user", "content": input_dict["question"]},
262
- # ],
263
- # max_tokens=1000,
264
- # temperature=0.2,
265
- # )
266
-
267
- # return response.choices[0].message.content
268
- from langchain.chat_models import ChatOpenAI
269
- from langchain.schema.messages import SystemMessage, HumanMessage
270
-
271
- def generate_response(self, input_dict: Dict[str, Any]) -> str:
272
- llm = ChatOpenAI(
273
- model="gpt-4", # or "gpt-3.5-turbo"
274
- temperature=0.2,
275
- openai_api_key=self.OPENAI_API_KEY,
276
- )
277
-
278
- messages = [
279
- SystemMessage(content=self.prompt.format(**input_dict)),
280
- HumanMessage(content=input_dict["question"]),
281
- ]
282
-
283
- return llm(messages).content
284
 
 
 
 
 
 
 
 
285
 
286
- def setup_rag_chain(self) -> None:
287
- """
288
- Parameters
289
- ----------
290
- None
291
 
292
- Output
293
- ------
294
- None
295
 
296
- Purpose
297
- -------
298
- Sets up the RAG chain for processing questions and generating answers
 
299
 
300
- Assumptions
301
- -----------
302
- Retriever and response generator are properly initialized
303
 
304
- Notes
305
- -----
306
- Creates a chain that combines retrieval and response generation
307
- """
308
 
309
- self.prompt = PromptTemplate.from_template(
310
- """Use the following pieces of context to answer the question at the end.
311
 
312
- START OF CONTEXT:
313
- {context}
314
- END OF CONTEXT:
315
-
316
- START OF QUESTION:
317
- {question}
318
- END OF QUESTION:
319
-
320
- If you do not know the answer, just say that you do not know.
321
- NEVER assume things.
322
- """
323
- )
324
-
325
- self.rag_chain = {
326
- "context": self.retriever | RunnableLambda(self.format_docs),
327
- "question": RunnablePassthrough(),
328
- } | RunnableLambda(self.generate_response)
329
-
330
- def process_question(self, question: str) -> str:
331
- """
332
- Parameters
333
- ----------
334
- **question:** str - The user's question to be answered
335
-
336
- Output
337
- ------
338
- str: The generated answer to the question
339
-
340
- Purpose
341
- -------
342
- Processes a user question through the RAG chain and returns an answer
343
-
344
- Assumptions
345
- -----------
346
- - Question is a non-empty string
347
- - RAG chain is properly initialized
348
-
349
- Notes
350
- -----
351
- Main interface for question-answering functionality
352
- """
353
-
354
- return self.rag_chain.invoke(question)
355
-
356
-
357
- #############################################################################################################################
358
- def setup_streamlit_ui() -> None:
359
- """
360
- Parameters
361
- ----------
362
- None
363
-
364
- Output
365
- ------
366
- None
367
-
368
- Purpose
369
- -------
370
- Sets up the Streamlit user interface with proper styling and layout
371
-
372
- Assumptions
373
- -----------
374
- - CSS file exists at ./static/styles/style.css
375
- - Image file exists at ./static/images/ctp.png
376
-
377
- Notes
378
- -----
379
- Handles all UI-related setup and styling
380
- """
381
-
382
- st.set_page_config(page_title="RAG Question Answering", page_icon="🤖")
383
-
384
- # Load CSS.
385
- with open("./static/styles/style.css") as f:
386
- st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
387
-
388
- # Title and subtitles.
389
- st.markdown(
390
- '<h1 align="center" style="font-family: monospace; font-size: 2.1rem; margin-top: -4rem">RAG Question Answering</h1>',
391
- unsafe_allow_html=True,
392
- )
393
- st.markdown(
394
- '<h3 align="center" style="font-family: monospace; font-size: 1.5rem; margin-top: -2rem">Using Zoom Closed Captioning From The Lectures</h3>',
395
- unsafe_allow_html=True,
396
  )
397
- st.markdown(
398
- '<h2 align="center" style="font-family: monospace; font-size: 1.5rem; margin-top: 0rem">CUNY Tech Prep Tutorial 5</h2>',
399
- unsafe_allow_html=True,
 
 
400
  )
401
 
402
- # Display logo.
403
- left_co, cent_co, last_co = st.columns(3)
404
- with cent_co:
405
- st.image("./static/images/ctp.png")
406
-
407
-
408
- #############################################################################################################################
 
 
 
 
 
 
 
 
 
 
 
 
409
 
 
 
 
 
 
 
410
 
 
411
  def main():
412
- """
413
- Parameters
414
- ----------
415
- None
416
-
417
- Output
418
- ------
419
- None
420
-
421
- Purpose
422
- -------
423
- Main function that runs the Streamlit application
424
-
425
- Assumptions
426
- -----------
427
- All required environment variables and files are present
428
-
429
- Notes
430
- -----
431
- Entry point for the application
432
- """
433
-
434
- # Setup UI.
435
- setup_streamlit_ui()
436
-
437
- # Initialize RAG system.
438
- rag_system = RAGQuestionAnswering()
439
-
440
- # Create input elements.
441
- query = st.text_input("Question:", key="question_input")
442
-
443
- # Handle submission.
444
- if st.button("Submit", type="primary"):
445
- if query:
446
- with st.spinner("Generating response..."):
447
- response = rag_system.process_question(query)
448
- st.text_area("Answer:", value=response, height=200, disabled=True)
449
- else:
450
  st.warning("Please enter a question.")
 
451
 
452
- # Add GitHub link.
453
- st.markdown(
454
- """
455
- <p align="center" style="font-family: monospace; color: #FAF9F6; font-size: 1rem;">
456
- <b>Check out our <a href="https://github.com/GeorgiosIoannouCoder/" style="color: #FAF9F6;">GitHub repository</a></b>
457
- </p>
458
- """,
459
- unsafe_allow_html=True,
460
- )
 
 
461
 
462
 
463
- #############################################################################################################################
464
  if __name__ == "__main__":
465
  main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # #############################################################################################################################
2
+ # # Filename : app.py
3
+ # # Description: A Streamlit application to showcase how RAG works.
4
+ # # Author : Georgios Ioannou
5
+ # #
6
+ # # Copyright © 2024 by Georgios Ioannou
7
+ # #############################################################################################################################
8
+ # app.py
9
  import os
10
+ import json
11
  import streamlit as st
12
+ from typing import List, Dict, Any
13
+ from urllib.parse import quote_plus
14
+ from pymongo import MongoClient
15
+ from PyPDF2 import PdfReader
16
 
17
+ from langchain_community.embeddings import HuggingFaceInferenceAPIEmbeddings
18
+ from langchain_community.vectorstores import MongoDBAtlasVectorSearch
19
  from langchain.prompts import PromptTemplate
20
  from langchain.schema import Document
21
+ from langchain.schema.runnable import RunnableLambda, RunnablePassthrough
22
+ from huggingface_hub import InferenceClient
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ # =================== Secure Env via Hugging Face Secrets ===================
25
+ user = quote_plus(os.getenv("MONGO_USERNAME"))
26
+ password = quote_plus(os.getenv("MONGO_PASSWORD"))
27
+ cluster = os.getenv("MONGO_CLUSTER")
28
+ db_name = os.getenv("MONGO_DB_NAME", "files")
29
+ collection_name = os.getenv("MONGO_COLLECTION", "files_collection")
30
+ index_name = os.getenv("MONGO_VECTOR_INDEX", "vector_index")
31
 
32
+ HF_TOKEN = os.getenv("HF_TOKEN")
 
 
 
 
33
 
34
+ MONGO_URI = f"mongodb+srv://{user}:{password}@{cluster}/{db_name}?retryWrites=true&w=majority"
 
 
35
 
36
+ # =================== Prompt ===================
37
+ grantbuddy_prompt = PromptTemplate.from_template(
38
+ """You are Grant Buddy, a specialized assistant helping nonprofits apply for grants.
39
+ Always align answers with the nonprofit’s mission to combat systemic poverty through education, technology, and social innovation.
40
 
41
+ Use the following context to answer the question. Be concise and mission-aligned.
 
 
42
 
43
+ CONTEXT:
44
+ {context}
 
 
45
 
46
+ QUESTION:
47
+ {question}
48
 
49
+ Respond truthfully. If the answer is not available, say "This information is not available in the current context."
50
+ """
51
+ )
52
+
53
+ # =================== Vector Search Setup ===================
54
+ @st.cache_resource
55
+ def init_vector_search() -> MongoDBAtlasVectorSearch:
56
+ embedding_model = HuggingFaceInferenceAPIEmbeddings(
57
+ api_key=HF_TOKEN,
58
+ model_name="sentence-transformers/all-mpnet-base-v2"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  )
60
+ return MongoDBAtlasVectorSearch.from_connection_string(
61
+ connection_string=MONGO_URI,
62
+ namespace=f"{db_name}.{collection_name}",
63
+ embedding=embedding_model,
64
+ index_name=index_name,
65
  )
66
 
67
+ # =================== Format Retrieved Chunks ===================
68
+ def format_docs(docs: List[Document]) -> str:
69
+ return "\n\n".join(doc.page_content or doc.metadata.get("content", "") for doc in docs)
70
+
71
+ # =================== Generate Response from Hugging Face Model ===================
72
+ def generate_response(input_dict: Dict[str, Any]) -> str:
73
+ client = InferenceClient(api_key=HF_TOKEN)
74
+ prompt = grantbuddy_prompt.format(**input_dict)
75
+
76
+ response = client.chat.completions.create(
77
+ model="Qwen/Qwen2.5-1.5B-Instruct",
78
+ messages=[
79
+ {"role": "system", "content": prompt},
80
+ {"role": "user", "content": input_dict["question"]},
81
+ ],
82
+ max_tokens=1000,
83
+ temperature=0.2,
84
+ )
85
+ return response.choices[0].message.content
86
 
87
+ # =================== RAG Chain ===================
88
+ def get_rag_chain(retriever):
89
+ return {
90
+ "context": retriever | RunnableLambda(format_docs),
91
+ "question": RunnablePassthrough()
92
+ } | RunnableLambda(generate_response)
93
 
94
+ # =================== Streamlit UI ===================
95
  def main():
96
+ st.set_page_config(page_title="Grant Buddy RAG", page_icon="🤖")
97
+ st.title("🤖 Grant Buddy: Grant-Writing Assistant")
98
+
99
+ uploaded_file = st.file_uploader("Upload PDF or TXT for extra context (optional)", type=["pdf", "txt"])
100
+ uploaded_text = ""
101
+ if uploaded_file:
102
+ if uploaded_file.name.endswith(".pdf"):
103
+ reader = PdfReader(uploaded_file)
104
+ uploaded_text = "\n".join([page.extract_text() for page in reader.pages])
105
+ elif uploaded_file.name.endswith(".txt"):
106
+ uploaded_text = uploaded_file.read().decode("utf-8")
107
+
108
+ retriever = init_vector_search().as_retriever(search_kwargs={"k": 10, "score_threshold": 0.75})
109
+ rag_chain = get_rag_chain(retriever)
110
+
111
+ query = st.text_input("Ask a grant-related question")
112
+ if st.button("Submit"):
113
+ if not query:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  st.warning("Please enter a question.")
115
+ return
116
 
117
+ full_query = f"{query}\n\nAdditional context:\n{uploaded_text}" if uploaded_text else query
118
+ with st.spinner("Thinking..."):
119
+ response = rag_chain.invoke(full_query)
120
+ st.text_area("Grant Buddy says:", value=response, height=250, disabled=True)
121
+
122
+ with st.expander("🔍 Retrieved Chunks"):
123
+ context_docs = retriever.get_relevant_documents(full_query)
124
+ for doc in context_docs:
125
+ st.markdown(f"**Chunk ID:** {doc.metadata.get('chunk_id', 'unknown')}")
126
+ st.markdown(doc.page_content[:700] + "...")
127
+ st.markdown("---")
128
 
129
 
 
130
  if __name__ == "__main__":
131
  main()
132
+
133
+ # # Import libraries.
134
+ # import os
135
+ # import streamlit as st
136
+
137
+ # from dotenv import load_dotenv, find_dotenv
138
+ # from huggingface_hub import InferenceClient
139
+ # from langchain.prompts import PromptTemplate
140
+ # from langchain.schema import Document
141
+ # from langchain.schema.runnable import RunnablePassthrough, RunnableLambda
142
+ # # from langchain_community.embeddings import HuggingFaceInferenceAPIEmbeddings
143
+ # from langchain.embeddings import OpenAIEmbeddings
144
+ # from langchain_community.vectorstores import MongoDBAtlasVectorSearch
145
+ # from pymongo import MongoClient
146
+ # from pymongo.collection import Collection
147
+ # from typing import Dict, Any
148
+ # from langchain.chat_models import ChatOpenAI
149
+
150
+
151
+
152
+ # #############################################################################################################################
153
+
154
+
155
+ # class RAGQuestionAnswering:
156
+ # def __init__(self):
157
+ # """
158
+ # Parameters
159
+ # ----------
160
+ # None
161
+
162
+ # Output
163
+ # ------
164
+ # None
165
+
166
+ # Purpose
167
+ # -------
168
+ # Initializes the RAG Question Answering system by setting up configuration
169
+ # and loading environment variables.
170
+
171
+ # Assumptions
172
+ # -----------
173
+ # - Expects .env file with MONGO_URI and HF_TOKEN
174
+ # - Requires proper MongoDB setup with vector search index
175
+ # - Needs connection to Hugging Face API
176
+
177
+ # Notes
178
+ # -----
179
+ # This is the main class that handles all RAG operations
180
+ # """
181
+ # self.load_environment()
182
+ # self.setup_mongodb()
183
+ # self.setup_embedding_model()
184
+ # self.setup_vector_search()
185
+ # self.setup_rag_chain()
186
+
187
+ # def load_environment(self) -> None:
188
+ # """
189
+ # Parameters
190
+ # ----------
191
+ # None
192
+
193
+ # Output
194
+ # ------
195
+ # None
196
+
197
+ # Purpose
198
+ # -------
199
+ # Loads environment variables from .env file and sets up configuration constants.
200
+
201
+ # Assumptions
202
+ # -----------
203
+ # Expects a .env file with MONGO_URI and HF_TOKEN defined
204
+
205
+ # Notes
206
+ # -----
207
+ # Will stop the application if required environment variables are missing
208
+ # """
209
+
210
+ # load_dotenv(find_dotenv())
211
+ # self.MONGO_URI = os.getenv("MONGO_URI")
212
+ # # self.HF_TOKEN = os.getenv("HF_TOKEN")
213
+ # self.OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
214
+
215
+
216
+ # if not self.MONGO_URI or not self.OPENAI_API_KEY:
217
+ # st.error("Please ensure MONGO_URI and OPENAI_API_KEY are set in your .env file")
218
+ # st.stop()
219
+
220
+ # # MongoDB configuration.
221
+ # self.DB_NAME = "txts"
222
+ # self.COLLECTION_NAME = "txts_collection"
223
+ # self.VECTOR_SEARCH_INDEX = "vector_index"
224
+
225
+ # def setup_mongodb(self) -> None:
226
+ # """
227
+ # Parameters
228
+ # ----------
229
+ # None
230
+
231
+ # Output
232
+ # ------
233
+ # None
234
+
235
+ # Purpose
236
+ # -------
237
+ # Initializes the MongoDB connection and sets up the collection.
238
+
239
+ # Assumptions
240
+ # -----------
241
+ # - Valid MongoDB URI is available
242
+ # - Database and collection exist in MongoDB Atlas
243
+
244
+ # Notes
245
+ # -----
246
+ # Uses st.cache_resource for efficient connection management
247
+ # """
248
+
249
+ # @st.cache_resource
250
+ # def init_mongodb() -> Collection:
251
+ # cluster = MongoClient(self.MONGO_URI)
252
+ # return cluster[self.DB_NAME][self.COLLECTION_NAME]
253
+
254
+ # self.mongodb_collection = init_mongodb()
255
+
256
+ # def setup_embedding_model(self) -> None:
257
+ # """
258
+ # Parameters
259
+ # ----------
260
+ # None
261
+
262
+ # Output
263
+ # ------
264
+ # None
265
+
266
+ # Purpose
267
+ # -------
268
+ # Initializes the embedding model for vector search.
269
+
270
+ # Assumptions
271
+ # -----------
272
+ # - Valid Hugging Face API token
273
+ # - Internet connection to access the model
274
+
275
+ # Notes
276
+ # -----
277
+ # Uses the all-mpnet-base-v2 model from sentence-transformers
278
+ # """
279
+
280
+ # # @st.cache_resource
281
+ # # def init_embedding_model() -> HuggingFaceInferenceAPIEmbeddings:
282
+ # # return HuggingFaceInferenceAPIEmbeddings(
283
+ # # api_key=self.HF_TOKEN,
284
+ # # model_name="sentence-transformers/all-mpnet-base-v2",
285
+ # # )
286
+
287
+ # @st.cache_resource
288
+ # def init_embedding_model() -> OpenAIEmbeddings:
289
+ # return OpenAIEmbeddings(model="text-embedding-3-small", openai_api_key=self.OPENAI_API_KEY)
290
+
291
+ # self.embedding_model = init_embedding_model()
292
+
293
+ # def setup_vector_search(self) -> None:
294
+ # """
295
+ # Parameters
296
+ # ----------
297
+ # None
298
+
299
+ # Output
300
+ # ------
301
+ # None
302
+
303
+ # Purpose
304
+ # -------
305
+ # Sets up the vector search functionality using MongoDB Atlas.
306
+
307
+ # Assumptions
308
+ # -----------
309
+ # - MongoDB Atlas vector search index is properly configured
310
+ # - Valid embedding model is initialized
311
+
312
+ # Notes
313
+ # -----
314
+ # Creates a retriever with similarity search and score threshold
315
+ # """
316
+
317
+ # @st.cache_resource
318
+ # def init_vector_search() -> MongoDBAtlasVectorSearch:
319
+ # return MongoDBAtlasVectorSearch.from_connection_string(
320
+ # connection_string=self.MONGO_URI,
321
+ # namespace=f"{self.DB_NAME}.{self.COLLECTION_NAME}",
322
+ # embedding=self.embedding_model,
323
+ # index_name=self.VECTOR_SEARCH_INDEX,
324
+ # )
325
+
326
+ # self.vector_search = init_vector_search()
327
+ # self.retriever = self.vector_search.as_retriever(
328
+ # search_type="similarity", search_kwargs={"k": 10, "score_threshold": 0.85}
329
+ # )
330
+
331
+ # def format_docs(self, docs: list[Document]) -> str:
332
+ # """
333
+ # Parameters
334
+ # ----------
335
+ # **docs:** list[Document] - List of documents to be formatted
336
+
337
+ # Output
338
+ # ------
339
+ # str: Formatted string containing concatenated document content
340
+
341
+ # Purpose
342
+ # -------
343
+ # Formats the retrieved documents into a single string for processing
344
+
345
+ # Assumptions
346
+ # -----------
347
+ # Documents have page_content attribute
348
+
349
+ # Notes
350
+ # -----
351
+ # Joins documents with double newlines for better readability
352
+ # """
353
+
354
+ # return "\n\n".join(doc.page_content for doc in docs)
355
+
356
+ # # def generate_response(self, input_dict: Dict[str, Any]) -> str:
357
+ # # """
358
+ # # Parameters
359
+ # # ----------
360
+ # # **input_dict:** Dict[str, Any] - Dictionary containing context and question
361
+
362
+ # # Output
363
+ # # ------
364
+ # # str: Generated response from the model
365
+
366
+ # # Purpose
367
+ # # -------
368
+ # # Generates a response using the Hugging Face model based on context and question
369
+
370
+ # # Assumptions
371
+ # # -----------
372
+ # # - Valid Hugging Face API token
373
+ # # - Input dictionary contains 'context' and 'question' keys
374
+
375
+ # # Notes
376
+ # # -----
377
+ # # Uses Qwen2.5-1.5B-Instruct model with controlled temperature
378
+ # # """
379
+ # # hf_client = InferenceClient(api_key=self.HF_TOKEN)
380
+ # # formatted_prompt = self.prompt.format(**input_dict)
381
+
382
+ # # response = hf_client.chat.completions.create(
383
+ # # model="Qwen/Qwen2.5-1.5B-Instruct",
384
+ # # messages=[
385
+ # # {"role": "system", "content": formatted_prompt},
386
+ # # {"role": "user", "content": input_dict["question"]},
387
+ # # ],
388
+ # # max_tokens=1000,
389
+ # # temperature=0.2,
390
+ # # )
391
+
392
+ # # return response.choices[0].message.content
393
+ # from langchain.chat_models import ChatOpenAI
394
+ # from langchain.schema.messages import SystemMessage, HumanMessage
395
+
396
+ # def generate_response(self, input_dict: Dict[str, Any]) -> str:
397
+ # llm = ChatOpenAI(
398
+ # model="gpt-4", # or "gpt-3.5-turbo"
399
+ # temperature=0.2,
400
+ # openai_api_key=self.OPENAI_API_KEY,
401
+ # )
402
+
403
+ # messages = [
404
+ # SystemMessage(content=self.prompt.format(**input_dict)),
405
+ # HumanMessage(content=input_dict["question"]),
406
+ # ]
407
+
408
+ # return llm(messages).content
409
+
410
+
411
+ # def setup_rag_chain(self) -> None:
412
+ # """
413
+ # Parameters
414
+ # ----------
415
+ # None
416
+
417
+ # Output
418
+ # ------
419
+ # None
420
+
421
+ # Purpose
422
+ # -------
423
+ # Sets up the RAG chain for processing questions and generating answers
424
+
425
+ # Assumptions
426
+ # -----------
427
+ # Retriever and response generator are properly initialized
428
+
429
+ # Notes
430
+ # -----
431
+ # Creates a chain that combines retrieval and response generation
432
+ # """
433
+
434
+ # self.prompt = PromptTemplate.from_template(
435
+ # """Use the following pieces of context to answer the question at the end.
436
+
437
+ # START OF CONTEXT:
438
+ # {context}
439
+ # END OF CONTEXT:
440
+
441
+ # START OF QUESTION:
442
+ # {question}
443
+ # END OF QUESTION:
444
+
445
+ # If you do not know the answer, just say that you do not know.
446
+ # NEVER assume things.
447
+ # """
448
+ # )
449
+
450
+ # self.rag_chain = {
451
+ # "context": self.retriever | RunnableLambda(self.format_docs),
452
+ # "question": RunnablePassthrough(),
453
+ # } | RunnableLambda(self.generate_response)
454
+
455
+ # def process_question(self, question: str) -> str:
456
+ # """
457
+ # Parameters
458
+ # ----------
459
+ # **question:** str - The user's question to be answered
460
+
461
+ # Output
462
+ # ------
463
+ # str: The generated answer to the question
464
+
465
+ # Purpose
466
+ # -------
467
+ # Processes a user question through the RAG chain and returns an answer
468
+
469
+ # Assumptions
470
+ # -----------
471
+ # - Question is a non-empty string
472
+ # - RAG chain is properly initialized
473
+
474
+ # Notes
475
+ # -----
476
+ # Main interface for question-answering functionality
477
+ # """
478
+
479
+ # return self.rag_chain.invoke(question)
480
+
481
+
482
+ # #############################################################################################################################
483
+ # def setup_streamlit_ui() -> None:
484
+ # """
485
+ # Parameters
486
+ # ----------
487
+ # None
488
+
489
+ # Output
490
+ # ------
491
+ # None
492
+
493
+ # Purpose
494
+ # -------
495
+ # Sets up the Streamlit user interface with proper styling and layout
496
+
497
+ # Assumptions
498
+ # -----------
499
+ # - CSS file exists at ./static/styles/style.css
500
+ # - Image file exists at ./static/images/ctp.png
501
+
502
+ # Notes
503
+ # -----
504
+ # Handles all UI-related setup and styling
505
+ # """
506
+
507
+ # st.set_page_config(page_title="RAG Question Answering", page_icon="🤖")
508
+
509
+ # # Load CSS.
510
+ # with open("./static/styles/style.css") as f:
511
+ # st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
512
+
513
+ # # Title and subtitles.
514
+ # st.markdown(
515
+ # '<h1 align="center" style="font-family: monospace; font-size: 2.1rem; margin-top: -4rem">RAG Question Answering</h1>',
516
+ # unsafe_allow_html=True,
517
+ # )
518
+ # st.markdown(
519
+ # '<h3 align="center" style="font-family: monospace; font-size: 1.5rem; margin-top: -2rem">Using Zoom Closed Captioning From The Lectures</h3>',
520
+ # unsafe_allow_html=True,
521
+ # )
522
+ # st.markdown(
523
+ # '<h2 align="center" style="font-family: monospace; font-size: 1.5rem; margin-top: 0rem">CUNY Tech Prep Tutorial 5</h2>',
524
+ # unsafe_allow_html=True,
525
+ # )
526
+
527
+ # # Display logo.
528
+ # left_co, cent_co, last_co = st.columns(3)
529
+ # with cent_co:
530
+ # st.image("./static/images/ctp.png")
531
+
532
+
533
+ # #############################################################################################################################
534
+
535
+
536
+ # def main():
537
+ # """
538
+ # Parameters
539
+ # ----------
540
+ # None
541
+
542
+ # Output
543
+ # ------
544
+ # None
545
+
546
+ # Purpose
547
+ # -------
548
+ # Main function that runs the Streamlit application
549
+
550
+ # Assumptions
551
+ # -----------
552
+ # All required environment variables and files are present
553
+
554
+ # Notes
555
+ # -----
556
+ # Entry point for the application
557
+ # """
558
+
559
+ # # Setup UI.
560
+ # setup_streamlit_ui()
561
+
562
+ # # Initialize RAG system.
563
+ # rag_system = RAGQuestionAnswering()
564
+
565
+ # # Create input elements.
566
+ # query = st.text_input("Question:", key="question_input")
567
+
568
+ # # Handle submission.
569
+ # if st.button("Submit", type="primary"):
570
+ # if query:
571
+ # with st.spinner("Generating response..."):
572
+ # response = rag_system.process_question(query)
573
+ # st.text_area("Answer:", value=response, height=200, disabled=True)
574
+ # else:
575
+ # st.warning("Please enter a question.")
576
+
577
+ # # Add GitHub link.
578
+ # st.markdown(
579
+ # """
580
+ # <p align="center" style="font-family: monospace; color: #FAF9F6; font-size: 1rem;">
581
+ # <b>Check out our <a href="https://github.com/GeorgiosIoannouCoder/" style="color: #FAF9F6;">GitHub repository</a></b>
582
+ # </p>
583
+ # """,
584
+ # unsafe_allow_html=True,
585
+ # )
586
+
587
+
588
+ # #############################################################################################################################
589
+ # if __name__ == "__main__":
590
+ # main()