Files changed (1) hide show
  1. app.py +417 -58
app.py CHANGED
@@ -1,172 +1,531 @@
1
  # =======================================
2
- # 📘 RAG App Pro – Gemini + Smart Embeddings (Multi-User Safe)
 
3
  # =======================================
4
 
5
- import os, re, shutil, textwrap, requests, uuid
 
 
 
 
6
  from bs4 import BeautifulSoup
7
- import google.generativeai as genai
 
8
  from sentence_transformers import SentenceTransformer
9
  import chromadb
10
  import gradio as gr
 
11
  from langchain_community.document_loaders import UnstructuredPDFLoader
12
  import camelot
13
 
 
14
  # ======================
15
- # 🔹 Gemini + Local Setup
16
  # ======================
17
- genai.configure(api_key="AQ.Ab8RN6I4za-ifPTOLhC78wesRWQxGaWeQQZZa44Cv7dBtaxX6A") # 🔑 apni Gemini key
18
- MODEL = "gemini-3.5-flash"
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  embedder = SentenceTransformer("all-MiniLM-L6-v2")
 
 
21
  chroma_client = chromadb.Client()
22
 
 
23
  # ======================
24
  # 🔹 Utils
25
  # ======================
 
26
  def clean_text(text):
27
  return re.sub(r"\s+", " ", text).strip()
28
 
 
29
  def adaptive_chunk_text(text):
30
  length = len(text)
 
31
  if length < 3000:
32
  size = 500
33
  elif length < 10000:
34
  size = 1000
35
  else:
36
  size = 1500
 
37
  chunks = []
 
38
  for i in range(0, len(text), size - 150):
39
  chunks.append(text[i:i + size])
 
40
  return chunks
41
 
 
42
  def extract_pdf_text(pdf_path):
43
  """Smart PDF extractor (tables + text)"""
 
44
  full_text = ""
 
 
 
 
45
  try:
46
- tables = camelot.read_pdf(pdf_path, pages="all")
 
 
 
 
47
  for i, table in enumerate(tables):
48
- full_text += f"\n\n[Table {i+1}]\n" + table.df.to_string(index=False)
 
 
 
 
49
  except Exception:
50
  pass
51
 
 
 
 
52
  try:
53
  loader = UnstructuredPDFLoader(pdf_path)
 
54
  docs = loader.load()
55
- full_text += "\n\n".join([doc.page_content for doc in docs])
 
 
 
 
56
  except Exception as e:
57
- full_text += f"\n\n[Error extracting text: {e}]"
 
 
58
 
59
  return clean_text(full_text)
60
 
 
61
  # ======================
62
  # 🔹 Session Handling
63
  # ======================
 
64
  def create_user_collection():
65
- """Each user/session gets unique collection"""
 
 
66
  session_id = f"user_{str(uuid.uuid4())[:8]}"
67
- collection = chroma_client.create_collection(name=session_id)
 
 
 
 
68
  return session_id, collection
69
 
 
70
  def reset_collection(collection_name):
71
- """Delete previous data for same user"""
 
 
72
  try:
73
- chroma_client.delete_collection(name=collection_name)
 
 
 
74
  except Exception:
75
  pass
76
- return chroma_client.create_collection(name=collection_name)
 
 
 
 
77
 
78
  # ======================
79
  # 🔹 Ingestion Logic
80
  # ======================
 
81
  def ingest_source(source, from_url, collection_name):
 
82
  # Delete previous user data
83
- collection = reset_collection(collection_name)
 
 
84
 
 
 
 
85
  if from_url:
86
- html = requests.get(source, timeout=15).text
87
- soup = BeautifulSoup(html, "html.parser")
88
- text = clean_text(soup.get_text())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  else:
90
- text = extract_pdf_text(source)
 
 
 
 
 
 
 
 
 
 
91
 
92
  if not text.strip():
93
- return "⚠️ No readable text found (maybe image-only PDF)."
 
 
 
 
 
 
 
 
94
 
95
  chunks = adaptive_chunk_text(text)
96
- embeddings = embedder.encode(chunks).tolist()
 
 
 
 
 
 
 
 
 
 
 
97
 
