Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
# Vision 2030 Virtual Assistant with RAG and Evaluation Framework
|
2 |
-
# Modified for Hugging Face Spaces compatibility
|
3 |
|
4 |
import gradio as gr
|
5 |
import time
|
@@ -16,6 +16,8 @@ import json
|
|
16 |
from langdetect import detect
|
17 |
from sentence_transformers import SentenceTransformer
|
18 |
import faiss
|
|
|
|
|
19 |
|
20 |
# Configure logging
|
21 |
logging.basicConfig(
|
@@ -27,6 +29,10 @@ logging.basicConfig(
|
|
27 |
)
|
28 |
logger = logging.getLogger('vision2030_assistant')
|
29 |
|
|
|
|
|
|
|
|
|
30 |
class Vision2030Assistant:
|
31 |
def __init__(self, pdf_path=None, eval_data_path=None):
|
32 |
"""
|
@@ -64,18 +70,53 @@ class Vision2030Assistant:
|
|
64 |
self.response_history = []
|
65 |
logger.info("Vision 2030 Assistant initialized successfully")
|
66 |
|
|
|
67 |
def load_embedding_models(self):
|
68 |
-
"""Load embedding models for retrieval"""
|
69 |
-
logger.info("Loading embedding models...")
|
70 |
|
71 |
try:
|
72 |
# Load embedding models
|
73 |
self.arabic_embedder = SentenceTransformer('CAMeL-Lab/bert-base-arabic-camelbert-ca')
|
74 |
self.english_embedder = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
logger.info("Embedding models loaded successfully")
|
76 |
except Exception as e:
|
77 |
logger.error(f"Error loading embedding models: {str(e)}")
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
|
80 |
def load_and_process_documents(self, pdf_path):
|
81 |
"""Load and process the Vision 2030 document from PDF"""
|
@@ -152,16 +193,28 @@ class Vision2030Assistant:
|
|
152 |
"تتضمن رؤية 2030 خططًا لتطوير البنية التحتية الرقمية والدعم للشركات الناشئة التكنولوجية في المملكة العربية السعودية."
|
153 |
]
|
154 |
|
|
|
155 |
def _create_indices(self):
|
156 |
-
"""Create FAISS indices for fast text retrieval"""
|
157 |
logger.info("Creating FAISS indices for text retrieval")
|
158 |
|
159 |
try:
|
160 |
# Process and embed English texts
|
161 |
self.english_vectors = []
|
162 |
for text in self.english_texts:
|
163 |
-
|
164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
|
166 |
# Create English index
|
167 |
if self.english_vectors:
|
@@ -174,8 +227,19 @@ class Vision2030Assistant:
|
|
174 |
# Process and embed Arabic texts
|
175 |
self.arabic_vectors = []
|
176 |
for text in self.arabic_texts:
|
177 |
-
|
178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
|
180 |
# Create Arabic index
|
181 |
if self.arabic_vectors:
|
@@ -225,17 +289,28 @@ class Vision2030Assistant:
|
|
225 |
]
|
226 |
logger.info(f"Created {len(self.eval_data)} sample evaluation examples")
|
227 |
|
|
|
228 |
def retrieve_context(self, query, lang):
|
229 |
-
"""Retrieve relevant context for a query based on language"""
|
230 |
start_time = time.time()
|
231 |
|
232 |
try:
|
233 |
if lang == "ar":
|
234 |
-
|
|
|
|
|
|
|
|
|
|
|
235 |
D, I = self.arabic_index.search(np.array([query_vec]), k=2) # Get top 2 most relevant chunks
|
236 |
context = "\n".join([self.arabic_texts[i] for i in I[0] if i < len(self.arabic_texts) and i >= 0])
|
237 |
else:
|
238 |
-
|
|
|
|
|
|
|
|
|
|
|
239 |
D, I = self.english_index.search(np.array([query_vec]), k=2) # Get top 2 most relevant chunks
|
240 |
context = "\n".join([self.english_texts[i] for i in I[0] if i < len(self.english_texts) and i >= 0])
|
241 |
|
@@ -345,8 +420,9 @@ class Vision2030Assistant:
|
|
345 |
|
346 |
return accuracy
|
347 |
|
|
|
348 |
def evaluate_on_test_set(self):
|
349 |
-
"""Evaluate the assistant on the test set"""
|
350 |
logger.info("Running evaluation on test set")
|
351 |
|
352 |
eval_results = []
|
@@ -442,102 +518,113 @@ class Vision2030Assistant:
|
|
442 |
|
443 |
# Create the Gradio interface
|
444 |
def create_gradio_interface():
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
def chat(message, history):
|
449 |
-
if not message.strip():
|
450 |
-
return history, ""
|
451 |
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
last_interaction = history[-1]
|
464 |
-
assistant.record_user_feedback(last_interaction[0], last_interaction[1], rating, feedback_text)
|
465 |
-
return f"Thank you for your feedback! (Rating: {rating}/5)"
|
466 |
-
return "No conversation found to rate."
|
467 |
-
|
468 |
-
def run_evaluation():
|
469 |
-
results = assistant.evaluate_on_test_set()
|
470 |
-
|
471 |
-
# Create summary text
|
472 |
-
summary = f"""
|
473 |
-
Evaluation Results:
|
474 |
-
------------------
|
475 |
-
Total questions evaluated: {len(results['detailed_results'])}
|
476 |
-
Overall factual accuracy: {results['average_factual_accuracy']:.2f}
|
477 |
-
Average response time: {results['average_response_time']:.4f} seconds
|
478 |
-
|
479 |
-
Detailed Results:
|
480 |
-
"""
|
481 |
-
|
482 |
-
for i, result in enumerate(results['detailed_results']):
|
483 |
-
summary += f"\nQ{i+1}: {result['question']}\n"
|
484 |
-
summary += f"Reference: {result['reference']}\n"
|
485 |
-
summary += f"Response: {result['response']}\n"
|
486 |
-
summary += f"Accuracy: {result['factual_accuracy']:.2f}\n"
|
487 |
-
summary += "-" * 40 + "\n"
|
488 |
-
|
489 |
-
# Return both the results summary and visualization
|
490 |
-
fig = assistant.visualize_evaluation_results(results)
|
491 |
|
492 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
493 |
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
gr.
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
540 |
|
541 |
-
# Launch the app
|
542 |
demo = create_gradio_interface()
|
543 |
demo.launch()
|
|
|
1 |
# Vision 2030 Virtual Assistant with RAG and Evaluation Framework
|
2 |
+
# Modified for Hugging Face Spaces compatibility with GPU support
|
3 |
|
4 |
import gradio as gr
|
5 |
import time
|
|
|
16 |
from langdetect import detect
|
17 |
from sentence_transformers import SentenceTransformer
|
18 |
import faiss
|
19 |
+
import torch
|
20 |
+
import spaces
|
21 |
|
22 |
# Configure logging
|
23 |
logging.basicConfig(
|
|
|
29 |
)
|
30 |
logger = logging.getLogger('vision2030_assistant')
|
31 |
|
32 |
+
# Check for GPU availability
|
33 |
+
has_gpu = torch.cuda.is_available()
|
34 |
+
logger.info(f"GPU available: {has_gpu}")
|
35 |
+
|
36 |
class Vision2030Assistant:
|
37 |
def __init__(self, pdf_path=None, eval_data_path=None):
|
38 |
"""
|
|
|
70 |
self.response_history = []
|
71 |
logger.info("Vision 2030 Assistant initialized successfully")
|
72 |
|
73 |
+
@spaces.GPU
|
74 |
def load_embedding_models(self):
|
75 |
+
"""Load embedding models for retrieval with GPU support"""
|
76 |
+
logger.info("Loading embedding models with GPU support...")
|
77 |
|
78 |
try:
|
79 |
# Load embedding models
|
80 |
self.arabic_embedder = SentenceTransformer('CAMeL-Lab/bert-base-arabic-camelbert-ca')
|
81 |
self.english_embedder = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
|
82 |
+
|
83 |
+
# Move to GPU if available
|
84 |
+
if has_gpu:
|
85 |
+
self.arabic_embedder = self.arabic_embedder.to('cuda')
|
86 |
+
self.english_embedder = self.english_embedder.to('cuda')
|
87 |
+
logger.info("Models moved to GPU")
|
88 |
+
|
89 |
logger.info("Embedding models loaded successfully")
|
90 |
except Exception as e:
|
91 |
logger.error(f"Error loading embedding models: {str(e)}")
|
92 |
+
# Create simple placeholder models if loading fails
|
93 |
+
self._create_fallback_embedders()
|
94 |
+
|
95 |
+
def _create_fallback_embedders(self):
|
96 |
+
"""Create fallback embedding methods if model loading fails"""
|
97 |
+
logger.warning("Using fallback embedding methods")
|
98 |
+
|
99 |
+
# Simple fallback using character-level encoding (not a real embedding, just for demo)
|
100 |
+
def simple_encode(text, dim=384):
|
101 |
+
import hashlib
|
102 |
+
# Create a hash of the text
|
103 |
+
hash_object = hashlib.md5(text.encode())
|
104 |
+
# Use the hash to seed a random number generator
|
105 |
+
import numpy as np
|
106 |
+
np.random.seed(int(hash_object.hexdigest(), 16) % 2**32)
|
107 |
+
# Generate a random vector
|
108 |
+
return np.random.randn(dim).astype(np.float32)
|
109 |
+
|
110 |
+
# Create embedding function objects
|
111 |
+
class SimpleEmbedder:
|
112 |
+
def __init__(self, dim=384):
|
113 |
+
self.dim = dim
|
114 |
+
|
115 |
+
def encode(self, text):
|
116 |
+
return simple_encode(text, self.dim)
|
117 |
+
|
118 |
+
self.arabic_embedder = SimpleEmbedder()
|
119 |
+
self.english_embedder = SimpleEmbedder()
|
120 |
|
121 |
def load_and_process_documents(self, pdf_path):
|
122 |
"""Load and process the Vision 2030 document from PDF"""
|
|
|
193 |
"تتضمن رؤية 2030 خططًا لتطوير البنية التحتية الرقمية والدعم للشركات الناشئة التكنولوجية في المملكة العربية السعودية."
|
194 |
]
|
195 |
|
196 |
+
@spaces.GPU
|
197 |
def _create_indices(self):
|
198 |
+
"""Create FAISS indices for fast text retrieval with GPU support"""
|
199 |
logger.info("Creating FAISS indices for text retrieval")
|
200 |
|
201 |
try:
|
202 |
# Process and embed English texts
|
203 |
self.english_vectors = []
|
204 |
for text in self.english_texts:
|
205 |
+
try:
|
206 |
+
if has_gpu and hasattr(self.english_embedder, 'to') and callable(getattr(self.english_embedder, 'to')):
|
207 |
+
# If it's a real model on GPU
|
208 |
+
with torch.no_grad():
|
209 |
+
vec = self.english_embedder.encode(text)
|
210 |
+
else:
|
211 |
+
# If it's our fallback
|
212 |
+
vec = self.english_embedder.encode(text)
|
213 |
+
self.english_vectors.append(vec)
|
214 |
+
except Exception as e:
|
215 |
+
logger.error(f"Error encoding English text: {str(e)}")
|
216 |
+
# Use a random vector as fallback
|
217 |
+
self.english_vectors.append(np.random.randn(384).astype(np.float32))
|
218 |
|
219 |
# Create English index
|
220 |
if self.english_vectors:
|
|
|
227 |
# Process and embed Arabic texts
|
228 |
self.arabic_vectors = []
|
229 |
for text in self.arabic_texts:
|
230 |
+
try:
|
231 |
+
if has_gpu and hasattr(self.arabic_embedder, 'to') and callable(getattr(self.arabic_embedder, 'to')):
|
232 |
+
# If it's a real model on GPU
|
233 |
+
with torch.no_grad():
|
234 |
+
vec = self.arabic_embedder.encode(text)
|
235 |
+
else:
|
236 |
+
# If it's our fallback
|
237 |
+
vec = self.arabic_embedder.encode(text)
|
238 |
+
self.arabic_vectors.append(vec)
|
239 |
+
except Exception as e:
|
240 |
+
logger.error(f"Error encoding Arabic text: {str(e)}")
|
241 |
+
# Use a random vector as fallback
|
242 |
+
self.arabic_vectors.append(np.random.randn(384).astype(np.float32))
|
243 |
|
244 |
# Create Arabic index
|
245 |
if self.arabic_vectors:
|
|
|
289 |
]
|
290 |
logger.info(f"Created {len(self.eval_data)} sample evaluation examples")
|
291 |
|
292 |
+
@spaces.GPU
|
293 |
def retrieve_context(self, query, lang):
|
294 |
+
"""Retrieve relevant context for a query based on language with GPU support"""
|
295 |
start_time = time.time()
|
296 |
|
297 |
try:
|
298 |
if lang == "ar":
|
299 |
+
if has_gpu and hasattr(self.arabic_embedder, 'to') and callable(getattr(self.arabic_embedder, 'to')):
|
300 |
+
with torch.no_grad():
|
301 |
+
query_vec = self.arabic_embedder.encode(query)
|
302 |
+
else:
|
303 |
+
query_vec = self.arabic_embedder.encode(query)
|
304 |
+
|
305 |
D, I = self.arabic_index.search(np.array([query_vec]), k=2) # Get top 2 most relevant chunks
|
306 |
context = "\n".join([self.arabic_texts[i] for i in I[0] if i < len(self.arabic_texts) and i >= 0])
|
307 |
else:
|
308 |
+
if has_gpu and hasattr(self.english_embedder, 'to') and callable(getattr(self.english_embedder, 'to')):
|
309 |
+
with torch.no_grad():
|
310 |
+
query_vec = self.english_embedder.encode(query)
|
311 |
+
else:
|
312 |
+
query_vec = self.english_embedder.encode(query)
|
313 |
+
|
314 |
D, I = self.english_index.search(np.array([query_vec]), k=2) # Get top 2 most relevant chunks
|
315 |
context = "\n".join([self.english_texts[i] for i in I[0] if i < len(self.english_texts) and i >= 0])
|
316 |
|
|
|
420 |
|
421 |
return accuracy
|
422 |
|
423 |
+
@spaces.GPU
|
424 |
def evaluate_on_test_set(self):
|
425 |
+
"""Evaluate the assistant on the test set with GPU support"""
|
426 |
logger.info("Running evaluation on test set")
|
427 |
|
428 |
eval_results = []
|
|
|
518 |
|
519 |
# Create the Gradio interface
|
520 |
def create_gradio_interface():
|
521 |
+
try:
|
522 |
+
# Initialize the assistant
|
523 |
+
assistant = Vision2030Assistant()
|
|
|
|
|
|
|
524 |
|
525 |
+
def chat(message, history):
|
526 |
+
if not message.strip():
|
527 |
+
return history, ""
|
528 |
+
|
529 |
+
# Generate response
|
530 |
+
reply = assistant.generate_response(message)
|
531 |
+
|
532 |
+
# Update history
|
533 |
+
history.append((message, reply))
|
534 |
+
|
535 |
+
return history, ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
536 |
|
537 |
+
def provide_feedback(history, rating, feedback_text):
|
538 |
+
# Record feedback for the last conversation
|
539 |
+
if history and len(history) > 0:
|
540 |
+
last_interaction = history[-1]
|
541 |
+
assistant.record_user_feedback(last_interaction[0], last_interaction[1], rating, feedback_text)
|
542 |
+
return f"Thank you for your feedback! (Rating: {rating}/5)"
|
543 |
+
return "No conversation found to rate."
|
544 |
+
|
545 |
+
@spaces.GPU
|
546 |
+
def run_evaluation():
|
547 |
+
results = assistant.evaluate_on_test_set()
|
548 |
+
|
549 |
+
# Create summary text
|
550 |
+
summary = f"""
|
551 |
+
Evaluation Results:
|
552 |
+
------------------
|
553 |
+
Total questions evaluated: {len(results['detailed_results'])}
|
554 |
+
Overall factual accuracy: {results['average_factual_accuracy']:.2f}
|
555 |
+
Average response time: {results['average_response_time']:.4f} seconds
|
556 |
+
|
557 |
+
Detailed Results:
|
558 |
+
"""
|
559 |
+
|
560 |
+
for i, result in enumerate(results['detailed_results']):
|
561 |
+
summary += f"\nQ{i+1}: {result['question']}\n"
|
562 |
+
summary += f"Reference: {result['reference']}\n"
|
563 |
+
summary += f"Response: {result['response']}\n"
|
564 |
+
summary += f"Accuracy: {result['factual_accuracy']:.2f}\n"
|
565 |
+
summary += "-" * 40 + "\n"
|
566 |
+
|
567 |
+
# Return both the results summary and visualization
|
568 |
+
fig = assistant.visualize_evaluation_results(results)
|
569 |
+
|
570 |
+
return summary, fig
|
571 |
|
572 |
+
@spaces.GPU
|
573 |
+
def process_uploaded_file(file):
|
574 |
+
if file is not None:
|
575 |
+
# Create a new assistant with the uploaded PDF
|
576 |
+
global assistant
|
577 |
+
assistant = Vision2030Assistant(pdf_path=file.name)
|
578 |
+
return f"Successfully processed {file.name}. The assistant is ready to use."
|
579 |
+
return "No file uploaded. Using sample data."
|
580 |
+
|
581 |
+
# Create the Gradio interface
|
582 |
+
with gr.Blocks() as demo:
|
583 |
+
gr.Markdown("# Vision 2030 Virtual Assistant 🌟")
|
584 |
+
gr.Markdown("Ask questions about Saudi Arabia's Vision 2030 in both Arabic and English")
|
585 |
+
|
586 |
+
with gr.Tab("Chat"):
|
587 |
+
chatbot = gr.Chatbot(height=400)
|
588 |
+
msg = gr.Textbox(label="Your Question", placeholder="Ask about Vision 2030...")
|
589 |
+
with gr.Row():
|
590 |
+
submit_btn = gr.Button("Submit")
|
591 |
+
clear_btn = gr.Button("Clear Chat")
|
592 |
+
|
593 |
+
gr.Markdown("### Provide Feedback")
|
594 |
+
with gr.Row():
|
595 |
+
rating = gr.Slider(minimum=1, maximum=5, step=1, value=3, label="Rate the Response (1-5)")
|
596 |
+
feedback_text = gr.Textbox(label="Additional Comments (Optional)")
|
597 |
+
feedback_btn = gr.Button("Submit Feedback")
|
598 |
+
feedback_result = gr.Textbox(label="Feedback Status")
|
599 |
+
|
600 |
+
with gr.Tab("Evaluation"):
|
601 |
+
evaluate_btn = gr.Button("Run Evaluation on Test Set")
|
602 |
+
eval_output = gr.Textbox(label="Evaluation Results", lines=20)
|
603 |
+
eval_chart = gr.Plot(label="Evaluation Metrics")
|
604 |
+
|
605 |
+
with gr.Tab("Upload PDF"):
|
606 |
+
file_input = gr.File(label="Upload Vision 2030 PDF")
|
607 |
+
upload_result = gr.Textbox(label="Upload Status")
|
608 |
+
upload_btn = gr.Button("Process PDF")
|
609 |
+
|
610 |
+
# Set up event handlers
|
611 |
+
msg.submit(chat, [msg, chatbot], [chatbot, msg])
|
612 |
+
submit_btn.click(chat, [msg, chatbot], [chatbot, msg])
|
613 |
+
clear_btn.click(lambda: [], None, chatbot)
|
614 |
+
feedback_btn.click(provide_feedback, [chatbot, rating, feedback_text], feedback_result)
|
615 |
+
evaluate_btn.click(run_evaluation, None, [eval_output, eval_chart])
|
616 |
+
upload_btn.click(process_uploaded_file, [file_input], upload_result)
|
617 |
+
|
618 |
+
return demo
|
619 |
+
except Exception as e:
|
620 |
+
logger.error(f"Error creating Gradio interface: {str(e)}")
|
621 |
+
# Create a simple demo for fallback
|
622 |
+
with gr.Blocks() as demo:
|
623 |
+
gr.Markdown("# Vision 2030 Virtual Assistant")
|
624 |
+
gr.Markdown("There was an error initializing the assistant. Please check the logs.")
|
625 |
+
gr.Markdown(f"Error: {str(e)}")
|
626 |
+
return demo
|
627 |
|
628 |
+
# Launch the app with proper GPU initialization
|
629 |
demo = create_gradio_interface()
|
630 |
demo.launch()
|