martina3435 commited on
Commit
0cd4d75
·
verified ·
1 Parent(s): 3122354

Update rag.py

Browse files
Files changed (1) hide show
  1. rag.py +177 -83
rag.py CHANGED
@@ -6,16 +6,17 @@ import zipfile
6
  import gdown
7
  import time
8
  import asyncio
 
9
  from typing import Dict, List
10
 
11
  from google import genai
12
 
13
- from langchain_community.vectorstores import Chroma
14
- from langchain_community.embeddings import HuggingFaceEmbeddings
15
  from langchain.memory import ConversationBufferMemory
16
- from langchain.retrievers import BM25Retriever, EnsembleRetriever
17
  from langchain.schema import Document
18
 
 
 
 
19
 
20
  # =====================================================
21
  # Logging
@@ -41,22 +42,34 @@ MODEL_NAME = "models/gemini-2.5-flash-lite"
41
  # Download Vector DB
42
  # =====================================================
43
  if not os.path.exists("chroma_db"):
 
44
  print("Downloading vector DB...")
45
 
46
- url = "https://drive.google.com/uc?id=1yelCLmiRD2Qds-_8EsnKzUNY7uPT5xMg"
 
 
 
47
 
48
- gdown.download(url, "chroma_db.zip", quiet=False)
 
 
 
 
49
 
50
  print("Extracting DB...")
51
 
52
- with zipfile.ZipFile("chroma_db.zip", "r") as zip_ref:
 
 
 
 
53
  zip_ref.extractall(".")
54
 
55
  print("Vector DB Ready ✅")
56
 
57
 
58
  # =====================================================
59
- # Embedding Model
60
  # =====================================================
