Rezuwan commited on
Commit
e2f9f03
·
verified ·
1 Parent(s): c6c10cc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +235 -107
app.py CHANGED
@@ -1,20 +1,23 @@
1
  import os
 
 
2
  import warnings
 
 
3
  import gradio as gr
4
  import numpy as np
5
- import re
 
6
  from dotenv import load_dotenv
 
7
  from sklearn.metrics.pairwise import cosine_similarity
8
 
9
- from langchain.schema import SystemMessage, HumanMessage
10
- from langchain.chains import RetrievalQA
11
- from langchain_community.vectorstores import FAISS
12
- from langchain_openai import ChatOpenAI
13
- from langchain_community.embeddings import OpenAIEmbeddings
14
-
15
  # Patch Gradio bug (schema parsing issue)
16
- import gradio_client.utils
17
- gradio_client.utils.json_schema_to_python_type = lambda schema, defs=None: "string"
 
 
 
18
 
19
  # Load environment variables
20
  load_dotenv()
@@ -25,13 +28,147 @@ if not OPENAI_API_KEY:
25
  # Suppress warnings
26
  warnings.filterwarnings("ignore")
27
 
28
- # Initialize embedding model
29
- embeddings = OpenAIEmbeddings(openai_api_key=OPENAI_API_KEY)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- # Load FAISS vector store
32
- vectorstore = FAISS.load_local(
33
- "faiss_index_unmad_magz", embeddings, allow_dangerous_deserialization=True
34
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  def clean_bangla_content(text):
37
  """
@@ -93,93 +230,32 @@ def clean_bangla_content(text):
93
 
94
  return '\n'.join(cleaned_lines)
95
 
96
- def maximal_marginal_relevance_search(query, vectorstore, k=10, lambda_param=0.5, top_k=3):
97
- """
98
- Implement Maximal Marginal Relevance (MMR) for diverse document retrieval.
99
  """
100
- # Get initial candidate documents (more than needed)
101
- candidate_docs = vectorstore.similarity_search_with_score(query, k=k)
102
-
103
- if not candidate_docs:
104
- return []
105
-
106
- # Extract documents and their embeddings
107
- docs = [doc for doc, score in candidate_docs]
108
-
109
- # Get query embedding
110
- query_embedding = np.array(embeddings.embed_query(query)).reshape(1, -1)
111
-
112
- # Get document embeddings
113
- doc_embeddings = []
114
- for doc in docs:
115
- doc_embedding = np.array(embeddings.embed_documents([doc.page_content])[0])
116
- doc_embeddings.append(doc_embedding)
117
-
118
- doc_embeddings = np.array(doc_embeddings)
119
-
120
- # MMR Selection Algorithm
121
- selected_docs = []
122
- selected_indices = []
123
- remaining_indices = list(range(len(docs)))
124
-
125
- for _ in range(min(top_k, len(docs))):
126
- mmr_scores = []
127
-
128
- for i in remaining_indices:
129
- # Calculate relevance score (similarity to query)
130
- relevance = cosine_similarity(query_embedding, doc_embeddings[i].reshape(1, -1))[0][0]
131
-
132
- # Calculate diversity score (max similarity to already selected docs)
133
- if selected_indices:
134
- diversity_scores = []
135
- for j in selected_indices:
136
- similarity = cosine_similarity(
137
- doc_embeddings[i].reshape(1, -1),
138
- doc_embeddings[j].reshape(1, -1)
139
- )[0][0]
140
- diversity_scores.append(similarity)
141
- diversity = max(diversity_scores)
142
- else:
143
- diversity = 0
144
-
145
- # Calculate MMR score
146
- mmr_score = lambda_param * relevance - (1 - lambda_param) * diversity
147
- mmr_scores.append((mmr_score, i))
148
-
149
- # Select document with highest MMR score
150
- if mmr_scores:
151
- best_score, best_idx = max(mmr_scores, key=lambda x: x[0])
152
- selected_docs.append(docs[best_idx])
153
- selected_indices.append(best_idx)
154
- remaining_indices.remove(best_idx)
155
 
156
- return selected_docs
157
-
158
- # Initialize gpt-4o Chat model
159
- llm = ChatOpenAI(
160
- model_name="gpt-4o",
161
- temperature=0.7,
162
- max_tokens=700,
163
- openai_api_key=OPENAI_API_KEY
164
- )
165
-
166
- # Satirical QA function with MMR and content cleaning
167
- def custom_unmad_satirical_bot(message, history, top_k=3):
168
- # Use MMR search with default parameters
169
  docs = maximal_marginal_relevance_search(
170
  query=message,
171
- vectorstore=vectorstore,
172
  k=15, # Consider more candidates for better diversity
173
- lambda_param=0.6, # Slightly favor relevance over diversity
174
  top_k=top_k
175
  )
176
 
177
- # Extract context from MMR-selected documents with cleaning
178
  if docs:
179
  # Clean each document's content before joining
180
  cleaned_contexts = []
181
  for doc in docs:
182
- cleaned_content = clean_bangla_content(doc.page_content)
183
  if cleaned_content.strip(): # Only add if there's meaningful Bengali content
184
  cleaned_contexts.append(cleaned_content)
185
 
@@ -187,11 +263,23 @@ def custom_unmad_satirical_bot(message, history, top_k=3):
187
  top_contexts = "\n\n---\n\n".join(cleaned_contexts)
188
  else:
189
  top_contexts = "প্রাসঙ্গিক বাংলা তথ্য পাওয়া যায়নি।"
 
 
 
 
 
 
 
 
 
 
 
190
  else:
191
  top_contexts = "কোন প্রাসঙ্গিক তথ্য পাওয়া যায়নি।"
 
192
 
193
- messages = [
194
- SystemMessage(content="""
195
  তুমি 'উন্মাদ' ম্যাগাজিনের একজন পুরানো ব্যঙ্গাত্মক লেখক। তোমার কাজ হলো ব্যবহারকারীর প্রশ্ন শুনে স্যাটায়ার, কটাক্ষ, রসিকতা, ঠাট্টা, আর একটু জ্ঞান মিশিয়ে উত্তর দেওয়া — যাতে লোক হাসে, চিন্তা করে, আবার নতুন কিছু শিখে।
196
  তুমি কখনোই একদম সোজাসাপ্টা উত্তর দেবে না — বরং একটু অভিনয় করে, অবাক হয়ে, ঠাট্টা করে, খোঁচা মেরে দেবে।
197
 
@@ -202,40 +290,80 @@ def custom_unmad_satirical_bot(message, history, top_k=3):
202
  ৪। প্রসঙ্গের মধ্যে যেসব ইংরেজি টেক্সট, স্ক্যান ওয়াটারমার্ক, ওয়েবসাইট নাম, বা প্রযুক্তিগত শব্দ আছে সেগুলো একেবারেই উল্লেখ করবে না।
203
  ৫। শুধুমাত্র বাংলা ভাষায় লেখা বিষয়বস্তু ব্যবহার করবে।
204
  ৬। যদি প্রসঙ্গে কোন বাংলা কন্টেন্ট না থাকে, তাহলে নিজের সাধারণ জ্ঞান দিয়ে উত্তর দেবে।
205
- """),
206
- HumanMessage(content=f"""
207
- প্রসঙ্গ (নির্বাচিত বাংলা তথ্য):
 
 
 
208
  {top_contexts}
209
 
210
  প্রশ্ন: {message}
211
 
212
  নির্দেশনা: উপরের প্রসঙ্গ থেকে শুধুমাত্র বাংলা ভাষার বিষয়বস্তু ব্যবহার করে উন্মাদ ম্যাগাজিনের স্টাইলে উত্তর দাও। কোন ইংরেজি শব্দ, ইমোজি, বা স্ক্যান ওয়াটারমার্ক উল্লেখ করবে না। সম্পূর্ণ বাংলায় ব্যঙ্গাত্মক ও মজার উত্তর লেখো।
213
- """)
214
- ]
215
 
216
- response = llm.invoke(messages).content
217
- history.append((message, response))
218
- return "", history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
- # Gradio UI
221
  with gr.Blocks(css=".gradio-container {padding-top: 80px;}") as demo:
222
  gr.Markdown("# USB: Unmad Satirical Bot", elem_id="title", elem_classes="title-text")
 
223
 
224
  with gr.Row():
225
- gr.Image("images/c1.png", width=450, show_label=False, container=False)
 
 
 
226
 
227
  chatbot = gr.Chatbot()
228
 
229
  with gr.Row():
230
- msg = gr.Textbox(placeholder="কি চলে আপনার মনে বলেন শুনি?", scale=8, show_label=False)
 
 
 
 
231
  send = gr.Button("Send", variant="primary", scale=1)
232
 
233
- clear = gr.Button("Clear")
234
  state = gr.State([])
235
 
236
- # Connect interaction
237
- msg.submit(custom_unmad_satirical_bot, [msg, state], [msg, chatbot])
238
- send.click(custom_unmad_satirical_bot, [msg, state], [msg, chatbot])
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  clear.click(lambda: ([], ""), None, [chatbot, msg])
240
 
241
  if __name__ == "__main__":
 
1
  import os
2
+ import re
3
+ import json
4
  import warnings
5
+ from typing import List, Dict, Any, Optional
6
+ import lancedb
7
  import gradio as gr
8
  import numpy as np
9
+ import pandas as pd
10
+ from datetime import datetime
11
  from dotenv import load_dotenv
12
+ from openai import OpenAI
13
  from sklearn.metrics.pairwise import cosine_similarity
14
 
 
 
 
 
 
 
15
  # Patch Gradio bug (schema parsing issue)
16
+ try:
17
+ import gradio_client.utils
18
+ gradio_client.utils.json_schema_to_python_type = lambda schema, defs=None: "string"
19
+ except ImportError:
20
+ pass
21
 
22
  # Load environment variables
23
  load_dotenv()
 
28
  # Suppress warnings
29
  warnings.filterwarnings("ignore")
30
 
31
+ class LanceDBRAG:
32
+ def __init__(self,
33
+ db_path: str = "lance_unmad_db",
34
+ table_name: str = "unmad_documents"):
35
+ """Initialize LanceDB RAG System"""
36
+ self.db_path = db_path
37
+ self.table_name = table_name
38
+
39
+ # Initialize OpenAI client
40
+ self.client = OpenAI(api_key=OPENAI_API_KEY)
41
+
42
+ # Connect to LanceDB
43
+ try:
44
+ self.db = lancedb.connect(self.db_path)
45
+ self.table = self.db.open_table(self.table_name)
46
+ print(f"Connected to LanceDB: {self.db_path}/{self.table_name}")
47
+ except Exception as e:
48
+ raise ConnectionError(f"Failed to connect to LanceDB: {e}")
49
+
50
+ def get_embedding(self, text: str) -> List[float]:
51
+ """Get OpenAI embedding for query text"""
52
+ try:
53
+ response = self.client.embeddings.create(
54
+ model="text-embedding-3-small",
55
+ input=text
56
+ )
57
+ return response.data[0].embedding
58
+ except Exception as e:
59
+ print(f"❌ Error getting embedding: {e}")
60
+ return None
61
 
62
+ def search_similar_content(self, query: str, limit: int = 10) -> pd.DataFrame:
63
+ """Search for similar content in the database"""
64
+ print(f"🔍 Searching: '{query}'")
65
+
66
+ # Get query embedding
67
+ query_embedding = self.get_embedding(query)
68
+ if not query_embedding:
69
+ return pd.DataFrame()
70
+
71
+ # Perform vector search
72
+ try:
73
+ search_query = self.table.search(query_embedding).limit(limit)
74
+ results = search_query.to_pandas()
75
+
76
+ if not results.empty:
77
+ print(f"Found {len(results)} relevant results")
78
+ else:
79
+ print("No results found")
80
+
81
+ return results
82
+
83
+ except Exception as e:
84
+ print(f"Search error: {e}")
85
+ return pd.DataFrame()
86
+
87
+ # Initialize global RAG instance
88
+ rag_system = LanceDBRAG()
89
+
90
+ def maximal_marginal_relevance_search(query, rag_instance, k=10, lambda_param=0.6, top_k=3):
91
+ """
92
+ Implement Maximal Marginal Relevance (MMR) for diverse document retrieval using LanceDB.
93
+
94
+ Args:
95
+ query: Search query string
96
+ rag_instance: LanceDB RAG instance
97
+ k: Number of candidate documents to consider
98
+ lambda_param: Trade-off between relevance and diversity (0-1)
99
+ top_k: Number of final documents to return
100
+
101
+ Returns:
102
+ List of selected documents with MMR ranking
103
+ """
104
+ # Get initial candidate documents using LanceDB search
105
+ search_results = rag_instance.search_similar_content(query, limit=k)
106
+
107
+ if search_results.empty:
108
+ return []
109
+
110
+ # Convert to document-like objects for compatibility
111
+ docs = []
112
+ for _, row in search_results.iterrows():
113
+ doc_obj = {
114
+ 'page_content': row['text'],
115
+ 'metadata': {
116
+ 'source': row['magazine_name'],
117
+ 'page': row['page_number'],
118
+ 'chunk': row.get('chunk_id', 0)
119
+ },
120
+ 'score': row['_distance']
121
+ }
122
+ docs.append(doc_obj)
123
+
124
+ # Apply MMR selection if we have enough documents
125
+ if len(docs) <= top_k:
126
+ return docs[:top_k]
127
+
128
+ # MMR Selection Algorithm
129
+ selected_docs = []
130
+ remaining_indices = list(range(len(docs)))
131
+
132
+ for _ in range(min(top_k, len(docs))):
133
+ if not remaining_indices:
134
+ break
135
+
136
+ mmr_scores = []
137
+
138
+ for i in remaining_indices:
139
+ # Calculate relevance score (inverse of distance)
140
+ relevance = 1 / (1 + docs[i]['score'])
141
+
142
+ # Calculate diversity score (max similarity to already selected docs)
143
+ if selected_docs:
144
+ max_similarity = 0
145
+ for selected_doc in selected_docs:
146
+ # Simple text-based similarity for diversity
147
+ text1 = docs[i]['page_content']
148
+ text2 = selected_doc['page_content']
149
+
150
+ # Calculate simple Jaccard similarity
151
+ words1 = set(text1.split())
152
+ words2 = set(text2.split())
153
+ if words1 and words2:
154
+ similarity = len(words1.intersection(words2)) / len(words1.union(words2))
155
+ max_similarity = max(max_similarity, similarity)
156
+
157
+ diversity = max_similarity
158
+ else:
159
+ diversity = 0
160
+
161
+ # Calculate MMR score
162
+ mmr_score = lambda_param * relevance - (1 - lambda_param) * diversity
163
+ mmr_scores.append((mmr_score, i))
164
+
165
+ # Select document with highest MMR score
166
+ if mmr_scores:
167
+ best_score, best_idx = max(mmr_scores, key=lambda x: x[0])
168
+ selected_docs.append(docs[best_idx])
169
+ remaining_indices.remove(best_idx)
170
+
171
+ return selected_docs
172
 
173
  def clean_bangla_content(text):
174
  """
 
230
 
231
  return '\n'.join(cleaned_lines)
232
 
233
+ # Enhanced Satirical QA function with MMR and content cleaning
234
+ def custom_unmad_satirical_bot(message, history, top_k=3, lambda_param=0.6):
 
235
  """
236
+ Enhanced satirical bot using MMR for diverse and relevant content retrieval.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
+ Args:
239
+ message: User query
240
+ history: Chat history
241
+ top_k: Number of documents to retrieve
242
+ lambda_param: MMR trade-off (0.6 = slightly favor relevance over diversity)
243
+ """
244
+ # Use MMR search with LanceDB
 
 
 
 
 
 
245
  docs = maximal_marginal_relevance_search(
246
  query=message,
247
+ rag_instance=rag_system,
248
  k=15, # Consider more candidates for better diversity
249
+ lambda_param=lambda_param,
250
  top_k=top_k
251
  )
252
 
253
+ # Extract context from MMR-selected documents
254
  if docs:
255
  # Clean each document's content before joining
256
  cleaned_contexts = []
257
  for doc in docs:
258
+ cleaned_content = clean_bangla_content(doc['page_content'])
259
  if cleaned_content.strip(): # Only add if there's meaningful Bengali content
260
  cleaned_contexts.append(cleaned_content)
261
 
 
263
  top_contexts = "\n\n---\n\n".join(cleaned_contexts)
264
  else:
265
  top_contexts = "প্রাসঙ্গিক বাংলা তথ্য পাওয়া যায়নি।"
266
+
267
+ # Add metadata about source diversity (optional)
268
+ source_info = []
269
+ for i, doc in enumerate(docs, 1):
270
+ source = doc['metadata'].get('source', 'অজানা উৎস')
271
+ page = doc['metadata'].get('page', 'অজানা পৃষ্ঠা')
272
+ # Clean source info too
273
+ if not re.search(r'[a-zA-Z]', str(source)): # Only if source doesn't contain English
274
+ source_info.append(f"[{i}] {source} - {page}")
275
+
276
+ source_context = "উৎস: " + " | ".join(source_info[:3]) if source_info else ""
277
  else:
278
  top_contexts = "কোন প্রাসঙ্গিক তথ্য পাওয়া যায়নি।"
279
+ source_context = ""
280
 
281
+ # Prepare system prompt
282
+ system_prompt = """
283
  তুমি 'উন্মাদ' ম্যাগাজিনের একজন পুরানো ব্যঙ্গাত্মক লেখক। তোমার কাজ হলো ব্যবহারকারীর প্রশ্ন শুনে স্যাটায়ার, কটাক্ষ, রসিকতা, ঠাট্টা, আর একটু জ্ঞান মিশিয়ে উত্তর দেওয়া — যাতে লোক হাসে, চিন্তা করে, আবার নতুন কিছু শিখে।
284
  তুমি কখনোই একদম সোজাসাপ্টা উত্তর দেবে না — বরং একটু অভিনয় করে, অবাক হয়ে, ঠাট্টা করে, খোঁচা মেরে দেবে।
285
 
 
290
  ৪। প্রসঙ্গের মধ্যে যেসব ইংরেজি টেক্সট, স্ক্যান ওয়াটারমার্ক, ওয়েবসাইট নাম, বা প্রযুক্তিগত শব্দ আছে সেগুলো একেবারেই উল্লেখ করবে না।
291
  ৫। শুধুমাত্র বাংলা ভাষায় লেখা বিষয়বস্তু ব্যবহার করবে।
292
  ৬। যদি প্রসঙ্গে কোন বাংলা কন্টেন্ট না থাকে, তাহলে নিজের সাধারণ জ্ঞান দিয়ে উত্তর দেবে।
293
+ ৭। বিভিন্ন উৎস থেকে তথ্য মিলিয়ে একটি সমন্বিত উত্তর দেবে।
294
+ ৮। কোন ধরনের ওয়েবসাইট বা পিডিএফ রেফারেন্স দেবে না।
295
+ """
296
+
297
+ user_prompt = f"""
298
+ প্রসঙ্গ (বিভিন্ন উৎস থেকে সংগৃহীত):
299
  {top_contexts}
300
 
301
  প্রশ্ন: {message}
302
 
303
  নির্দেশনা: উপরের প্রসঙ্গ থেকে শুধুমাত্র বাংলা ভাষার বিষয়বস্তু ব্যবহার করে উন্মাদ ম্যাগাজিনের স্টাইলে উত্তর দাও। কোন ইংরেজি শব্দ, ইমোজি, বা স্ক্যান ওয়াটারমার্ক উল্লেখ করবে না। সম্পূর্ণ বাংলায় ব্যঙ্গাত্মক ও মজার উত্তর লেখো।
304
+ """
 
305
 
306
+ # Generate response using OpenAI
307
+ try:
308
+ response = rag_system.client.chat.completions.create(
309
+ model="gpt-4o",
310
+ messages=[
311
+ {"role": "system", "content": system_prompt},
312
+ {"role": "user", "content": user_prompt}
313
+ ],
314
+ temperature=0.7,
315
+ max_tokens=700
316
+ )
317
+
318
+ ai_response = response.choices[0].message.content
319
+ history.append((message, ai_response))
320
+ return "", history
321
+
322
+ except Exception as e:
323
+ error_response = f"উত্তর তৈরিতে সমস্যা হয়েছে। আবার চেষ্টা করুন।"
324
+ history.append((message, error_response))
325
+ return "", history
326
 
327
+ # Enhanced Gradio UI with MMR (simplified)
328
  with gr.Blocks(css=".gradio-container {padding-top: 80px;}") as demo:
329
  gr.Markdown("# USB: Unmad Satirical Bot", elem_id="title", elem_classes="title-text")
330
+ gr.Markdown("### Enhanced with LanceDB and Maximal Marginal Relevance for diverse content retrieval")
331
 
332
  with gr.Row():
333
+ try:
334
+ gr.Image("images/c1.png", width=450, show_label=False, container=False)
335
+ except:
336
+ gr.Markdown("*[UNMAD Logo would appear here]*")
337
 
338
  chatbot = gr.Chatbot()
339
 
340
  with gr.Row():
341
+ msg = gr.Textbox(
342
+ placeholder="কি চলে আপনার মনে বলেন শুনি?",
343
+ scale=8,
344
+ show_label=False
345
+ )
346
  send = gr.Button("Send", variant="primary", scale=1)
347
 
348
+ clear = gr.Button("Clear Chat")
349
  state = gr.State([])
350
 
351
+ # Connect interactions with fixed MMR parameters
352
+ def chat_with_fixed_mmr(message, history):
353
+ return custom_unmad_satirical_bot(message, history, top_k=3, lambda_param=0.6)
354
+
355
+ msg.submit(
356
+ chat_with_fixed_mmr,
357
+ [msg, state],
358
+ [msg, chatbot]
359
+ )
360
+
361
+ send.click(
362
+ chat_with_fixed_mmr,
363
+ [msg, state],
364
+ [msg, chatbot]
365
+ )
366
+
367
  clear.click(lambda: ([], ""), None, [chatbot, msg])
368
 
369
  if __name__ == "__main__":