98
  for i, emb in enumerate(embeddings):
99
- collection.add(ids=[f"{collection_name}_{i}"], embeddings=[emb], documents=[chunks[i]])
100
 
101
- return f"✅ [{collection_name}] Ingested {len(chunks)} chunks successfully!"
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
  # ======================
104
  # 🔹 Query Logic
105
  # ======================
 
106
  def rag_query(query, collection_name):
 
107
  try:
108
- collection = chroma_client.get_collection(name=collection_name)
109
- q_emb = embedder.encode([query]).tolist()
110
- results = collection.query(query_embeddings=q_emb, n_results=4)
111
- if not results["documents"]:
112
- return "⚠️ No context found. Try ingesting data first."
113
 
114
- context = "\n\n".join(results["documents"][0])
115
- prompt = f"""
116
- You are a knowledgeable AI assistant.
117
- Use the context below to answer clearly and in multiple lines.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  Context:
 
120
  {context}
121
 
122
- Question: {query}
123
- Answer:
 
124
  """
125
- response = genai.GenerativeModel(MODEL).generate_content(prompt)
126
- ans = response.text.replace(". ", ".\n")
127
- return ans
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  except Exception as e:
 
129
  return f"⚠️ Error: {e}"
130
 
 
131
  # ======================
132
- # 🔹 Gradio UI
133
  # ======================
 
134
  def start_new_session():
 
135
  session_id, _ = create_user_collection()
 
136
  return session_id
137
 
 
138
  session_id = start_new_session()
139
 
 
 
 
 
 
140
  def ingest_website(url):
141
- return ingest_source(url, True, session_id)
 
 
 
 
 
 
142
 
143
  def ingest_pdf(file):
144
- return ingest_source(file.name, False, session_id)
 
 
 
 
 
 
 
 
 
145
 
146
  def query_ask(q):
147
- return rag_query(q, session_id)
148
 