61
  embedding = HuggingFaceEmbeddings(
62
  model_name="sentence-transformers/all-MiniLM-L6-v2"
@@ -75,61 +88,58 @@ print("Vector DB Loaded ✅")
75
 
76
 
77
  # =====================================================
78
- # Build BM25 Retriever (Hybrid Search)
79
- # =====================================================
80
- print("Building BM25 Retriever...")
81
-
82
- all_docs = vector_db.similarity_search(
83
- "nutrition",
84
- k=200
85
- )
86
-
87
- bm25_retriever = BM25Retriever.from_documents(all_docs)
88
- bm25_retriever.k = 6
89
-
90
- vector_retriever = vector_db.as_retriever(
91
- search_kwargs={"k": 6}
92
- )
93
-
94
- # Hybrid Search
95
- hybrid_retriever = EnsembleRetriever(
96
- retrievers=[bm25_retriever, vector_retriever],
97
- weights=[0.4, 0.6]
98
- )
99
-
100
- print("Hybrid Retriever Ready ✅")
101
-
102
-
103
- # =====================================================
104
- # Session-based Memory
105
  # =====================================================
106
  class SessionConversationMemory:
107
 
108
  def __init__(self):
109
- self.user_memories: Dict[str, ConversationBufferMemory] = {}
110
 
111
- def get_memory(self, session_id: str):
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  if session_id not in self.user_memories:
114
 
115
- self.user_memories[session_id] = ConversationBufferMemory(
 
 
116
  return_messages=True,
117
  memory_key="chat_history"
118
  )
119
 
120
- return self.user_memories[session_id]
 
 
121
 
122
- def clear_history(self, session_id: str):
 
 
 
 
 
 
123
 
124
  if session_id in self.user_memories:
125
- self.user_memories[session_id].clear()
 
 
 
126
 
127
 
128
  conversation = SessionConversationMemory()
129
 
130
 
131
  # =====================================================
132
- # Gemini Async Call with Retry
133
  # =====================================================
134
  async def call_gemini_async(
135
  prompt: str,
@@ -141,13 +151,16 @@ async def call_gemini_async(
141
  try:
142
 
143
  response = await asyncio.to_thread(
 
144
  client.models.generate_content,
 
145
  model=MODEL_NAME,
146
  contents=prompt
 
147
  )
148
 
149
- # Streaming handling
150
  if response and response.text:
 
151
  return response.text
152
 
153
  return "Empty response from model."
@@ -156,23 +169,36 @@ async def call_gemini_async(
156
 
157
  msg = str(e)
158
 
159
- if "429" in msg or "RESOURCE_EXHAUSTED" in msg:
 
 
 
 
 
 
 
160
 
161
  wait_time = 5 * (attempt + 1)
162
 
163
  logger.warning(
164
- f"Quota hit. Retrying in {wait_time}s..."
 
165
  )
166
 
167
  await asyncio.sleep(wait_time)
168
 
169
  continue
170
 
171
- logger.error(f"Gemini Error: {e}")
 
 
172
 
173
  break
174
 
175
- return "Model is temporarily unavailable. Please try again later."
 
 
 
176
 
177
 
178
  # =====================================================
@@ -185,18 +211,29 @@ def rerank_documents(
185
 
186
  scored_docs = []
187
 
188
- query_words = set(query.lower().split())
 
 
189
 
190
  for doc in docs:
191
 
192
- content = doc.page_content.lower()
 
 
193
 
194
  score = sum(
195
- 1 for word in query_words
 
 
 
 
196
  if word in content
 
197
  )
198
 
199
- scored_docs.append((score, doc))
 
 
200
 
201
  scored_docs.sort(
202
  key=lambda x: x[0],
@@ -204,23 +241,40 @@ def rerank_documents(
204
  )
205
 
206
  reranked_docs = [
207
- doc for score, doc in scored_docs[:4]
 
 
 
 
208
  ]
209
 
210
  return reranked_docs
211
 
212
 
213
  # =====================================================
214
- # Get Relevant Context
215
  # =====================================================
216
- def get_relevant_context_from_db(query):
 
 
217
 
218
  try:
219
 
220
- docs = hybrid_retriever.get_relevant_documents(query)
 
 
 
 
 
 
221
 
 
222
  # Reranking
223
- docs = rerank_documents(query, docs)
 
 
 
 
224
 
225
  context_parts = []
226
 
@@ -228,7 +282,9 @@ def get_relevant_context_from_db(query):
228
 
229
  for doc in docs:
230
 
231
- content = doc.page_content[:1200]
 
 
232
 
233
  source = doc.metadata.get(
234
  "source",
@@ -236,18 +292,25 @@ def get_relevant_context_from_db(query):
236
  )
237
 
238
  context_parts.append(
239
- f"[SOURCE: {source}]\n{content}"
 
 
 
240
  )
241
 
242
  sources.add(source)
243
 
244
- context = "\n\n".join(context_parts)
 
 
245
 
246
  return context, list(sources)
247
 
248
  except Exception as e:
249
 
250
- logger.error(f"Retriever Error: {e}")
 
 
251
 
252
  return "", []
253
 
@@ -263,7 +326,10 @@ def build_prompt(
263
 
264
  history = memory.load_memory_variables(
265
  {}
266
- ).get("chat_history", [])
 
 
 
267
 
268
  history_text = "\n".join([
269
 
@@ -316,31 +382,41 @@ async def generate_chat_answer(
316
  try:
317
 
318
  logger.info(
319
- f"Session: {session_id} | Query: {query}"
 
320
  )
321
 
322
- memory = conversation.get_memory(session_id)
 
 
 
 
 
323
 
324
- # =========================================
325
  # Retrieve Context
326
- # =========================================
327
- context, sources = get_relevant_context_from_db(
328
- query
 
 
329
  )
330
 
331
- # =========================================
332
  # Build Prompt
333
- # =========================================
334
  prompt = build_prompt(
335
  query=query,
336
  context=context,
337
  memory=memory
338
  )
339
 
340
- # =========================================
341
- # Call Gemini
342
- # =========================================
343
- answer = await call_gemini_async(prompt)
 
 
344
 
345
  answer = answer.strip()
346
 
@@ -351,28 +427,39 @@ async def generate_chat_answer(
351
  )
352
 
353
  if not answer:
354
- answer = "Sorry, no response generated."
355
 
356
- # =========================================
357
- # Save Memory
358
- # =========================================
 
 
 
 
359
  memory.save_context(
 
360
  {"input": query},
 
361
  {"output": answer}
 
362
  )
363
 
364
- # =========================================
365
  # Add Sources
366
- # =========================================
367
  if sources:
368
 
369
- unique_sources = list(set(sources))
 
 
370
 
371
  answer += "\n\nSources:\n"
372
 
373
  answer += "\n".join([
 
374
  f"- {source}"
 
375
  for source in unique_sources
 
376
  ])
377
 
378
  return answer
@@ -388,11 +475,15 @@ async def generate_chat_answer(
388
 
389
 
390
  # =====================================================
391
- # Clear Session Chat
392
  # =====================================================
393
- def clear_chat_history(session_id: str):
 
 
394
 
395
- conversation.clear_history(session_id)
 
 
396
 
397
 
398
  # =====================================================
@@ -405,4 +496,7 @@ def signal_handler(sig, frame):
405
  sys.exit(0)
406
 
407
 
408
- signal.signal(signal.SIGINT, signal_handler)
 
 
 
 
6
  import gdown
7
  import time
8
  import asyncio
9
+
10
  from typing import Dict, List
11
 
12
  from google import genai
13
 
 
 
14
  from langchain.memory import ConversationBufferMemory
 
15
  from langchain.schema import Document
16
 
17
+ from langchain_community.vectorstores import Chroma
18
+ from langchain_community.embeddings import HuggingFaceEmbeddings
19
+
20
 
21
  # =====================================================
22
  # Logging
 
42
  # Download Vector DB
43
  # =====================================================
44
  if not os.path.exists("chroma_db"):
45
+
46
  print("Downloading vector DB...")
47
 
48
+ url = (
49
+ "https://drive.google.com/uc?id="
50
+ "1yelCLmiRD2Qds-_8EsnKzUNY7uPT5xMg"
51
+ )
52
 
53
+ gdown.download(
54
+ url,
55
+ "chroma_db.zip",
56
+ quiet=False
57
+ )
58
 
59
  print("Extracting DB...")
60
 
61
+ with zipfile.ZipFile(
62
+ "chroma_db.zip",
63
+ "r"
64
+ ) as zip_ref:
65
+
66
  zip_ref.extractall(".")
67
 
68
  print("Vector DB Ready ✅")
69
 
70
 
71
  # =====================================================
72
+ # Load Embedding Model
73
  # =====================================================
74
  embedding = HuggingFaceEmbeddings(
75
  model_name="sentence-transformers/all-MiniLM-L6-v2"
 
88
 
89
 
90
  # =====================================================
91
+ # Session Memory
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  # =====================================================
93
  class SessionConversationMemory:
94
 
95
  def __init__(self):
 
96
 
97
+ self.user_memories: Dict[
98
+ str,
99
+ ConversationBufferMemory
100
+ ] = {}
101
+
102
+ # ===============================================
103
+ # Get User Memory
104
+ # ===============================================
105
+ def get_memory(
106
+ self,
107
+ session_id: str
108
+ ):
109
 
110
  if session_id not in self.user_memories:
111
 
112
+ self.user_memories[
113
+ session_id
114
+ ] = ConversationBufferMemory(
115
  return_messages=True,
116
  memory_key="chat_history"
117
  )
118
 
119
+ return self.user_memories[
120
+ session_id
121
+ ]
122
 
123
+ # ===============================================
124
+ # Clear User History
125
+ # ===============================================
126
+ def clear_history(
127
+ self,
128
+ session_id: str
129
+ ):
130
 
131
  if session_id in self.user_memories:
132
+
133
+ self.user_memories[
134
+ session_id
135
+ ].clear()
136
 
137
 
138
  conversation = SessionConversationMemory()
139
 
140
 
141
  # =====================================================
142
+ # Async Gemini Call with Retry
143
  # =====================================================
144
  async def call_gemini_async(
145
  prompt: str,
 
151
  try:
152
 
153
  response = await asyncio.to_thread(
154
+
155
  client.models.generate_content,
156
+
157
  model=MODEL_NAME,
158
  contents=prompt
159
+
160
  )
161
 
 
162
  if response and response.text:
163
+
164
  return response.text
165
 
166
  return "Empty response from model."
 
169
 
170
  msg = str(e)
171
 
172
+ # =========================================
173
+ # Handle Quota Errors
174
+ # =========================================
175
+ if (
176
+ "429" in msg
177
+ or
178
+ "RESOURCE_EXHAUSTED" in msg
179
+ ):
180
 
181
  wait_time = 5 * (attempt + 1)
182
 
183
  logger.warning(
184
+ f"Quota hit. "
185
+ f"Retrying in {wait_time}s..."
186
  )
187
 
188
  await asyncio.sleep(wait_time)
189
 
190
  continue
191
 
192
+ logger.error(
193
+ f"Gemini Error: {e}"
194
+ )
195
 
196
  break
197
 
198
+ return (
199
+ "Model is temporarily unavailable. "
200
+ "Please try again later."
201
+ )
202
 
203
 
204
  # =====================================================
 
211
 
212
  scored_docs = []
213
 
214
+ query_words = set(
215
+ query.lower().split()
216
+ )
217
 
218
  for doc in docs:
219
 
220
+ content = (
221
+ doc.page_content.lower()
222
+ )
223
 
224
  score = sum(
225
+
226
+ 1
227
+
228
+ for word in query_words
229
+
230
  if word in content
231
+
232
  )
233
 
234
+ scored_docs.append(
235
+ (score, doc)
236
+ )
237
 
238
  scored_docs.sort(
239
  key=lambda x: x[0],
 
241
  )
242
 
243
  reranked_docs = [
244
+
245
+ doc
246
+
247
+ for score, doc in scored_docs[:4]
248
+
249
  ]
250
 
251
  return reranked_docs
252
 
253
 
254
  # =====================================================
255
+ # Retrieve Context
256
  # =====================================================
257
+ def get_relevant_context_from_db(
258
+ query: str
259
+ ):
260
 
261
  try:
262
 
263
+ # =============================================
264
+ # Vector Search
265
+ # =============================================
266
+ docs = vector_db.similarity_search(
267
+ query,
268
+ k=8
269
+ )
270
 
271
+ # =============================================
272
  # Reranking
273
+ # =============================================
274
+ docs = rerank_documents(
275
+ query,
276
+ docs
277
+ )
278
 
279
  context_parts = []
280
 
 
282
 
283
  for doc in docs:
284
 
285
+ content = (
286
+ doc.page_content[:1200]
287
+ )
288
 
289
  source = doc.metadata.get(
290
  "source",
 
292
  )
293
 
294
  context_parts.append(
295
+
296
+ f"[SOURCE: {source}]\n"
297
+ f"{content}"
298
+
299
  )
300
 
301
  sources.add(source)
302
 
303
+ context = "\n\n".join(
304
+ context_parts
305
+ )
306
 
307
  return context, list(sources)
308
 
309
  except Exception as e:
310
 
311
+ logger.error(
312
+ f"Retriever Error: {e}"
313
+ )
314
 
315
  return "", []
316
 
 
326
 
327
  history = memory.load_memory_variables(
328
  {}
329
+ ).get(
330
+ "chat_history",
331
+ []
332
+ )
333
 
334
  history_text = "\n".join([
335
 
 
382
  try:
383
 
384
  logger.info(
385
+ f"Session: {session_id} "
386
+ f"| Query: {query}"
387
  )
388
 
389
+ # =============================================
390
+ # Get User Memory
391
+ # =============================================
392
+ memory = conversation.get_memory(
393
+ session_id
394
+ )
395
 
396
+ # =============================================
397
  # Retrieve Context
398
+ # =============================================
399
+ context, sources = (
400
+ get_relevant_context_from_db(
401
+ query
402
+ )
403
  )
404
 
405
+ # =============================================
406
  # Build Prompt
407
+ # =============================================
408
  prompt = build_prompt(
409
  query=query,
410
  context=context,
411
  memory=memory
412
  )
413
 
414
+ # =============================================
415
+ # Generate Answer
416
+ # =============================================
417
+ answer = await call_gemini_async(
418
+ prompt
419
+ )
420
 
421
  answer = answer.strip()
422
 
 
427
  )
428
 
429
  if not answer:
 
430
 
431
+ answer = (
432
+ "Sorry, no response generated."
433
+ )
434
+
435
+ # =============================================
436
+ # Save Conversation Memory
437
+ # =============================================
438
  memory.save_context(
439
+
440
  {"input": query},
441
+
442
  {"output": answer}
443
+
444
  )
445
 
446
+ # =============================================
447
  # Add Sources
448
+ # =============================================
449
  if sources:
450
 
451
+ unique_sources = list(
452
+ set(sources)
453
+ )
454
 
455
  answer += "\n\nSources:\n"
456
 
457
  answer += "\n".join([
458
+
459
  f"- {source}"
460
+
461
  for source in unique_sources
462
+
463
  ])
464
 
465
  return answer
 
475
 
476
 
477
  # =====================================================
478
+ # Clear Chat History
479
  # =====================================================
480
+ def clear_chat_history(
481
+ session_id: str
482
+ ):
483
 
484
+ conversation.clear_history(
485
+ session_id
486
+ )
487
 
488
 
489
  # =====================================================
 
496
  sys.exit(0)
497
 
498
 
499
+ signal.signal(
500
+ signal.SIGINT,
501
+ signal_handler
502
+ )