abdull4h commited on
Commit
732ba20
·
verified ·
1 Parent(s): 3373779

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +738 -90
app.py CHANGED
@@ -1,115 +1,763 @@
1
  import os
2
- import sys
3
- import traceback
4
- import gradio as gr
 
 
 
 
5
  import spaces
6
 
7
- @spaces.GPU
8
- def test_all_imports():
9
- results = []
10
-
11
- # Test basic imports
12
- for lib_name in [
13
- "torch",
14
- "numpy",
15
- "pandas",
16
- "PyPDF2",
17
- "transformers",
18
- "sentence_transformers"
19
- ]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  try:
21
- __import__(lib_name)
22
- results.append(f" {lib_name}: Successfully imported")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  except Exception as e:
24
- results.append(f" {lib_name}: Error - {str(e)}")
 
 
25
 
26
- # Test langchain imports specifically
27
- try:
28
- import langchain
29
- results.append(f"✅ langchain: Successfully imported (version {langchain.__version__})")
30
- except Exception as e:
31
- results.append(f"❌ langchain: Error - {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- # Test langchain_community imports
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  try:
35
- import langchain_community
36
- results.append(f"✅ langchain_community: Successfully imported")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  except Exception as e:
38
- results.append(f" langchain_community: Error - {str(e)}")
 
39
 
40
- # Try alternative import format
41
- try:
42
- import langchain.community
43
- results.append(f"✅ langchain.community: Successfully imported")
44
- except Exception as e2:
45
- results.append(f"❌ langchain.community: Error - {str(e2)}")
 
 
 
 
 
 
46
 
47
- # Test specific langchain imports that might be failing
48
- try:
49
- from langchain.text_splitter import RecursiveCharacterTextSplitter
50
- results.append(f" langchain.text_splitter: Successfully imported")
51
- except Exception as e:
52
- results.append(f"❌ langchain.text_splitter: Error - {str(e)}")
53
 
54
- try:
55
- from langchain.schema import Document
56
- results.append(f"✅ langchain.schema: Successfully imported")
57
- except Exception as e:
58
- results.append(f" langchain.schema: Error - {str(e)}")
 
 
 
59
 
60
- try:
61
- from langchain.embeddings import HuggingFaceEmbeddings
62
- results.append(f"✅ langchain.embeddings: Successfully imported")
63
- except Exception as e:
64
- results.append(f" langchain.embeddings: Error - {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  try:
67
- from langchain_community.vectorstores import FAISS
68
- results.append(f"✅ langchain_community.vectorstores: Successfully imported")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  except Exception as e:
70
- results.append(f" langchain_community.vectorstores: Error - {str(e)}")
 
 
 
 
 
 
 
 
 
 
71
 
72
- # Try alternative import
73
- try:
74
- from langchain.vectorstores import FAISS
75
- results.append(f"✅ langchain.vectorstores: Successfully imported")
76
- except Exception as e2:
77
- results.append(f"❌ langchain.vectorstores: Error - {str(e2)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- # Check installed package versions
80
- try:
81
- import pkg_resources
82
- results.append("\n📦 Installed Packages:")
83
- for package in ["langchain", "langchain-community", "transformers", "sentence-transformers"]:
84
- try:
85
- version = pkg_resources.get_distribution(package).version
86
- results.append(f" - {package}: {version}")
87
- except:
88
- results.append(f" - {package}: Not installed")
89
- except:
90
- results.append(" Could not check installed packages")
91
-
92
- # Check PDF files
93
- results.append("\n📄 PDF Files:")
94
- for pdf_file in ["saudi_vision203.pdf", "saudi_vision2030_ar.pdf"]:
95
- if os.path.exists(pdf_file):
96
- size = os.path.getsize(pdf_file) / (1024 * 1024) # MB
97
- results.append(f" - {pdf_file}: Found ({size:.2f} MB)")
98
- else:
99
- results.append(f" - {pdf_file}: Not found")
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- return "\n".join(results)
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
- def main():
104
- with gr.Blocks(title="Import Diagnosis") as interface:
105
- gr.Markdown("# Vision 2030 Assistant - Import Error Diagnosis")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- test_button = gr.Button("Test All Imports")
108
- results_box = gr.Textbox(label="Results", lines=30)
 
 
 
 
109
 
110
- test_button.click(test_all_imports, inputs=[], outputs=[results_box])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- interface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  if __name__ == "__main__":
115
- main()
 
 
1
  import os
2
+ import re
3
+ import json
4
+ import torch
5
+ import numpy as np
6
+ import pandas as pd
7
+ from tqdm import tqdm
8
+ from pathlib import Path
9
  import spaces
10
 
11
+ # PDF processing
12
+ import PyPDF2
13
+
14
+ # LLM and embeddings
15
+ from transformers import AutoTokenizer, AutoModelForCausalLM
16
+ from sentence_transformers import SentenceTransformer
17
+
18
+ # RAG components - using exact import syntax from installed packages
19
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
20
+ from langchain_community.vectorstores import FAISS # Note: langchain_community not langchain.community
21
+ from langchain.schema import Document
22
+ from langchain.embeddings import HuggingFaceEmbeddings
23
+
24
+ # Arabic text processing
25
+ import arabic_reshaper
26
+ from bidi.algorithm import get_display
27
+
28
+ # Evaluation
29
+ from rouge_score import rouge_scorer
30
+ import sacrebleu
31
+ from sklearn.metrics import accuracy_score, precision_recall_fscore_support
32
+ import matplotlib.pyplot as plt
33
+ import seaborn as sns
34
+ from collections import defaultdict
35
+
36
+ # Gradio for the interface
37
+ import gradio as gr
38
+
39
+ # Helper functions
40
+ def safe_tokenize(text):
41
+ """Pure regex tokenizer with no NLTK dependency"""
42
+ if not text:
43
+ return []
44
+ # Replace punctuation with spaces around them
45
+ text = re.sub(r'([.,!?;:()\[\]{}"\'/\\])', r' \1 ', text)
46
+ # Split on whitespace and filter empty strings
47
+ return [token for token in re.split(r'\s+', text.lower()) if token]
48
+
49
+ def detect_language(text):
50
+ """Detect if text is primarily Arabic or English"""
51
+ # Simple heuristic: count Arabic characters
52
+ arabic_chars = re.findall(r'[\u0600-\u06FF]', text)
53
+ is_arabic = len(arabic_chars) > len(text) * 0.5
54
+ return "arabic" if is_arabic else "english"
55
+
56
+ # Define evaluation metrics
57
+ def calculate_bleu(prediction, reference):
58
+ """Calculate BLEU score without any NLTK dependency"""
59
+ # Tokenize texts using our own tokenizer
60
+ pred_tokens = safe_tokenize(prediction.lower())
61
+ ref_tokens = [safe_tokenize(reference.lower())]
62
+
63
+ # If either is empty, return 0
64
+ if not pred_tokens or not ref_tokens[0]:
65
+ return {"bleu_1": 0, "bleu_2": 0, "bleu_4": 0}
66
+
67
+ # Get n-grams function
68
+ def get_ngrams(tokens, n):
69
+ return [tuple(tokens[i:i+n]) for i in range(len(tokens) - n + 1)]
70
+
71
+ # Calculate precision for each n-gram level
72
+ precisions = []
73
+ for n in range(1, 5): # 1-gram to 4-gram
74
+ if len(pred_tokens) < n:
75
+ precisions.append(0)
76
+ continue
77
+
78
+ pred_ngrams = get_ngrams(pred_tokens, n)
79
+ ref_ngrams = get_ngrams(ref_tokens[0], n)
80
+
81
+ # Count matches
82
+ matches = sum(1 for ng in pred_ngrams if ng in ref_ngrams)
83
+
84
+ # Calculate precision
85
+ if pred_ngrams:
86
+ precisions.append(matches / len(pred_ngrams))
87
+ else:
88
+ precisions.append(0)
89
+
90
+ # Return BLEU scores
91
+ return {
92
+ "bleu_1": precisions[0],
93
+ "bleu_2": (precisions[0] * precisions[1]) ** 0.5 if len(precisions) > 1 else 0,
94
+ "bleu_4": (precisions[0] * precisions[1] * precisions[2] * precisions[3]) ** 0.25 if len(precisions) > 3 else 0
95
+ }
96
+
97
+ def calculate_meteor(prediction, reference):
98
+ """Simple word overlap metric as METEOR alternative"""
99
+ # Tokenize with our custom tokenizer
100
+ pred_tokens = set(safe_tokenize(prediction.lower()))
101
+ ref_tokens = set(safe_tokenize(reference.lower()))
102
+
103
+ # Calculate Jaccard similarity as METEOR alternative
104
+ if not pred_tokens or not ref_tokens:
105
+ return 0
106
+
107
+ intersection = len(pred_tokens.intersection(ref_tokens))
108
+ union = len(pred_tokens.union(ref_tokens))
109
+
110
+ return intersection / union if union > 0 else 0
111
+
112
+ def calculate_f1_precision_recall(prediction, reference):
113
+ """Calculate word-level F1, precision, and recall with custom tokenizer"""
114
+ # Tokenize with our custom tokenizer
115
+ pred_tokens = set(safe_tokenize(prediction.lower()))
116
+ ref_tokens = set(safe_tokenize(reference.lower()))
117
+
118
+ # Calculate overlap
119
+ common = pred_tokens.intersection(ref_tokens)
120
+
121
+ # Calculate precision, recall, F1
122
+ precision = len(common) / len(pred_tokens) if pred_tokens else 0
123
+ recall = len(common) / len(ref_tokens) if ref_tokens else 0
124
+ f1 = 2 * precision * recall / (precision + recall) if (precision + recall) else 0
125
+
126
+ return {'precision': precision, 'recall': recall, 'f1': f1}
127
+
128
+ def evaluate_retrieval_quality(contexts, query, language):
129
+ """Evaluate the quality of retrieved contexts"""
130
+ # This is a placeholder function - simplified for testing
131
+ return {
132
+ 'language_match_ratio': 1.0,
133
+ 'source_diversity': len(set([ctx.get('source', '') for ctx in contexts])) / max(1, len(contexts)),
134
+ 'mrr': 1.0
135
+ }
136
+
137
+ # PDF Processing and Vector Store
138
+ def simple_process_pdfs(pdf_paths):
139
+ """Process PDF documents and return document objects"""
140
+ documents = []
141
+
142
+ print(f"Processing PDFs: {pdf_paths}")
143
+
144
+ for pdf_path in pdf_paths:
145
  try:
146
+ if not os.path.exists(pdf_path):
147
+ print(f"Warning: {pdf_path} does not exist")
148
+ continue
149
+
150
+ print(f"Processing {pdf_path}...")
151
+ text = ""
152
+ with open(pdf_path, 'rb') as file:
153
+ reader = PyPDF2.PdfReader(file)
154
+ for page in reader.pages:
155
+ page_text = page.extract_text()
156
+ if page_text: # If we got text from this page
157
+ text += page_text + "\n\n"
158
+
159
+ if text.strip(): # If we got some text
160
+ doc = Document(
161
+ page_content=text,
162
+ metadata={"source": pdf_path, "filename": os.path.basename(pdf_path)}
163
+ )
164
+ documents.append(doc)
165
+ print(f"Successfully processed: {pdf_path}")
166
+ else:
167
+ print(f"Warning: No text extracted from {pdf_path}")
168
  except Exception as e:
169
+ print(f"Error processing {pdf_path}: {e}")
170
+ import traceback
171
+ traceback.print_exc()
172
 
173
+ print(f"Processed {len(documents)} PDF documents")
174
+ return documents
175
+
176
+ def create_vector_store(documents):
177
+ """Split documents into chunks and create a FAISS vector store"""
178
+ # Text splitter for breaking documents into chunks
179
+ text_splitter = RecursiveCharacterTextSplitter(
180
+ chunk_size=500,
181
+ chunk_overlap=50,
182
+ separators=["\n\n", "\n", ".", "!", "?", ",", " ", ""]
183
+ )
184
+
185
+ # Split documents into chunks
186
+ chunks = []
187
+ for doc in documents:
188
+ doc_chunks = text_splitter.split_text(doc.page_content)
189
+ # Preserve metadata for each chunk
190
+ chunks.extend([
191
+ Document(page_content=chunk, metadata=doc.metadata)
192
+ for chunk in doc_chunks
193
+ ])
194
+
195
+ print(f"Created {len(chunks)} chunks from {len(documents)} documents")
196
 
197
+ # Create a proper embedding function for LangChain
198
+ embedding_function = HuggingFaceEmbeddings(
199
+ model_name="sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
200
+ )
201
+
202
+ # Create FAISS index
203
+ vector_store = FAISS.from_documents(
204
+ chunks,
205
+ embedding_function
206
+ )
207
+
208
+ return vector_store
209
+
210
+ # Model Loading and RAG System
211
+ @spaces.GPU
212
+ def load_model_and_tokenizer():
213
+ """Load the ALLaM-7B model and tokenizer with error handling"""
214
+ model_name = "ALLaM-AI/ALLaM-7B-Instruct-preview"
215
+ print(f"Loading model: {model_name}")
216
+
217
  try:
218
+ # First attempt with AutoTokenizer
219
+ tokenizer = AutoTokenizer.from_pretrained(
220
+ model_name,
221
+ trust_remote_code=True,
222
+ use_fast=False
223
+ )
224
+
225
+ # Load model with appropriate settings for ALLaM
226
+ model = AutoModelForCausalLM.from_pretrained(
227
+ model_name,
228
+ torch_dtype=torch.bfloat16,
229
+ trust_remote_code=True,
230
+ device_map="auto",
231
+ )
232
+
233
+ print("Model loaded successfully with AutoTokenizer!")
234
+
235
  except Exception as e:
236
+ print(f"First loading attempt failed: {e}")
237
+ print("Trying alternative loading approach...")
238
 
239
+ # Try with specific tokenizer class if the first attempt fails
240
+ from transformers import LlamaTokenizer
241
+
242
+ tokenizer = LlamaTokenizer.from_pretrained(model_name)
243
+ model = AutoModelForCausalLM.from_pretrained(
244
+ model_name,
245
+ torch_dtype=torch.float16,
246
+ trust_remote_code=True,
247
+ device_map="auto",
248
+ )
249
+
250
+ print("Model loaded successfully with LlamaTokenizer!")
251
 
252
+ return model, tokenizer
253
+
254
+ def retrieve_context(query, vector_store, top_k=5):
255
+ """Retrieve most relevant document chunks for a given query"""
256
+ # Search the vector store using similarity search
257
+ results = vector_store.similarity_search_with_score(query, k=top_k)
258
 
259
+ # Format the retrieved contexts
260
+ contexts = []
261
+ for doc, score in results:
262
+ contexts.append({
263
+ "content": doc.page_content,
264
+ "source": doc.metadata.get("source", "Unknown"),
265
+ "relevance_score": score
266
+ })
267
 
268
+ return contexts
269
+
270
+ @spaces.GPU
271
+ def generate_response(query, contexts, model, tokenizer, language="auto"):
272
+ """Generate a response using retrieved contexts with ALLaM-specific formatting"""
273
+ # Auto-detect language if not specified
274
+ if language == "auto":
275
+ language = detect_language(query)
276
+
277
+ # Format the prompt based on language
278
+ if language == "arabic":
279
+ instruction = (
280
+ "أنت مساعد افتراضي يهتم برؤية السعودية 2030. استخدم المعلومات التالية للإجابة على السؤال. "
281
+ "إذا لم تعرف الإجابة، فقل بأمانة إنك لا تعرف."
282
+ )
283
+ else: # english
284
+ instruction = (
285
+ "You are a virtual assistant for Saudi Vision 2030. Use the following information to answer the question. "
286
+ "If you don't know the answer, honestly say you don't know."
287
+ )
288
+
289
+ # Combine retrieved contexts
290
+ context_text = "\n\n".join([f"Document: {ctx['content']}" for ctx in contexts])
291
+
292
+ # Format the prompt for ALLaM instruction format
293
+ prompt = f"""<s>[INST] {instruction}
294
+
295
+ Context:
296
+ {context_text}
297
+
298
+ Question: {query} [/INST]</s>"""
299
 
300
  try:
301
+ # Generate response with appropriate parameters for ALLaM
302
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
303
+
304
+ # Generate with appropriate parameters
305
+ outputs = model.generate(
306
+ inputs.input_ids,
307
+ attention_mask=inputs.attention_mask,
308
+ max_new_tokens=512,
309
+ temperature=0.7,
310
+ top_p=0.9,
311
+ do_sample=True,
312
+ repetition_penalty=1.1
313
+ )
314
+
315
+ # Decode the response
316
+ full_output = tokenizer.decode(outputs[0], skip_special_tokens=True)
317
+
318
+ # Extract just the answer part (after the instruction)
319
+ response = full_output.split("[/INST]")[-1].strip()
320
+
321
+ # If response is empty for some reason, return the full output
322
+ if not response:
323
+ response = full_output
324
+
325
+ return response
326
+
327
  except Exception as e:
328
+ print(f"Error during generation: {e}")
329
+ # Fallback response
330
+ return "I apologize, but I encountered an error while generating a response."
331
+
332
+ # Assistant Class
333
+ class Vision2030Assistant:
334
+ def __init__(self, model, tokenizer, vector_store):
335
+ self.model = model
336
+ self.tokenizer = tokenizer
337
+ self.vector_store = vector_store
338
+ self.conversation_history = []
339
 
340
+ def answer(self, user_query):
341
+ """Process a user query and return a response with sources"""
342
+ # Detect language
343
+ language = detect_language(user_query)
344
+
345
+ # Add user query to conversation history
346
+ self.conversation_history.append({"role": "user", "content": user_query})
347
+
348
+ # Get the full conversation context
349
+ conversation_context = "\n".join([
350
+ f"{'User' if msg['role'] == 'user' else 'Assistant'}: {msg['content']}"
351
+ for msg in self.conversation_history[-6:] # Keep last 3 turns (6 messages)
352
+ ])
353
+
354
+ # Enhance query with conversation context for better retrieval
355
+ enhanced_query = f"{conversation_context}\n{user_query}"
356
+
357
+ # Retrieve relevant contexts
358
+ contexts = retrieve_context(enhanced_query, self.vector_store, top_k=5)
359
+
360
+ # Generate response
361
+ response = generate_response(user_query, contexts, self.model, self.tokenizer, language)
362
+
363
+ # Add response to conversation history
364
+ self.conversation_history.append({"role": "assistant", "content": response})
365
+
366
+ # Also return sources for transparency
367
+ sources = [ctx.get("source", "Unknown") for ctx in contexts]
368
+ unique_sources = list(set(sources))
369
+
370
+ return response, unique_sources, contexts
371
 
372
+ def reset_conversation(self):
373
+ """Reset the conversation history"""
374
+ self.conversation_history = []
375
+ return "Conversation has been reset."
376
+
377
+ # Comprehensive evaluation dataset
378
+ comprehensive_evaluation_data = [
379
+ # === Overview ===
380
+ {
381
+ "query": "ما هي رؤية السعودية 2030؟",
382
+ "reference": "رؤية السعودية 2030 هي خطة استراتيجية تهدف إلى تنويع الاقتصاد السعودي وتقليل الاعتماد على النفط مع تطوير قطاعات مختلفة مثل الصحة والتعليم والسياحة.",
383
+ "category": "overview",
384
+ "language": "arabic"
385
+ },
386
+ {
387
+ "query": "What is Saudi Vision 2030?",
388
+ "reference": "Saudi Vision 2030 is a strategic framework aiming to diversify Saudi Arabia's economy and reduce dependence on oil, while developing sectors like health, education, and tourism.",
389
+ "category": "overview",
390
+ "language": "english"
391
+ },
392
+
393
+ # === Economic Goals ===
394
+ {
395
+ "query": "ما هي الأهداف الاقتصادية لرؤية 2030؟",
396
+ "reference": "تشمل الأهداف الاقتصادية زيادة مساهمة القطاع الخاص إلى 65%، وزيادة الصادرات غير النفطية إلى 50% من الناتج المحلي غير النفطي، وخفض البطالة إلى 7%.",
397
+ "category": "economic",
398
+ "language": "arabic"
399
+ },
400
+ {
401
+ "query": "What are the economic goals of Vision 2030?",
402
+ "reference": "The economic goals of Vision 2030 include increasing private sector contribution from 40% to 65% of GDP, raising non-oil exports from 16% to 50%, reducing unemployment from 11.6% to 7%.",
403
+ "category": "economic",
404
+ "language": "english"
405
+ },
406
 
407
+ # === Social Goals ===
408
+ {
409
+ "query": "كيف تعزز رؤية 2030 الإرث الثقافي السعودي؟",
410
+ "reference": "تتضمن رؤية 2030 الحفاظ على الهوية الوطنية، تسجيل مواقع أثرية في اليونسكو، وتعزيز الفعاليات الثقافية.",
411
+ "category": "social",
412
+ "language": "arabic"
413
+ },
414
+ {
415
+ "query": "How does Vision 2030 aim to improve quality of life?",
416
+ "reference": "Vision 2030 plans to enhance quality of life by expanding sports facilities, promoting cultural activities, and boosting tourism and entertainment sectors.",
417
+ "category": "social",
418
+ "language": "english"
419
+ }
420
+ ]
421
 
422
+ # Gradio Interface
423
+ def initialize_system():
424
+ """Initialize the Vision 2030 Assistant system"""
425
+ # Define paths for PDF files in the root directory
426
+ pdf_files = ["saudi_vision203.pdf", "saudi_vision2030_ar.pdf"]
427
+
428
+ # Print available files for debugging
429
+ print("Files in current directory:", os.listdir("."))
430
+
431
+ # Process PDFs and create vector store
432
+ vector_store_dir = "vector_stores"
433
+ os.makedirs(vector_store_dir, exist_ok=True)
434
+
435
+ if os.path.exists(os.path.join(vector_store_dir, "index.faiss")):
436
+ print("Loading existing vector store...")
437
+ embedding_function = HuggingFaceEmbeddings(
438
+ model_name="sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
439
+ )
440
+ vector_store = FAISS.load_local(vector_store_dir, embedding_function)
441
+ else:
442
+ print("Creating new vector store...")
443
+ documents = simple_process_pdfs(pdf_files)
444
+ if not documents:
445
+ raise ValueError("No documents were processed successfully. Cannot continue.")
446
+ vector_store = create_vector_store(documents)
447
+ vector_store.save_local(vector_store_dir)
448
+
449
+ # Load model and tokenizer
450
+ model, tokenizer = load_model_and_tokenizer()
451
+
452
+ # Initialize assistant
453
+ assistant = Vision2030Assistant(model, tokenizer, vector_store)
454
+
455
+ return assistant
456
+
457
+ def evaluate_response(query, response, reference):
458
+ """Evaluate a single response against a reference"""
459
+ # Calculate metrics
460
+ rouge = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
461
+ rouge_scores = rouge.score(response, reference)
462
+
463
+ bleu_scores = calculate_bleu(response, reference)
464
+ meteor = calculate_meteor(response, reference)
465
+ word_metrics = calculate_f1_precision_recall(response, reference)
466
+
467
+ # Format results
468
+ evaluation_results = {
469
+ "ROUGE-1": f"{rouge_scores['rouge1'].fmeasure:.4f}",
470
+ "ROUGE-2": f"{rouge_scores['rouge2'].fmeasure:.4f}",
471
+ "ROUGE-L": f"{rouge_scores['rougeL'].fmeasure:.4f}",
472
+ "BLEU-1": f"{bleu_scores['bleu_1']:.4f}",
473
+ "BLEU-4": f"{bleu_scores['bleu_4']:.4f}",
474
+ "METEOR": f"{meteor:.4f}",
475
+ "Word Precision": f"{word_metrics['precision']:.4f}",
476
+ "Word Recall": f"{word_metrics['recall']:.4f}",
477
+ "Word F1": f"{word_metrics['f1']:.4f}"
478
+ }
479
+
480
+ return evaluation_results
481
+
482
+ @spaces.GPU
483
+ def run_conversation(assistant, query):
484
+ """Run a query through the assistant and return the response"""
485
+ response, sources, contexts = assistant.answer(query)
486
+ return response, sources, contexts
487
+
488
+ @spaces.GPU
489
+ def run_evaluation_on_sample(assistant, sample_index=0):
490
+ """Run evaluation on a selected sample from the evaluation dataset"""
491
+ if sample_index < 0 or sample_index >= len(comprehensive_evaluation_data):
492
+ return "Invalid sample index", "", "", {}
493
+
494
+ # Get the sample
495
+ sample = comprehensive_evaluation_data[sample_index]
496
+ query = sample["query"]
497
+ reference = sample["reference"]
498
+ category = sample["category"]
499
+ language = sample["language"]
500
+
501
+ # Reset conversation and get response
502
+ assistant.reset_conversation()
503
+ response, sources, contexts = assistant.answer(query)
504
+
505
+ # Evaluate response
506
+ evaluation_results = evaluate_response(query, response, reference)
507
+
508
+ return query, response, reference, evaluation_results, sources, category, language
509
+
510
+ def qualitative_evaluation_interface(assistant=None):
511
+ """Create a Gradio interface for qualitative evaluation"""
512
+
513
+ # If assistant is None, create a simplified interface
514
+ if assistant is None:
515
+ with gr.Blocks(title="Vision 2030 Assistant - Initialization Error") as interface:
516
+ gr.Markdown("# Vision 2030 Assistant - Initialization Error")
517
+ gr.Markdown("There was an error initializing the assistant. Please check the logs for details.")
518
+ gr.Textbox(label="Status", value="System initialization failed")
519
+ return interface
520
+
521
+ sample_options = [f"{i+1}. {item['query'][:50]}..." for i, item in enumerate(comprehensive_evaluation_data)]
522
+
523
+ with gr.Blocks(title="Vision 2030 Assistant - Qualitative Evaluation") as interface:
524
+ gr.Markdown("# Vision 2030 Assistant - Qualitative Evaluation")
525
+ gr.Markdown("This interface allows you to evaluate the Vision 2030 Assistant on predefined samples or your own queries.")
526
+
527
+ with gr.Tab("Sample Evaluation"):
528
+ gr.Markdown("### Evaluate the assistant on predefined samples")
529
+
530
+ sample_dropdown = gr.Dropdown(
531
+ choices=sample_options,
532
+ label="Select a sample query",
533
+ value=sample_options[0] if sample_options else None
534
+ )
535
+
536
+ eval_button = gr.Button("Evaluate Sample")
537
+
538
+ with gr.Row():
539
+ with gr.Column():
540
+ sample_query = gr.Textbox(label="Query")
541
+ sample_category = gr.Textbox(label="Category")
542
+ sample_language = gr.Textbox(label="Language")
543
+
544
+ with gr.Column():
545
+ sample_response = gr.Textbox(label="Assistant Response")
546
+ sample_reference = gr.Textbox(label="Reference Answer")
547
+ sample_sources = gr.Textbox(label="Sources Used")
548
+
549
+ with gr.Row():
550
+ metrics_display = gr.JSON(label="Evaluation Metrics")
551
+
552
+ with gr.Tab("Custom Evaluation"):
553
+ gr.Markdown("### Evaluate the assistant on your own query")
554
+
555
+ custom_query = gr.Textbox(
556
+ lines=3,
557
+ placeholder="Enter your question about Saudi Vision 2030...",
558
+ label="Your Query"
559
+ )
560
+
561
+ custom_reference = gr.Textbox(
562
+ lines=3,
563
+ placeholder="Enter a reference answer (optional)...",
564
+ label="Reference Answer (Optional)"
565
+ )
566
+
567
+ custom_eval_button = gr.Button("Get Response and Evaluate")
568
+
569
+ custom_response = gr.Textbox(label="Assistant Response")
570
+ custom_sources = gr.Textbox(label="Sources Used")
571
+
572
+ custom_metrics = gr.JSON(
573
+ label="Evaluation Metrics (if reference provided)",
574
+ visible=True
575
+ )
576
+
577
+ with gr.Tab("Conversation Mode"):
578
+ gr.Markdown("### Have a conversation with the Vision 2030 Assistant")
579
+
580
+ chatbot = gr.Chatbot(label="Conversation")
581
+
582
+ conv_input = gr.Textbox(
583
+ placeholder="Ask about Saudi Vision 2030...",
584
+ label="Your message"
585
+ )
586
+
587
+ with gr.Row():
588
+ conv_button = gr.Button("Send")
589
+ reset_button = gr.Button("Reset Conversation")
590
+
591
+ conv_sources = gr.Textbox(label="Sources Used")
592
+
593
+ # Sample evaluation event handlers
594
+ def handle_sample_selection(selection):
595
+ if not selection:
596
+ return "", "", "", "", "", "", ""
597
+
598
+ # Extract index from the selection string
599
+ try:
600
+ index = int(selection.split(".")[0]) - 1
601
+ query, response, reference, metrics, sources, category, language = run_evaluation_on_sample(assistant, index)
602
+ sources_str = ", ".join(sources)
603
+ return query, response, reference, metrics, sources_str, category, language
604
+ except Exception as e:
605
+ print(f"Error in handle_sample_selection: {e}")
606
+ import traceback
607
+ traceback.print_exc()
608
+ return f"Error processing selection: {e}", "", "", {}, "", "", ""
609
 
610
+ eval_button.click(
611
+ handle_sample_selection,
612
+ inputs=[sample_dropdown],
613
+ outputs=[sample_query, sample_response, sample_reference, metrics_display,
614
+ sample_sources, sample_category, sample_language]
615
+ )
616
 
617
+ sample_dropdown.change(
618
+ handle_sample_selection,
619
+ inputs=[sample_dropdown],
620
+ outputs=[sample_query, sample_response, sample_reference, metrics_display,
621
+ sample_sources, sample_category, sample_language]
622
+ )
623
+
624
+ # Custom evaluation event handlers
625
+ @spaces.GPU
626
+ def handle_custom_evaluation(query, reference):
627
+ if not query:
628
+ return "Please enter a query", "", {}
629
+
630
+ # Reset conversation to ensure clean state
631
+ assistant.reset_conversation()
632
+
633
+ # Get response
634
+ response, sources, _ = assistant.answer(query)
635
+ sources_str = ", ".join(sources)
636
+
637
+ # Evaluate if reference is provided
638
+ metrics = {}
639
+ if reference:
640
+ metrics = evaluate_response(query, response, reference)
641
+
642
+ return response, sources_str, metrics
643
+
644
+ custom_eval_button.click(
645
+ handle_custom_evaluation,
646
+ inputs=[custom_query, custom_reference],
647
+ outputs=[custom_response, custom_sources, custom_metrics]
648
+ )
649
+
650
+ # Conversation mode event handlers
651
+ @spaces.GPU
652
+ def handle_conversation(message, history):
653
+ if not message:
654
+ return history, "", ""
655
+
656
+ # Get response
657
+ response, sources, _ = assistant.answer(message)
658
+ sources_str = ", ".join(sources)
659
+
660
+ # Update history
661
+ history = history + [[message, response]]
662
+
663
+ return history, "", sources_str
664
+
665
+ def reset_conv():
666
+ result = assistant.reset_conversation()
667
+ return [], result, ""
668
+
669
+ conv_button.click(
670
+ handle_conversation,
671
+ inputs=[conv_input, chatbot],
672
+ outputs=[chatbot, conv_input, conv_sources]
673
+ )
674
+
675
+ reset_button.click(
676
+ reset_conv,
677
+ inputs=[],
678
+ outputs=[chatbot, conv_input, conv_sources]
679
+ )
680
+
681
+ return interface
682
+
683
+ # Main function to run in Hugging Face Space
684
+ def main():
685
+ # Start with a debugging report
686
+ print("=" * 50)
687
+ print("SYSTEM INITIALIZATION")
688
+ print("=" * 50)
689
+ print("Current directory:", os.getcwd())
690
+ print("Files in directory:", os.listdir("."))
691
+ print("=" * 50)
692
 
693
+ # Initialize the system with simplified error handling
694
+ try:
695
+ # First create a very simple Gradio interface to show we're starting
696
+ with gr.Blocks(title="Vision 2030 Assistant - Starting") as loading_interface:
697
+ gr.Markdown("# Vision 2030 Assistant")
698
+ gr.Markdown("System is initializing. This may take a few minutes...")
699
+ status = gr.Textbox(value="Loading resources...", label="Status")
700
+
701
+ app = loading_interface.queue()
702
+
703
+ # Now try the actual initialization
704
+ try:
705
+ print("Starting system initialization...")
706
+ assistant = initialize_system()
707
+
708
+ print("Creating interface...")
709
+ interface = qualitative_evaluation_interface(assistant)
710
+
711
+ print("Launching interface...")
712
+ return interface
713
+ except Exception as e:
714
+ print(f"Error during initialization: {e}")
715
+ import traceback
716
+ traceback.print_exc()
717
+
718
+ # Create a simple error interface
719
+ with gr.Blocks(title="Vision 2030 Assistant - Error") as debug_interface:
720
+ gr.Markdown("# Vision 2030 Assistant - Initialization Error")
721
+ gr.Markdown("There was an error initializing the assistant.")
722
+
723
+ # Display error details
724
+ gr.Textbox(
725
+ value=f"Error: {str(e)}",
726
+ label="Error Details",
727
+ lines=5
728
+ )
729
+
730
+ # Show file system status
731
+ files_list = "\n".join(os.listdir("."))
732
+ gr.Textbox(
733
+ value=files_list,
734
+ label="Files in Directory",
735
+ lines=10
736
+ )
737
+
738
+ # Add a button to check PDFs
739
+ def check_pdfs():
740
+ result = []
741
+ for pdf_file in ["saudi_vision203.pdf", "saudi_vision2030_ar.pdf"]:
742
+ if os.path.exists(pdf_file):
743
+ size = os.path.getsize(pdf_file) / (1024 * 1024) # Size in MB
744
+ result.append(f"{pdf_file}: Found ({size:.2f} MB)")
745
+ else:
746
+ result.append(f"{pdf_file}: Not found")
747
+ return "\n".join(result)
748
+
749
+ check_btn = gr.Button("Check PDF Files")
750
+ pdf_status = gr.Textbox(label="PDF Status", lines=3)
751
+ check_btn.click(check_pdfs, inputs=[], outputs=[pdf_status])
752
+
753
+ return debug_interface
754
+ except Exception as e:
755
+ print(f"Critical error: {e}")
756
+ with gr.Blocks(title="Vision 2030 Assistant - Critical Error") as critical_error:
757
+ gr.Markdown("# Vision 2030 Assistant - Critical Error")
758
+ gr.Markdown(f"A critical error occurred: {str(e)}")
759
+ return critical_error
760
 
761
  if __name__ == "__main__":
762
+ demo = main()
763
+ demo.launch()