149
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="emerald")) as demo:
150
- gr.Markdown("# 🤖 Smart RAG App Pro (Gemini + Adaptive PDF + Multi-User Mode)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- gr.Markdown(f"🆕 **Private Session ID:** `{session_id}` – Your data is isolated and auto-clears on refresh.")
 
 
 
 
 
 
 
153
 
154
  with gr.Tab("🌐 Ingest Website"):
155
- url_in = gr.Textbox(label="Enter Website URL")
156
- url_btn = gr.Button("Ingest Website")
157
- url_out = gr.Textbox(label="Status")
158
- url_btn.click(fn=ingest_website, inputs=url_in, outputs=url_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  with gr.Tab("📄 Ingest PDF"):
161
- pdf_in = gr.File(label="Upload PDF")
162
- pdf_btn = gr.Button("Ingest PDF")
163
- pdf_out = gr.Textbox(label="Status")
164
- pdf_btn.click(fn=ingest_pdf, inputs=pdf_in, outputs=pdf_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
  with gr.Tab("💬 Ask Questions"):
167
- q_in = gr.Textbox(label="Ask anything from ingested data")
168
- q_btn = gr.Button("Ask Gemini")
169
- q_out = gr.Markdown(label="Answer")
170
- q_btn.click(fn=query_ask, inputs=q_in, outputs=q_out)
171
 
172
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # =======================================
2
+ # 📘 RAG App Pro – Groq + Smart Embeddings
3
+ # Multi-User Safe
4
  # =======================================
5
 
6
+ import os
7
+ import re
8
+ import requests
9
+ import uuid
10
+
11
  from bs4 import BeautifulSoup
12
+ from groq import Groq
13
+
14
  from sentence_transformers import SentenceTransformer
15
  import chromadb
16
  import gradio as gr
17
+
18
  from langchain_community.document_loaders import UnstructuredPDFLoader
19
  import camelot
20
 
21
+
22
  # ======================
23
+ # 🔹 Groq + Local Setup
24
  # ======================
 
 
25
 
26
+ # Set your Groq API key as an environment variable:
27
+ # Windows CMD:
28
+ # set GROQ_API_KEY=your_groq_api_key
29
+ #
30
+ # PowerShell:
31
+ # $env:GROQ_API_KEY="your_groq_api_key"
32
+
33
+ GROQ_API_KEY = os.getenv("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")
34
+
35
+ if not GROQ_API_KEY:
36
+ raise ValueError(
37
+ "GROQ_API_KEY not found. Please set your Groq API key "
38
+ "as an environment variable."
39
+ )
40
+
41
+ client = Groq(api_key=GROQ_API_KEY)
42
+
43
+ # Groq model
44
+ MODEL = "llama-3.3-70b-versatile"
45
+
46
+ # Local embedding model
47
  embedder = SentenceTransformer("all-MiniLM-L6-v2")
48
+
49
+ # ChromaDB
50
  chroma_client = chromadb.Client()
51
 
52
+
53
  # ======================
54
  # 🔹 Utils
55
  # ======================
56
+
57
  def clean_text(text):
58
  return re.sub(r"\s+", " ", text).strip()
59
 
60
+
61
  def adaptive_chunk_text(text):
62
  length = len(text)
63
+
64
  if length < 3000:
65
  size = 500
66
  elif length < 10000:
67
  size = 1000
68
  else:
69
  size = 1500
70
+
71
  chunks = []
72
+
73
  for i in range(0, len(text), size - 150):
74
  chunks.append(text[i:i + size])
75
+
76
  return chunks
77
 
78
+
79
  def extract_pdf_text(pdf_path):
80
  """Smart PDF extractor (tables + text)"""
81
+
82
  full_text = ""
83
+
84
+ # ----------------------
85
+ # Extract tables
86
+ # ----------------------
87
  try:
88
+ tables = camelot.read_pdf(
89
+ pdf_path,
90
+ pages="all"
91
+ )
92
+
93
  for i, table in enumerate(tables):
94
+ full_text += (
95
+ f"\n\n[Table {i + 1}]\n"
96
+ + table.df.to_string(index=False)
97
+ )
98
+
99
  except Exception:
100
  pass
101
 
102
+ # ----------------------
103
+ # Extract normal text
104
+ # ----------------------
105
  try:
106
  loader = UnstructuredPDFLoader(pdf_path)
107
+
108
  docs = loader.load()
109
+
110
+ full_text += "\n\n".join(
111
+ [doc.page_content for doc in docs]
112
+ )
113
+
114
  except Exception as e:
115
+ full_text += (
116
+ f"\n\n[Error extracting text: {e}]"
117
+ )
118
 
119
  return clean_text(full_text)
120
 
121
+
122
  # ======================
123
  # 🔹 Session Handling
124
  # ======================
125
+
126
  def create_user_collection():
127
+
128
+ """Each user/session gets a unique collection"""
129
+
130
  session_id = f"user_{str(uuid.uuid4())[:8]}"
131
+
132
+ collection = chroma_client.create_collection(
133
+ name=session_id
134
+ )
135
+
136
  return session_id, collection
137
 
138
+
139
  def reset_collection(collection_name):
140
+
141
+ """Delete previous user data"""
142
+
143
  try:
144
+ chroma_client.delete_collection(
145
+ name=collection_name
146
+ )
147
+
148
  except Exception:
149
  pass
150
+
151
+ return chroma_client.create_collection(
152
+ name=collection_name
153
+ )
154
+
155
 
156
  # ======================
157
  # 🔹 Ingestion Logic
158
  # ======================
159
+
160
  def ingest_source(source, from_url, collection_name):
161
+
162
  # Delete previous user data
163
+ collection = reset_collection(
164
+ collection_name
165
+ )
166
 
167
+ # ----------------------
168
+ # Website
169
+ # ----------------------
170
  if from_url:
171
+
172
+ try:
173
+ response = requests.get(
174
+ source,
175
+ timeout=15,
176
+ headers={
177
+ "User-Agent": "Mozilla/5.0"
178
+ }
179
+ )
180
+
181
+ response.raise_for_status()
182
+
183
+ soup = BeautifulSoup(
184
+ response.text,
185
+ "html.parser"
186
+ )
187
+
188
+ # Remove unnecessary HTML
189
+ for tag in soup(
190
+ ["script", "style", "noscript"]
191
+ ):
192
+ tag.decompose()
193
+
194
+ text = clean_text(
195
+ soup.get_text()
196
+ )
197
+
198
+ except Exception as e:
199
+
200
+ return f"⚠️ Website error: {e}"
201
+
202
+ # ----------------------
203
+ # PDF
204
+ # ----------------------
205
  else:
206
+
207
+ try:
208
+ text = extract_pdf_text(source)
209
+
210
+ except Exception as e:
211
+
212
+ return f"⚠️ PDF error: {e}"
213
+
214
+ # ----------------------
215
+ # Check text
216
+ # ----------------------
217
 
218
  if not text.strip():
219
+
220
+ return (
221
+ "⚠️ No readable text found "
222
+ "(maybe image-only PDF)."
223
+ )
224
+
225
+ # ----------------------
226
+ # Chunking
227
+ # ----------------------
228
 
229
  chunks = adaptive_chunk_text(text)
230
+
231
+ # ----------------------
232
+ # Embeddings
233
+ # ----------------------
234
+
235
+ embeddings = embedder.encode(
236
+ chunks
237
+ ).tolist()
238
+
239
+ # ----------------------
240
+ # Store in ChromaDB
241
+ # ----------------------
242
 
243
  for i, emb in enumerate(embeddings):
 
244
 
245
+ collection.add(
246
+ ids=[
247
+ f"{collection_name}_{i}"
248
+ ],
249
+ embeddings=[emb],
250
+ documents=[chunks[i]]
251
+ )
252
+
253
+ return (
254
+ f"✅ [{collection_name}] "
255
+ f"Ingested {len(chunks)} chunks successfully!"
256
+ )
257
+
258
 
259
  # ======================
260
  # 🔹 Query Logic
261
  # ======================
262
+
263
  def rag_query(query, collection_name):
264
+
265
  try:
 
 
 
 
 
266
 
267
+ # ----------------------
268
+ # Get collection
269
+ # ----------------------
270
+
271
+ collection = chroma_client.get_collection(
272
+ name=collection_name
273
+ )
274
+
275
+ # ----------------------
276
+ # Embed user query
277
+ # ----------------------
278
+
279
+ q_emb = embedder.encode(
280
+ [query]
281
+ ).tolist()
282
+
283
+ # ----------------------
284
+ # Semantic search
285
+ # ----------------------
286
+
287
+ results = collection.query(
288
+ query_embeddings=q_emb,
289
+ n_results=4
290
+ )
291
 
292
+ if (
293
+ not results["documents"]
294
+ or not results["documents"][0]
295
+ ):
296
+ return (
297
+ "⚠️ No context found. "
298
+ "Try ingesting data first."
299
+ )
300
+
301
+ # ----------------------
302
+ # Build context
303
+ # ----------------------
304
+
305
+ context = "\n\n".join(
306
+ results["documents"][0]
307
+ )
308
+
309
+ # ----------------------
310
+ # Groq prompt
311
+ # ----------------------
312
+
313
+ system_prompt = """
314
+ You are a knowledgeable RAG assistant.
315
+
316
+ Answer the user's question using ONLY
317
+ the provided context.
318
+
319
+ Rules:
320
+ 1. Do not invent information.
321
+ 2. If the answer is not present in the context,
322
+ clearly say that the information was not found.
323
+ 3. Give a clear and useful answer.
324
+ 4. Use multiple lines when appropriate.
325
+ 5. Keep the answer concise but informative.
326
+ """
327
+
328
+ user_prompt = f"""
329
  Context:
330
+
331
  {context}
332
 
333
+ Question:
334
+
335
+ {query}
336
  """
337
+
338
+ # ----------------------
339
+ # Groq API
340
+ # ----------------------
341
+
342
+ response = client.chat.completions.create(
343
+
344
+ model=MODEL,
345
+
346
+ messages=[
347
+ {
348
+ "role": "system",
349
+ "content": system_prompt
350
+ },
351
+ {
352
+ "role": "user",
353
+ "content": user_prompt
354
+ }
355
+ ],
356
+
357
+ temperature=0.2,
358
+
359
+ max_completion_tokens=1000
360
+ )
361
+
362
+ # ----------------------
363
+ # Get answer
364
+ # ----------------------
365
+
366
+ answer = response.choices[
367
+ 0
368
+ ].message.content
369
+
370
+ return answer
371
+
372
  except Exception as e:
373
+
374
  return f"⚠️ Error: {e}"
375
 
376
+
377
  # ======================
378
+ # 🔹 Session
379
  # ======================
380
+
381
  def start_new_session():
382
+
383
  session_id, _ = create_user_collection()
384
+
385
  return session_id
386
 
387
+
388
  session_id = start_new_session()
389
 
390
+
391
+ # ======================
392
+ # 🔹 Gradio Functions
393
+ # ======================
394
+
395
  def ingest_website(url):
396
+
397
+ return ingest_source(
398
+ url,
399
+ True,
400
+ session_id
401
+ )
402
+
403
 
404
  def ingest_pdf(file):
405
+
406
+ if file is None:
407
+ return "⚠️ Please upload a PDF."
408
+
409
+ return ingest_source(
410
+ file.name,
411
+ False,
412
+ session_id
413
+ )
414
+
415
 
416
  def query_ask(q):
 
417
 
418
+ if not q.strip():
419
+ return "⚠️ Please enter a question."
420
+
421
+ return rag_query(
422
+ q,
423
+ session_id
424
+ )
425
+
426
+
427
+ # ======================
428
+ # 🔹 Gradio UI
429
+ # ======================
430
+
431
+ with gr.Blocks(
432
+ theme=gr.themes.Soft(
433
+ primary_hue="emerald"
434
+ )
435
+ ) as demo:
436
+
437
+ gr.Markdown(
438
+ "# 🤖 Smart RAG App Pro "
439
+ "(Groq + Adaptive PDF + Multi-User Mode)"
440
+ )
441
+
442
+ gr.Markdown(
443
+ f"""
444
+ 🆕 **Private Session ID:** `{session_id}`
445
 
446
+ Your uploaded/ingested data is isolated
447
+ inside your session.
448
+ """
449
+ )
450
+
451
+ # ======================
452
+ # Website Tab
453
+ # ======================
454
 
455
  with gr.Tab("🌐 Ingest Website"):
456
+
457
+ url_in = gr.Textbox(
458
+ label="Enter Website URL",
459
+ placeholder="https://example.com"
460
+ )
461
+
462
+ url_btn = gr.Button(
463
+ "Ingest Website"
464
+ )
465
+
466
+ url_out = gr.Textbox(
467
+ label="Status"
468
+ )
469
+
470
+ url_btn.click(
471
+ fn=ingest_website,
472
+ inputs=url_in,
473
+ outputs=url_out
474
+ )
475
+
476
+ # ======================
477
+ # PDF Tab
478
+ # ======================
479
 
480
  with gr.Tab("📄 Ingest PDF"):
481
+
482
+ pdf_in = gr.File(
483
+ label="Upload PDF",
484
+ file_types=[".pdf"]
485
+ )
486
+
487
+ pdf_btn = gr.Button(
488
+ "Ingest PDF"
489
+ )
490
+
491
+ pdf_out = gr.Textbox(
492
+ label="Status"
493
+ )
494
+
495
+ pdf_btn.click(
496
+ fn=ingest_pdf,
497
+ inputs=pdf_in,
498
+ outputs=pdf_out
499
+ )
500
+
501
+ # ======================
502
+ # Ask Tab
503
+ # ======================
504
 
505
  with gr.Tab("💬 Ask Questions"):
 
 
 
 
506
 
507
+ q_in = gr.Textbox(
508
+ label="Ask anything from ingested data",
509
+ placeholder="Ask a question..."
510
+ )
511
+
512
+ q_btn = gr.Button(
513
+ "Ask Groq"
514
+ )
515
+
516
+ q_out = gr.Markdown(
517
+ label="Answer"
518
+ )
519
+
520
+ q_btn.click(
521
+ fn=query_ask,
522
+ inputs=q_in,
523
+ outputs=q_out
524
+ )
525
+
526
+
527
+ # ======================
528
+ # 🔹 Launch
529
+ # ======================
530
+
531
+ demo.launch()