rivapereira123 commited on
Commit
b1a0ec6
Β·
verified Β·
1 Parent(s): 7898ef6

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +1031 -230
app.py CHANGED
@@ -4,10 +4,13 @@ import json
4
  import logging
5
  import warnings
6
  from pathlib import Path
7
- from typing import List, Dict, Any, Optional
8
  import hashlib
9
  import pickle
10
  from datetime import datetime
 
 
 
11
 
12
  # Suppress warnings for cleaner output
13
  warnings.filterwarnings("ignore")
@@ -22,15 +25,16 @@ import torch
22
  from transformers import (
23
  AutoTokenizer,
24
  AutoModelForCausalLM,
25
- BitsAndBytesConfig
 
26
  )
27
 
28
-
29
  # Document processing
30
- from llama_index.core import Document, VectorStoreIndex, ServiceContext
31
  from llama_index.core.node_parser import SentenceSplitter
32
  from llama_index.vector_stores.faiss import FaissVectorStore
33
  from llama_index.embeddings.huggingface import HuggingFaceEmbedding
 
34
 
35
  # PDF processing
36
  import PyPDF2
@@ -39,9 +43,6 @@ from io import BytesIO
39
  # Medical knowledge validation
40
  import re
41
 
42
-
43
- #STORAGE
44
-
45
  # Configure logging
46
  logging.basicConfig(
47
  level=logging.INFO,
@@ -50,7 +51,7 @@ logging.basicConfig(
50
  logger = logging.getLogger(__name__)
51
 
52
  class MedicalFactChecker:
53
- """Optimized medical fact checker with faster validation"""
54
 
55
  def __init__(self):
56
  self.medical_facts = self._load_medical_facts()
@@ -67,15 +68,33 @@ class MedicalFactChecker:
67
  ]
68
 
69
  def _load_medical_facts(self) -> Dict[str, Any]:
70
- """Pre-loaded medical facts"""
71
  return {
72
- # ... same as before ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
 
75
  def _load_contraindications(self) -> Dict[str, List[str]]:
76
- """Pre-loaded contraindications"""
77
  return {
78
- # ... same as before ...
 
 
 
79
  }
80
 
81
  def _compile_dosage_patterns(self) -> List[re.Pattern]:
@@ -91,31 +110,38 @@ class MedicalFactChecker:
91
  return [re.compile(pattern, re.IGNORECASE) for pattern in patterns]
92
 
93
  def check_medical_accuracy(self, response: str, context: str) -> Dict[str, Any]:
94
- """Optimized medical accuracy check"""
95
  issues = []
96
  warnings = []
97
  accuracy_score = 0.0
98
 
99
  # Check for contraindications (faster keyword matching)
100
  response_lower = response.lower()
101
- for contra_list in self.contraindications.values():
102
- for contra in contra_list:
103
- if contra.split(" ", 2)[-1].lower() in response_lower:
104
- issues.append(f"Potential contraindication: {contra}")
105
- accuracy_score -= 0.3
106
- break
 
107
 
108
  # Context alignment using Jaccard similarity
109
  if context:
110
  resp_words = set(response_lower.split())
111
  ctx_words = set(context.lower().split())
112
  context_similarity = len(resp_words & ctx_words) / len(resp_words | ctx_words) if ctx_words else 0.0
113
- if context_similarity < 0.7:
114
  warnings.append(f"Low context similarity: {context_similarity:.2f}")
115
- accuracy_score -= 0.2
116
  else:
117
  context_similarity = 0.0
118
 
 
 
 
 
 
 
119
  # Unsupported claims check
120
  for pattern in self.definitive_patterns:
121
  if pattern.search(response):
@@ -137,14 +163,11 @@ class MedicalFactChecker:
137
  "issues": issues,
138
  "warnings": warnings,
139
  "context_similarity": context_similarity,
140
- "is_safe": len(issues) == 0 and confidence_score > 0.6
141
  }
142
 
143
- from llama_index.core import StorageContext # Add this import
144
- from llama_index.vector_stores.faiss import FaissVectorStore
145
- from llama_index.core import Settings # Add this import at the top
146
- class GazaKnowledgeBase:
147
- """Optimized knowledge base for offline operation"""
148
 
149
  def __init__(self, data_dir: str = "./data"):
150
  self.data_dir = Path(data_dir)
@@ -152,33 +175,47 @@ class GazaKnowledgeBase:
152
  self.vector_store = None
153
  self.index = None
154
  self.chunk_metadata = []
155
- self.index_path = self.data_dir / "vector_store"
156
 
157
- # Pre-defined medical priorities
158
  self.medical_priorities = {
159
- "trauma": ["gunshot", "blast", "burns?", "fracture"],
160
- "infectious": ["cholera", "dysentery", "infection"],
161
- "chronic": ["diabetes", "hypertension", "malnutrition"],
162
- "emergency": ["cardiac", "bleeding", "airway"]
 
163
  }
164
 
165
  def initialize(self):
166
- """Faster initialization with persistent storage"""
167
  if not self.index_path.exists():
168
  self.index_path.mkdir(parents=True)
169
 
170
- # Configure local embeddings
171
  device = 'cuda' if torch.cuda.is_available() else 'cpu'
172
- self.embedding_model = HuggingFaceEmbedding(
173
- model_name="sentence-transformers/all-MiniLM-L6-v2",
174
- device=device,
175
- embed_batch_size=4 # Smaller batch for low-resource devices
176
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
  # Configure global settings
179
  Settings.embed_model = self.embedding_model
180
- Settings.chunk_size = 384
181
- Settings.chunk_overlap = 30
182
 
183
  # Check for existing index
184
  if (self.index_path / "index.faiss").exists() and (self.index_path / "docstore.json").exists():
@@ -187,13 +224,13 @@ class GazaKnowledgeBase:
187
  self._create_vector_store()
188
 
189
  def _load_vector_store(self):
190
- """Load existing vector store"""
191
  try:
192
  # Load the FAISS index directly
193
  faiss_index = faiss.read_index(str(self.index_path / "index.faiss"))
194
  vector_store = FaissVectorStore(faiss_index=faiss_index)
195
 
196
- # Create storage context with properly closed parentheses
197
  storage_context = StorageContext.from_defaults(
198
  vector_store=vector_store,
199
  persist_dir=str(self.index_path)
@@ -205,23 +242,51 @@ class GazaKnowledgeBase:
205
  )
206
 
207
  # Load metadata
208
- with open(self.index_path / "metadata.pkl", 'rb') as f:
209
- self.chunk_metadata = pickle.load(f)
 
 
210
 
211
- logger.info("Loaded existing vector store")
212
  except Exception as e:
213
  logger.error(f"Error loading vector store: {e}")
214
  # Fallback to creating new store if loading fails
215
  self._create_vector_store()
216
 
217
-
218
  def _create_vector_store(self):
219
- """Create and persist vector store"""
220
  documents = self._load_documents()
221
 
222
- # Initialize FAISS index
223
- dimension = 384 # all-MiniLM-L6-v2 dimension
224
- faiss_index = faiss.IndexFlatL2(dimension)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  vector_store = FaissVectorStore(faiss_index=faiss_index)
226
 
227
  # Create storage context
@@ -229,30 +294,38 @@ class GazaKnowledgeBase:
229
  vector_store=vector_store
230
  )
231
 
232
- if documents:
233
- # Configure node parser
234
- parser = SentenceSplitter(
235
- chunk_size=Settings.chunk_size,
236
- chunk_overlap=Settings.chunk_overlap,
237
- include_prev_next_rel=False
238
- )
239
-
240
- # Create index using global settings
241
- self.index = VectorStoreIndex.from_documents(
242
- documents,
243
- storage_context=storage_context,
244
- transformations=[parser],
245
- show_progress=True
246
- )
247
-
248
- # Save metadata
249
- self.chunk_metadata = [
250
- {"text": node.text, "source": node.metadata.get("source", "unknown")}
251
- for node in self.index.docstore.docs.values()
252
- ]
253
- else:
254
- logger.warning("No documents found. Creating empty index")
255
- self.chunk_metadata = []
 
 
 
 
 
 
 
 
256
 
257
  # Persist the index
258
  self.index.storage_context.persist(persist_dir=str(self.index_path))
@@ -261,11 +334,10 @@ class GazaKnowledgeBase:
261
  with open(self.index_path / "metadata.pkl", 'wb') as f:
262
  pickle.dump(self.chunk_metadata, f)
263
 
264
- logger.info(f"Created vector store with {len(self.chunk_metadata)} chunks")
265
 
266
-
267
  def _load_documents(self) -> List[Document]:
268
- """Efficient document loading with caching"""
269
  documents = []
270
  doc_cache = self.index_path / "document_cache.pkl"
271
 
@@ -273,333 +345,1062 @@ class GazaKnowledgeBase:
273
  if doc_cache.exists():
274
  try:
275
  with open(doc_cache, 'rb') as f:
276
- cached_docs = pickle.load(f)
277
- if isinstance(cached_docs, list) and all(isinstance(d, Document) for d in cached_docs):
278
- return cached_docs
 
 
 
279
  logger.warning("Document cache format invalid")
280
  except Exception as e:
281
  logger.warning(f"Document cache corrupted: {e}")
282
 
283
- # Process files
 
284
  for pdf_file in self.data_dir.glob("*.pdf"):
285
  try:
286
  doc_text = self._extract_pdf_text(pdf_file)
287
- if doc_text:
288
  documents.append(Document(
289
  text=doc_text,
290
- metadata={"source": str(pdf_file), "type": "pdf"}
 
 
 
 
 
291
  ))
 
 
292
  except Exception as e:
293
  logger.error(f"Error loading {pdf_file}: {e}")
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  # Save to cache if we found documents
296
  if documents:
 
 
 
 
 
297
  with open(doc_cache, 'wb') as f:
298
- pickle.dump(documents, f)
 
299
 
300
  return documents
301
 
302
  def _extract_pdf_text(self, pdf_path: Path) -> str:
303
- """Extract text from PDF file with error handling"""
304
  try:
305
  with open(pdf_path, 'rb') as file:
306
  pdf_reader = PyPDF2.PdfReader(file)
307
  text = []
308
- for page in pdf_reader.pages:
309
- page_text = page.extract_text()
310
- if page_text:
311
- text.append(page_text)
312
- return "\n".join(text) if text else ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  except Exception as e:
314
  logger.error(f"Error extracting text from {pdf_path}: {e}")
315
  return ""
316
 
317
- def search(self, query: str, k: int = 3) -> List[Dict[str, Any]]:
318
- """Search the knowledge base with error handling"""
319
  if not self.index:
 
320
  return []
321
 
322
  try:
323
  retriever = self.index.as_retriever(similarity_top_k=k)
324
  results = retriever.retrieve(query)
325
 
326
- return [{
327
- "text": node.text,
328
- "source": node.metadata.get("source", "unknown"),
329
- "score": score,
330
- "medical_priority": self._assess_priority(node.text)
331
- } for node, score in results]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  except Exception as e:
333
  logger.error(f"Error during search: {e}")
334
  return []
335
 
336
  def _assess_priority(self, text: str) -> str:
337
- """Assess medical priority of text content"""
338
  text_lower = text.lower()
339
- for priority, keywords in self.medical_priorities.items():
 
 
 
 
 
340
  if any(re.search(keyword, text_lower) for keyword in keywords):
341
  return priority
 
342
  return "general"
343
- class GazaRAGSystem:
344
- """Optimized RAG system for low-resource environments"""
 
345
 
346
  def __init__(self):
347
- self.knowledge_base = GazaKnowledgeBase()
348
  self.fact_checker = MedicalFactChecker()
349
  self.llm = None
350
  self.tokenizer = None
351
  self.system_prompt = self._create_system_prompt()
352
  self.generation_pipeline = None
353
-
 
 
354
  def initialize(self):
355
- """Fast initialization with lazy loading"""
356
- logger.info("Initializing Gaza RAG System...")
357
- self.knowledge_base.initialize()
 
 
 
 
 
 
358
 
359
  # Lazy LLM loading - will load on first request
360
  logger.info("RAG system ready (LLM will load on first request)")
361
 
362
  def _initialize_llm(self):
363
- """Initialize LLM only when needed"""
364
  if self.llm is not None:
365
  return
366
 
367
  model_name = "microsoft/Phi-3-mini-4k-instruct"
368
  try:
 
 
 
369
  quantization_config = BitsAndBytesConfig(
370
  load_in_4bit=True,
371
  bnb_4bit_compute_dtype=torch.float16,
372
  bnb_4bit_use_double_quant=True,
373
- bnb_4bit_quant_type="nf4"
 
374
  )
375
 
376
  self.tokenizer = AutoTokenizer.from_pretrained(
377
  model_name,
378
- trust_remote_code=True
 
379
  )
380
 
 
 
 
 
381
  self.llm = AutoModelForCausalLM.from_pretrained(
382
  model_name,
383
  quantization_config=quantization_config,
384
  device_map="auto",
385
  trust_remote_code=True,
386
- torch_dtype=torch.float16
 
387
  )
388
 
389
- # Create pipeline for efficient generation
390
  self.generation_pipeline = pipeline(
391
  "text-generation",
392
  model=self.llm,
393
  tokenizer=self.tokenizer,
394
- device=self.llm.device,
395
- torch_dtype=torch.float16
 
396
  )
397
 
 
 
398
  except Exception as e:
399
- logger.error(f"Error loading model: {e}")
400
  self._initialize_fallback_llm()
401
 
402
  def _initialize_fallback_llm(self):
403
- """Lightweight fallback model"""
404
  try:
405
- self.tokenizer = AutoTokenizer.from_pretrained("microsoft/DialoGPT-small")
 
 
 
406
  self.llm = AutoModelForCausalLM.from_pretrained(
407
- "microsoft/DialoGPT-small",
408
- torch_dtype=torch.float32
 
409
  )
 
 
 
 
410
  self.generation_pipeline = pipeline(
411
  "text-generation",
412
  model=self.llm,
413
- tokenizer=self.tokenizer
 
414
  )
 
 
 
415
  except Exception as e:
416
  logger.error(f"Fallback model failed: {e}")
417
  self.llm = None
 
418
 
419
  def _create_system_prompt(self) -> str:
420
- """Compressed system prompt"""
421
- return ("You are a medical AI assistant for Gaza healthcare workers. "
422
- "Provide accurate, resource-aware first aid guidance. "
423
- "Prioritize patient safety. "
424
- "IMPORTANT: Never diagnose; recommend professional help for serious conditions.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
 
426
- def generate_response(self, query: str) -> Dict[str, Any]:
427
- """Efficient response generation"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
428
  try:
 
 
 
429
  # Initialize LLM only when needed
430
  if self.llm is None:
431
- self._initialize_llm()
 
 
 
 
 
432
 
433
- # Fast knowledge retrieval
434
- search_results = self.knowledge_base.search(query, k=2)
 
 
 
 
 
 
435
  context = self._prepare_context(search_results)
436
 
 
 
 
437
  # Generate response
438
- response = self._generate_response(query, context)
 
 
 
 
 
439
 
440
- # Fast safety check
441
  safety_check = self.fact_checker.check_medical_accuracy(response, context)
442
 
443
- return self._prepare_final_response(
 
444
  response,
445
  search_results,
446
- safety_check
 
447
  )
 
 
 
 
 
 
 
 
 
 
448
  except Exception as e:
449
- logger.error(f"Error: {e}")
450
- return self._create_error_response()
 
 
 
 
 
 
 
 
 
 
 
451
 
452
  def _prepare_context(self, search_results: List[Dict[str, Any]]) -> str:
453
- """Prepare concise context"""
454
- return "\n".join(
455
- f"[Source: {res['source']}]\n{res['text'][:300]}..."
456
- for res in search_results
457
- ) if search_results else ""
 
 
 
 
 
 
 
 
 
 
 
 
458
 
459
  def _generate_response(self, query: str, context: str) -> str:
460
- """Efficient generation with pipeline"""
461
  if not self.generation_pipeline:
462
  return self._generate_fallback_response(query, context)
463
 
464
- prompt = f"{self.system_prompt}\n\nContext:\n{context}\n\nQuestion: {query}\nAnswer:"
 
 
 
 
 
 
 
 
465
 
466
  try:
 
467
  response = self.generation_pipeline(
468
  prompt,
469
- max_new_tokens=256,
470
- temperature=0.3,
471
  do_sample=True,
472
  pad_token_id=self.tokenizer.eos_token_id,
473
- repetition_penalty=1.1,
474
- truncation=True
475
- )[0]['generated_text']
 
476
 
477
- return response[len(prompt):].strip()
478
- except:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  return self._generate_fallback_response(query, context)
480
 
481
  def _generate_fallback_response(self, query: str, context: str) -> str:
482
- """Static fallback response"""
483
- return f"Medical guidance for: {query}\n\nConsult WHO/ICRC guidelines. In emergencies, seek immediate help.\n\nContext:\n{context[:200]}"
 
 
 
 
 
 
 
 
 
 
 
 
 
484
 
485
  def _prepare_final_response(
486
  self,
487
  response: str,
488
  search_results: List[Dict[str, Any]],
489
- safety_check: Dict[str, Any]
 
490
  ) -> Dict[str, Any]:
491
- """Add safety info efficiently"""
 
 
492
  if not safety_check["is_safe"]:
493
- response = f"⚠️ VERIFY: {response}"
 
 
 
 
 
 
494
 
495
- sources = list(set(res["source"] for res in search_results)) if search_results else []
 
 
 
 
 
496
 
497
  return {
498
  "response": response,
499
- "confidence": safety_check["confidence_score"],
500
  "sources": sources,
501
- "timestamp": datetime.now().isoformat()[:19] # Shorter timestamp
 
 
 
 
 
502
  }
503
 
504
- def _create_error_response(self) -> Dict[str, Any]:
505
- """Efficient error response"""
506
  return {
507
- "response": "System error. Please consult medical professionals directly.",
508
  "confidence": 0.0,
509
  "sources": [],
510
- "timestamp": datetime.now().isoformat()[:19]
 
 
 
 
 
 
511
  }
512
 
513
  # Global system instance
514
- rag_system = None
515
 
516
- def initialize_system():
517
- """Initialize system with minimal resources"""
518
- global rag_system
519
- if rag_system is None:
520
- rag_system = GazaRAGSystem()
521
- rag_system.initialize()
522
- return rag_system
 
 
 
 
 
523
 
524
- def process_medical_query(query: str) -> str:
525
- """Efficient query processing"""
526
  if not query.strip():
527
- return "Please enter a medical question."
528
 
529
  try:
530
- system = initialize_system()
531
- result = system.generate_response(query)
532
- return result["response"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  except Exception as e:
534
- return f"Error: {str(e)}"
 
 
 
 
535
 
536
- def create_gradio_interface():
537
- """Create optimized Gradio interface"""
 
 
538
  css = """
539
- .compact-input { padding: 8px !important; font-size: 14px !important; }
540
- .compact-output { padding: 10px !important; font-size: 14px !important; }
541
- .emergency-notice { background-color: #ffdddd; padding: 10px; border-radius: 5px; margin: 10px 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
542
  """
543
 
544
- with gr.Blocks(css=css, title="Gaza First Aid Assistant") as interface:
545
- gr.Markdown("## πŸš‘ Gaza First Aid Assistant")
 
 
 
 
 
 
 
546
 
547
- with gr.Row():
548
- query_input = gr.Textbox(
549
- label="Medical Question",
550
- placeholder="Enter first aid question...",
551
- elem_classes=["compact-input"]
552
- )
553
- submit_btn = gr.Button("Get Guidance", variant="primary")
 
 
 
 
 
 
 
 
554
 
555
- with gr.Row():
556
- response_output = gr.Textbox(
557
- label="Medical Guidance",
558
- elem_classes=["compact-output"],
559
- lines=8
560
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
 
562
- gr.Markdown("""
563
- <div class="emergency-notice">
564
- 🚨 For life-threatening emergencies, seek immediate professional medical attention.
565
- </div>
566
- """)
567
-
568
- # Examples with common Gaza medical scenarios
569
- gr.Examples(
570
- examples=[
571
- "Treating shrapnel wounds with limited supplies",
572
- "Emergency burn treatment without clean water",
573
- "Recognizing signs of infection in wounds",
574
- "Managing diabetic emergencies without insulin",
575
- "Stopping severe bleeding with improvised materials"
576
- ],
577
- inputs=query_input
578
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  submit_btn.click(
581
- process_medical_query,
582
  inputs=query_input,
583
- outputs=response_output
 
584
  )
 
585
  query_input.submit(
586
- process_medical_query,
587
  inputs=query_input,
588
- outputs=response_output
 
 
 
 
 
 
589
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
 
591
  return interface
592
 
593
  def main():
594
- """Entry point with resource monitoring"""
595
- logger.info("Starting optimized Gaza First Aid Assistant")
596
- interface = create_gradio_interface()
597
- interface.launch(
598
- server_name="0.0.0.0",
599
- server_port=7860,
600
- share=False,
601
- max_threads=2 # Reduce resource usage
602
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
 
604
  if __name__ == "__main__":
605
- main()
 
 
4
  import logging
5
  import warnings
6
  from pathlib import Path
7
+ from typing import List, Dict, Any, Optional, Tuple
8
  import hashlib
9
  import pickle
10
  from datetime import datetime
11
+ import time
12
+ import asyncio
13
+ from concurrent.futures import ThreadPoolExecutor
14
 
15
  # Suppress warnings for cleaner output
16
  warnings.filterwarnings("ignore")
 
25
  from transformers import (
26
  AutoTokenizer,
27
  AutoModelForCausalLM,
28
+ BitsAndBytesConfig,
29
+ pipeline
30
  )
31
 
 
32
  # Document processing
33
+ from llama_index.core import Document, VectorStoreIndex, Settings
34
  from llama_index.core.node_parser import SentenceSplitter
35
  from llama_index.vector_stores.faiss import FaissVectorStore
36
  from llama_index.embeddings.huggingface import HuggingFaceEmbedding
37
+ from llama_index.core import StorageContext
38
 
39
  # PDF processing
40
  import PyPDF2
 
43
  # Medical knowledge validation
44
  import re
45
 
 
 
 
46
  # Configure logging
47
  logging.basicConfig(
48
  level=logging.INFO,
 
51
  logger = logging.getLogger(__name__)
52
 
53
  class MedicalFactChecker:
54
+ """Enhanced medical fact checker with faster validation"""
55
 
56
  def __init__(self):
57
  self.medical_facts = self._load_medical_facts()
 
68
  ]
69
 
70
  def _load_medical_facts(self) -> Dict[str, Any]:
71
+ """Pre-loaded medical facts for Gaza context"""
72
  return {
73
+ "burn_treatment": {
74
+ "cool_water": "Use clean, cool (not ice-cold) water for 10-20 minutes",
75
+ "no_ice": "Never apply ice directly to burns",
76
+ "clean_cloth": "Cover with clean, dry cloth if available"
77
+ },
78
+ "wound_care": {
79
+ "pressure": "Apply direct pressure to control bleeding",
80
+ "elevation": "Elevate injured limb if possible",
81
+ "clean_hands": "Clean hands before treating wounds when possible"
82
+ },
83
+ "infection_signs": {
84
+ "redness": "Increasing redness around wound",
85
+ "warmth": "Increased warmth at wound site",
86
+ "pus": "Yellow or green discharge",
87
+ "fever": "Fever may indicate systemic infection"
88
+ }
89
  }
90
 
91
  def _load_contraindications(self) -> Dict[str, List[str]]:
92
+ """Pre-loaded contraindications for common treatments"""
93
  return {
94
+ "aspirin": ["children under 16", "bleeding disorders", "stomach ulcers"],
95
+ "ibuprofen": ["kidney disease", "heart failure", "stomach bleeding"],
96
+ "hydrogen_peroxide": ["deep wounds", "closed wounds", "eyes"],
97
+ "tourniquets": ["non-life-threatening bleeding", "without proper training"]
98
  }
99
 
100
  def _compile_dosage_patterns(self) -> List[re.Pattern]:
 
110
  return [re.compile(pattern, re.IGNORECASE) for pattern in patterns]
111
 
112
  def check_medical_accuracy(self, response: str, context: str) -> Dict[str, Any]:
113
+ """Enhanced medical accuracy check with Gaza-specific considerations"""
114
  issues = []
115
  warnings = []
116
  accuracy_score = 0.0
117
 
118
  # Check for contraindications (faster keyword matching)
119
  response_lower = response.lower()
120
+ for medication, contra_list in self.contraindications.items():
121
+ if medication in response_lower:
122
+ for contra in contra_list:
123
+ if any(word in response_lower for word in contra.split()):
124
+ issues.append(f"Potential contraindication: {medication} with {contra}")
125
+ accuracy_score -= 0.3
126
+ break
127
 
128
  # Context alignment using Jaccard similarity
129
  if context:
130
  resp_words = set(response_lower.split())
131
  ctx_words = set(context.lower().split())
132
  context_similarity = len(resp_words & ctx_words) / len(resp_words | ctx_words) if ctx_words else 0.0
133
+ if context_similarity < 0.5: # Lowered threshold for Gaza context
134
  warnings.append(f"Low context similarity: {context_similarity:.2f}")
135
+ accuracy_score -= 0.1
136
  else:
137
  context_similarity = 0.0
138
 
139
+ # Gaza-specific resource checks
140
+ gaza_resources = ["clean water", "sterile", "hospital", "ambulance", "electricity"]
141
+ if any(resource in response_lower for resource in gaza_resources):
142
+ warnings.append("Consider resource limitations in Gaza context")
143
+ accuracy_score -= 0.05
144
+
145
  # Unsupported claims check
146
  for pattern in self.definitive_patterns:
147
  if pattern.search(response):
 
163
  "issues": issues,
164
  "warnings": warnings,
165
  "context_similarity": context_similarity,
166
+ "is_safe": len(issues) == 0 and confidence_score > 0.5
167
  }
168
 
169
+ class EnhancedGazaKnowledgeBase:
170
+ """Enhanced knowledge base with better embeddings and indexing"""
 
 
 
171
 
172
  def __init__(self, data_dir: str = "./data"):
173
  self.data_dir = Path(data_dir)
 
175
  self.vector_store = None
176
  self.index = None
177
  self.chunk_metadata = []
178
+ self.index_path = self.data_dir / "enhanced_vector_store"
179
 
180
+ # Enhanced medical priorities for Gaza context
181
  self.medical_priorities = {
182
+ "trauma": ["gunshot", "blast", "burns?", "fracture", "shrapnel", "explosion"],
183
+ "infectious": ["cholera", "dysentery", "infection", "sepsis", "wound infection"],
184
+ "chronic": ["diabetes", "hypertension", "malnutrition", "kidney", "heart"],
185
+ "emergency": ["cardiac", "bleeding", "airway", "unconscious", "shock"],
186
+ "gaza_specific": ["siege", "blockade", "limited supplies", "no electricity", "water shortage"]
187
  }
188
 
189
  def initialize(self):
190
+ """Enhanced initialization with better embedding model"""
191
  if not self.index_path.exists():
192
  self.index_path.mkdir(parents=True)
193
 
194
+ # Use a more powerful medical embedding model
195
  device = 'cuda' if torch.cuda.is_available() else 'cpu'
196
+
197
+ # Try to use a medical-specific embedding model, fallback to general model
198
+ try:
199
+ # First try a medical-specific model (if available)
200
+ self.embedding_model = HuggingFaceEmbedding(
201
+ model_name="sentence-transformers/all-mpnet-base-v2", # Higher dimension (768)
202
+ device=device,
203
+ embed_batch_size=4
204
+ )
205
+ logger.info("Using all-mpnet-base-v2 (768-dim) embedding model")
206
+ except Exception as e:
207
+ logger.warning(f"Failed to load preferred model, using fallback: {e}")
208
+ self.embedding_model = HuggingFaceEmbedding(
209
+ model_name="sentence-transformers/all-MiniLM-L6-v2",
210
+ device=device,
211
+ embed_batch_size=4
212
+ )
213
+ logger.info("Using all-MiniLM-L6-v2 (384-dim) embedding model")
214
 
215
  # Configure global settings
216
  Settings.embed_model = self.embedding_model
217
+ Settings.chunk_size = 512 # Increased chunk size for better context
218
+ Settings.chunk_overlap = 50 # Increased overlap
219
 
220
  # Check for existing index
221
  if (self.index_path / "index.faiss").exists() and (self.index_path / "docstore.json").exists():
 
224
  self._create_vector_store()
225
 
226
  def _load_vector_store(self):
227
+ """Load existing vector store with error handling"""
228
  try:
229
  # Load the FAISS index directly
230
  faiss_index = faiss.read_index(str(self.index_path / "index.faiss"))
231
  vector_store = FaissVectorStore(faiss_index=faiss_index)
232
 
233
+ # Create storage context
234
  storage_context = StorageContext.from_defaults(
235
  vector_store=vector_store,
236
  persist_dir=str(self.index_path)
 
242
  )
243
 
244
  # Load metadata
245
+ metadata_path = self.index_path / "metadata.pkl"
246
+ if metadata_path.exists():
247
+ with open(metadata_path, 'rb') as f:
248
+ self.chunk_metadata = pickle.load(f)
249
 
250
+ logger.info(f"Loaded existing vector store with {len(self.chunk_metadata)} chunks")
251
  except Exception as e:
252
  logger.error(f"Error loading vector store: {e}")
253
  # Fallback to creating new store if loading fails
254
  self._create_vector_store()
255
 
 
256
  def _create_vector_store(self):
257
+ """Create enhanced vector store with IVF indexing"""
258
  documents = self._load_documents()
259
 
260
+ if not documents:
261
+ logger.warning("No documents found. Creating empty index")
262
+ self.chunk_metadata = []
263
+ return
264
+
265
+ # Determine embedding dimension
266
+ try:
267
+ test_embedding = self.embedding_model.get_text_embedding("test")
268
+ dimension = len(test_embedding)
269
+ logger.info(f"Embedding dimension: {dimension}")
270
+ except Exception as e:
271
+ logger.error(f"Failed to determine embedding dimension: {e}")
272
+ dimension = 768 # Default for all-mpnet-base-v2
273
+
274
+ # Create enhanced FAISS index with IVF for better performance
275
+ try:
276
+ # For small datasets, use flat index; for larger ones, use IVF
277
+ if len(documents) < 1000:
278
+ faiss_index = faiss.IndexFlatL2(dimension)
279
+ logger.info("Using IndexFlatL2 for small dataset")
280
+ else:
281
+ # Use IVF with reasonable number of clusters
282
+ nlist = min(100, len(documents) // 10) # Adaptive cluster count
283
+ quantizer = faiss.IndexFlatL2(dimension)
284
+ faiss_index = faiss.IndexIVFFlat(quantizer, dimension, nlist)
285
+ logger.info(f"Using IndexIVFFlat with {nlist} clusters")
286
+ except Exception as e:
287
+ logger.error(f"Failed to create enhanced index, using flat: {e}")
288
+ faiss_index = faiss.IndexFlatL2(dimension)
289
+
290
  vector_store = FaissVectorStore(faiss_index=faiss_index)
291
 
292
  # Create storage context
 
294
  vector_store=vector_store
295
  )
296
 
297
+ # Configure node parser with enhanced settings
298
+ parser = SentenceSplitter(
299
+ chunk_size=Settings.chunk_size,
300
+ chunk_overlap=Settings.chunk_overlap,
301
+ include_prev_next_rel=True # Include relationships for better context
302
+ )
303
+
304
+ # Create index using global settings
305
+ self.index = VectorStoreIndex.from_documents(
306
+ documents,
307
+ storage_context=storage_context,
308
+ transformations=[parser],
309
+ show_progress=True
310
+ )
311
+
312
+ # Train IVF index if needed
313
+ if hasattr(faiss_index, 'train') and not faiss_index.is_trained:
314
+ logger.info("Training IVF index...")
315
+ # Get some embeddings for training
316
+ sample_texts = [doc.text[:500] for doc in documents[:100]] # Sample for training
317
+ sample_embeddings = np.array([
318
+ self.embedding_model.get_text_embedding(text)
319
+ for text in sample_texts
320
+ ]).astype('float32')
321
+ faiss_index.train(sample_embeddings)
322
+ logger.info("IVF index training completed")
323
+
324
+ # Save metadata
325
+ self.chunk_metadata = [
326
+ {"text": node.text, "source": node.metadata.get("source", "unknown")}
327
+ for node in self.index.docstore.docs.values()
328
+ ]
329
 
330
  # Persist the index
331
  self.index.storage_context.persist(persist_dir=str(self.index_path))
 
334
  with open(self.index_path / "metadata.pkl", 'wb') as f:
335
  pickle.dump(self.chunk_metadata, f)
336
 
337
+ logger.info(f"Created enhanced vector store with {len(self.chunk_metadata)} chunks")
338
 
 
339
  def _load_documents(self) -> List[Document]:
340
+ """Enhanced document loading with better caching"""
341
  documents = []
342
  doc_cache = self.index_path / "document_cache.pkl"
343
 
 
345
  if doc_cache.exists():
346
  try:
347
  with open(doc_cache, 'rb') as f:
348
+ cached_data = pickle.load(f)
349
+ if isinstance(cached_data, dict) and 'documents' in cached_data:
350
+ cached_docs = cached_data['documents']
351
+ if isinstance(cached_docs, list) and all(isinstance(d, Document) for d in cached_docs):
352
+ logger.info(f"Loaded {len(cached_docs)} documents from cache")
353
+ return cached_docs
354
  logger.warning("Document cache format invalid")
355
  except Exception as e:
356
  logger.warning(f"Document cache corrupted: {e}")
357
 
358
+ # Process files with enhanced error handling
359
+ processed_files = []
360
  for pdf_file in self.data_dir.glob("*.pdf"):
361
  try:
362
  doc_text = self._extract_pdf_text(pdf_file)
363
+ if doc_text and len(doc_text.strip()) > 100: # Minimum content check
364
  documents.append(Document(
365
  text=doc_text,
366
+ metadata={
367
+ "source": str(pdf_file.name),
368
+ "type": "pdf",
369
+ "file_size": pdf_file.stat().st_size,
370
+ "processed_date": datetime.now().isoformat()
371
+ }
372
  ))
373
+ processed_files.append(str(pdf_file.name))
374
+ logger.info(f"Processed: {pdf_file.name} ({len(doc_text)} chars)")
375
  except Exception as e:
376
  logger.error(f"Error loading {pdf_file}: {e}")
377
 
378
+ # Process text files as well
379
+ for txt_file in self.data_dir.glob("*.txt"):
380
+ try:
381
+ with open(txt_file, 'r', encoding='utf-8') as f:
382
+ doc_text = f.read()
383
+ if doc_text and len(doc_text.strip()) > 100:
384
+ documents.append(Document(
385
+ text=doc_text,
386
+ metadata={
387
+ "source": str(txt_file.name),
388
+ "type": "txt",
389
+ "file_size": txt_file.stat().st_size,
390
+ "processed_date": datetime.now().isoformat()
391
+ }
392
+ ))
393
+ processed_files.append(str(txt_file.name))
394
+ logger.info(f"Processed: {txt_file.name} ({len(doc_text)} chars)")
395
+ except Exception as e:
396
+ logger.error(f"Error loading {txt_file}: {e}")
397
+
398
  # Save to cache if we found documents
399
  if documents:
400
+ cache_data = {
401
+ 'documents': documents,
402
+ 'processed_files': processed_files,
403
+ 'cache_date': datetime.now().isoformat()
404
+ }
405
  with open(doc_cache, 'wb') as f:
406
+ pickle.dump(cache_data, f)
407
+ logger.info(f"Cached {len(documents)} documents")
408
 
409
  return documents
410
 
411
  def _extract_pdf_text(self, pdf_path: Path) -> str:
412
+ """Enhanced PDF text extraction with better error handling"""
413
  try:
414
  with open(pdf_path, 'rb') as file:
415
  pdf_reader = PyPDF2.PdfReader(file)
416
  text = []
417
+
418
+ for page_num, page in enumerate(pdf_reader.pages):
419
+ try:
420
+ page_text = page.extract_text()
421
+ if page_text and page_text.strip():
422
+ # Clean up the text
423
+ page_text = re.sub(r'\s+', ' ', page_text) # Normalize whitespace
424
+ text.append(page_text)
425
+ except Exception as e:
426
+ logger.warning(f"Error extracting page {page_num} from {pdf_path}: {e}")
427
+ continue
428
+
429
+ full_text = "\n".join(text) if text else ""
430
+
431
+ # Additional validation
432
+ if len(full_text.strip()) < 100:
433
+ logger.warning(f"Extracted text too short from {pdf_path}")
434
+ return ""
435
+
436
+ return full_text
437
+
438
  except Exception as e:
439
  logger.error(f"Error extracting text from {pdf_path}: {e}")
440
  return ""
441
 
442
+ def search(self, query: str, k: int = 5) -> List[Dict[str, Any]]:
443
+ """Enhanced search with better error handling and result processing"""
444
  if not self.index:
445
+ logger.warning("Index not available for search")
446
  return []
447
 
448
  try:
449
  retriever = self.index.as_retriever(similarity_top_k=k)
450
  results = retriever.retrieve(query)
451
 
452
+ # FIX: Handle the tuple object error by properly extracting node and score
453
+ processed_results = []
454
+ for result in results:
455
+ try:
456
+ # Handle both tuple and direct node results
457
+ if isinstance(result, tuple):
458
+ node, score = result
459
+ else:
460
+ node = result
461
+ score = getattr(result, 'score', 0.0)
462
+
463
+ # Extract text safely
464
+ text = getattr(node, 'text', str(node))
465
+ source = node.metadata.get("source", "unknown") if hasattr(node, 'metadata') else "unknown"
466
+
467
+ processed_results.append({
468
+ "text": text,
469
+ "source": source,
470
+ "score": float(score) if score is not None else 0.0,
471
+ "medical_priority": self._assess_priority(text)
472
+ })
473
+ except Exception as e:
474
+ logger.error(f"Error processing search result: {e}")
475
+ continue
476
+
477
+ # Sort by score (higher is better)
478
+ processed_results.sort(key=lambda x: x['score'], reverse=True)
479
+
480
+ logger.info(f"Search returned {len(processed_results)} results for query: {query[:50]}...")
481
+ return processed_results
482
+
483
  except Exception as e:
484
  logger.error(f"Error during search: {e}")
485
  return []
486
 
487
  def _assess_priority(self, text: str) -> str:
488
+ """Enhanced medical priority assessment"""
489
  text_lower = text.lower()
490
+
491
+ # Check priorities in order of importance
492
+ priority_order = ["emergency", "trauma", "gaza_specific", "infectious", "chronic"]
493
+
494
+ for priority in priority_order:
495
+ keywords = self.medical_priorities.get(priority, [])
496
  if any(re.search(keyword, text_lower) for keyword in keywords):
497
  return priority
498
+
499
  return "general"
500
+
501
+ class EnhancedGazaRAGSystem:
502
+ """Enhanced RAG system with better performance and error handling"""
503
 
504
  def __init__(self):
505
+ self.knowledge_base = EnhancedGazaKnowledgeBase()
506
  self.fact_checker = MedicalFactChecker()
507
  self.llm = None
508
  self.tokenizer = None
509
  self.system_prompt = self._create_system_prompt()
510
  self.generation_pipeline = None
511
+ self.response_cache = {} # Simple response caching
512
+ self.executor = ThreadPoolExecutor(max_workers=2) # For async processing
513
+
514
  def initialize(self):
515
+ """Enhanced initialization with better error handling"""
516
+ logger.info("Initializing Enhanced Gaza RAG System...")
517
+
518
+ try:
519
+ self.knowledge_base.initialize()
520
+ logger.info("Knowledge base initialized successfully")
521
+ except Exception as e:
522
+ logger.error(f"Failed to initialize knowledge base: {e}")
523
+ raise
524
 
525
  # Lazy LLM loading - will load on first request
526
  logger.info("RAG system ready (LLM will load on first request)")
527
 
528
  def _initialize_llm(self):
529
+ """Enhanced LLM initialization with better error handling"""
530
  if self.llm is not None:
531
  return
532
 
533
  model_name = "microsoft/Phi-3-mini-4k-instruct"
534
  try:
535
+ logger.info(f"Loading LLM: {model_name}")
536
+
537
+ # Enhanced quantization configuration
538
  quantization_config = BitsAndBytesConfig(
539
  load_in_4bit=True,
540
  bnb_4bit_compute_dtype=torch.float16,
541
  bnb_4bit_use_double_quant=True,
542
+ bnb_4bit_quant_type="nf4",
543
+ bnb_4bit_quant_storage=torch.uint8
544
  )
545
 
546
  self.tokenizer = AutoTokenizer.from_pretrained(
547
  model_name,
548
+ trust_remote_code=True,
549
+ padding_side="left" # Better for generation
550
  )
551
 
552
+ # Add pad token if missing
553
+ if self.tokenizer.pad_token is None:
554
+ self.tokenizer.pad_token = self.tokenizer.eos_token
555
+
556
  self.llm = AutoModelForCausalLM.from_pretrained(
557
  model_name,
558
  quantization_config=quantization_config,
559
  device_map="auto",
560
  trust_remote_code=True,
561
+ torch_dtype=torch.float16,
562
+ low_cpu_mem_usage=True
563
  )
564
 
565
+ # Create enhanced pipeline
566
  self.generation_pipeline = pipeline(
567
  "text-generation",
568
  model=self.llm,
569
  tokenizer=self.tokenizer,
570
+ device_map="auto",
571
+ torch_dtype=torch.float16,
572
+ return_full_text=False # Only return generated text
573
  )
574
 
575
+ logger.info("LLM loaded successfully")
576
+
577
  except Exception as e:
578
+ logger.error(f"Error loading primary model: {e}")
579
  self._initialize_fallback_llm()
580
 
581
  def _initialize_fallback_llm(self):
582
+ """Enhanced fallback model with better error handling"""
583
  try:
584
+ logger.info("Loading fallback model...")
585
+
586
+ fallback_model = "microsoft/DialoGPT-small"
587
+ self.tokenizer = AutoTokenizer.from_pretrained(fallback_model)
588
  self.llm = AutoModelForCausalLM.from_pretrained(
589
+ fallback_model,
590
+ torch_dtype=torch.float32,
591
+ low_cpu_mem_usage=True
592
  )
593
+
594
+ if self.tokenizer.pad_token is None:
595
+ self.tokenizer.pad_token = self.tokenizer.eos_token
596
+
597
  self.generation_pipeline = pipeline(
598
  "text-generation",
599
  model=self.llm,
600
+ tokenizer=self.tokenizer,
601
+ return_full_text=False
602
  )
603
+
604
+ logger.info("Fallback model loaded successfully")
605
+
606
  except Exception as e:
607
  logger.error(f"Fallback model failed: {e}")
608
  self.llm = None
609
+ self.generation_pipeline = None
610
 
611
  def _create_system_prompt(self) -> str:
612
+ """Enhanced system prompt for Gaza context"""
613
+ return """You are a medical AI assistant specifically designed for Gaza healthcare workers operating under siege conditions.
614
+
615
+ CRITICAL GUIDELINES:
616
+ - Provide practical first aid guidance considering limited resources (water, electricity, medical supplies)
617
+ - Always prioritize patient safety and recommend professional medical help when available
618
+ - Consider Gaza's specific challenges: blockade, limited hospitals, frequent power outages
619
+ - Suggest alternative treatments when standard medical supplies are unavailable
620
+ - Never provide definitive diagnoses - only supportive care guidance
621
+ - Be culturally sensitive and aware of the humanitarian crisis context
622
+
623
+ RESOURCE CONSTRAINTS TO CONSIDER:
624
+ - Limited clean water availability
625
+ - Frequent electricity outages
626
+ - Restricted medical supply access
627
+ - Overwhelmed healthcare facilities
628
+ - Limited transportation for medical emergencies
629
+
630
+ Provide clear, actionable advice while emphasizing the need for professional medical care when possible."""
631
 
632
+ async def generate_response_async(self, query: str, progress_callback=None) -> Dict[str, Any]:
633
+ """Async response generation with progress tracking"""
634
+ start_time = time.time()
635
+
636
+ if progress_callback:
637
+ progress_callback(0.1, "Checking cache...")
638
+
639
+ # Check cache first
640
+ query_hash = hashlib.md5(query.encode()).hexdigest()
641
+ if query_hash in self.response_cache:
642
+ cached_response = self.response_cache[query_hash]
643
+ cached_response["cached"] = True
644
+ cached_response["response_time"] = 0.1
645
+ if progress_callback:
646
+ progress_callback(1.0, "Retrieved from cache!")
647
+ return cached_response
648
+
649
  try:
650
+ if progress_callback:
651
+ progress_callback(0.2, "Initializing LLM...")
652
+
653
  # Initialize LLM only when needed
654
  if self.llm is None:
655
+ await asyncio.get_event_loop().run_in_executor(
656
+ self.executor, self._initialize_llm
657
+ )
658
+
659
+ if progress_callback:
660
+ progress_callback(0.4, "Searching knowledge base...")
661
 
662
+ # Enhanced knowledge retrieval
663
+ search_results = await asyncio.get_event_loop().run_in_executor(
664
+ self.executor, self.knowledge_base.search, query, 3
665
+ )
666
+
667
+ if progress_callback:
668
+ progress_callback(0.6, "Preparing context...")
669
+
670
  context = self._prepare_context(search_results)
671
 
672
+ if progress_callback:
673
+ progress_callback(0.8, "Generating response...")
674
+
675
  # Generate response
676
+ response = await asyncio.get_event_loop().run_in_executor(
677
+ self.executor, self._generate_response, query, context
678
+ )
679
+
680
+ if progress_callback:
681
+ progress_callback(0.9, "Validating safety...")
682
 
683
+ # Enhanced safety check
684
  safety_check = self.fact_checker.check_medical_accuracy(response, context)
685
 
686
+ # Prepare final response
687
+ final_response = self._prepare_final_response(
688
  response,
689
  search_results,
690
+ safety_check,
691
+ time.time() - start_time
692
  )
693
+
694
+ # Cache the response (limit cache size)
695
+ if len(self.response_cache) < 100:
696
+ self.response_cache[query_hash] = final_response
697
+
698
+ if progress_callback:
699
+ progress_callback(1.0, "Complete!")
700
+
701
+ return final_response
702
+
703
  except Exception as e:
704
+ logger.error(f"Error generating response: {e}")
705
+ if progress_callback:
706
+ progress_callback(1.0, f"Error: {str(e)}")
707
+ return self._create_error_response(str(e))
708
+
709
+ def generate_response(self, query: str) -> Dict[str, Any]:
710
+ """Synchronous wrapper for async response generation"""
711
+ loop = asyncio.new_event_loop()
712
+ asyncio.set_event_loop(loop)
713
+ try:
714
+ return loop.run_until_complete(self.generate_response_async(query))
715
+ finally:
716
+ loop.close()
717
 
718
  def _prepare_context(self, search_results: List[Dict[str, Any]]) -> str:
719
+ """Enhanced context preparation with better formatting"""
720
+ if not search_results:
721
+ return "No specific medical guidance found in knowledge base. Provide general first aid principles."
722
+
723
+ context_parts = []
724
+ for i, result in enumerate(search_results, 1):
725
+ source = result.get('source', 'unknown')
726
+ text = result.get('text', '')
727
+ priority = result.get('medical_priority', 'general')
728
+
729
+ # Truncate long text but preserve important information
730
+ if len(text) > 400:
731
+ text = text[:400] + "..."
732
+
733
+ context_parts.append(f"[Source {i}: {source} - Priority: {priority}]\n{text}")
734
+
735
+ return "\n\n".join(context_parts)
736
 
737
  def _generate_response(self, query: str, context: str) -> str:
738
+ """Enhanced response generation with better prompting"""
739
  if not self.generation_pipeline:
740
  return self._generate_fallback_response(query, context)
741
 
742
+ # Enhanced prompt structure
743
+ prompt = f"""{self.system_prompt}
744
+
745
+ MEDICAL KNOWLEDGE CONTEXT:
746
+ {context}
747
+
748
+ PATIENT QUESTION: {query}
749
+
750
+ RESPONSE (provide practical, Gaza-appropriate medical guidance):"""
751
 
752
  try:
753
+ # Enhanced generation parameters
754
  response = self.generation_pipeline(
755
  prompt,
756
+ max_new_tokens=300, # Increased for more detailed responses
757
+ temperature=0.2, # Lower for more consistent medical advice
758
  do_sample=True,
759
  pad_token_id=self.tokenizer.eos_token_id,
760
+ repetition_penalty=1.15,
761
+ truncation=True,
762
+ num_return_sequences=1
763
+ )
764
 
765
+ if response and len(response) > 0:
766
+ generated_text = response[0]['generated_text']
767
+ # Clean up the response
768
+ generated_text = generated_text.strip()
769
+
770
+ # Remove any repetitive patterns
771
+ lines = generated_text.split('\n')
772
+ unique_lines = []
773
+ for line in lines:
774
+ if line.strip() and line.strip() not in unique_lines:
775
+ unique_lines.append(line.strip())
776
+
777
+ return '\n'.join(unique_lines)
778
+ else:
779
+ return self._generate_fallback_response(query, context)
780
+
781
+ except Exception as e:
782
+ logger.error(f"Error in LLM generation: {e}")
783
  return self._generate_fallback_response(query, context)
784
 
785
  def _generate_fallback_response(self, query: str, context: str) -> str:
786
+ """Enhanced fallback response with Gaza-specific guidance"""
787
+ gaza_guidance = {
788
+ "burn": "For burns: Use clean, cool water if available. If water is scarce, use clean cloth. Avoid ice. Seek medical help urgently.",
789
+ "bleeding": "For bleeding: Apply direct pressure with clean cloth. Elevate if possible. If severe, seek immediate medical attention.",
790
+ "wound": "For wounds: Clean hands if possible. Apply pressure to stop bleeding. Cover with clean material. Watch for infection signs.",
791
+ "infection": "Signs of infection: Redness, warmth, swelling, pus, fever. Seek medical care immediately if available.",
792
+ "pain": "For pain management: Rest, elevation, cold/warm compress as appropriate. Avoid aspirin in children."
793
+ }
794
+
795
+ query_lower = query.lower()
796
+ for condition, guidance in gaza_guidance.items():
797
+ if condition in query_lower:
798
+ return f"{guidance}\n\nContext from medical sources:\n{context[:200]}..."
799
+
800
+ return f"Medical guidance for: {query}\n\nGeneral advice: Prioritize safety, seek professional help when available, consider resource limitations in Gaza.\n\nRelevant information:\n{context[:300]}..."
801
 
802
  def _prepare_final_response(
803
  self,
804
  response: str,
805
  search_results: List[Dict[str, Any]],
806
+ safety_check: Dict[str, Any],
807
+ response_time: float
808
  ) -> Dict[str, Any]:
809
+ """Enhanced final response preparation with more metadata"""
810
+
811
+ # Add safety warnings if needed
812
  if not safety_check["is_safe"]:
813
+ response = f"⚠️ MEDICAL CAUTION: {response}\n\n🚨 Please verify this guidance with a medical professional when possible."
814
+
815
+ # Add Gaza-specific disclaimer
816
+ response += "\n\nπŸ“ Gaza Context: This guidance considers resource limitations. Adapt based on available supplies and seek professional medical care when accessible."
817
+
818
+ # Extract unique sources
819
+ sources = list(set(res.get("source", "unknown") for res in search_results)) if search_results else []
820
 
821
+ # Calculate confidence based on multiple factors
822
+ base_confidence = safety_check.get("confidence_score", 0.5)
823
+ context_bonus = 0.1 if search_results else 0.0
824
+ safety_penalty = 0.2 if not safety_check.get("is_safe", True) else 0.0
825
+
826
+ final_confidence = max(0.0, min(1.0, base_confidence + context_bonus - safety_penalty))
827
 
828
  return {
829
  "response": response,
830
+ "confidence": final_confidence,
831
  "sources": sources,
832
+ "search_results_count": len(search_results),
833
+ "safety_issues": safety_check.get("issues", []),
834
+ "safety_warnings": safety_check.get("warnings", []),
835
+ "response_time": round(response_time, 2),
836
+ "timestamp": datetime.now().isoformat()[:19],
837
+ "cached": False
838
  }
839
 
840
+ def _create_error_response(self, error_msg: str) -> Dict[str, Any]:
841
+ """Enhanced error response with helpful information"""
842
  return {
843
+ "response": f"⚠️ System Error: Unable to process your medical query at this time.\n\nError: {error_msg}\n\n🚨 For immediate medical emergencies, seek professional help directly.\n\nπŸ“ž Gaza Emergency Numbers:\n- Palestinian Red Crescent: 101\n- Civil Defense: 102",
844
  "confidence": 0.0,
845
  "sources": [],
846
+ "search_results_count": 0,
847
+ "safety_issues": ["System error occurred"],
848
+ "safety_warnings": ["Unable to validate medical accuracy"],
849
+ "response_time": 0.0,
850
+ "timestamp": datetime.now().isoformat()[:19],
851
+ "cached": False,
852
+ "error": True
853
  }
854
 
855
  # Global system instance
856
+ enhanced_rag_system = None
857
 
858
+ def initialize_enhanced_system():
859
+ """Initialize enhanced system with better error handling"""
860
+ global enhanced_rag_system
861
+ if enhanced_rag_system is None:
862
+ try:
863
+ enhanced_rag_system = EnhancedGazaRAGSystem()
864
+ enhanced_rag_system.initialize()
865
+ logger.info("Enhanced Gaza RAG System initialized successfully")
866
+ except Exception as e:
867
+ logger.error(f"Failed to initialize enhanced system: {e}")
868
+ raise
869
+ return enhanced_rag_system
870
 
871
+ def process_medical_query_with_progress(query: str, progress=gr.Progress()) -> Tuple[str, str, str]:
872
+ """Enhanced query processing with detailed progress tracking and status updates"""
873
  if not query.strip():
874
+ return "Please enter a medical question.", "", "⚠️ No query provided"
875
 
876
  try:
877
+ # Initialize system with progress
878
+ progress(0.05, desc="πŸ”§ Initializing system...")
879
+ system = initialize_enhanced_system()
880
+
881
+ # Create async event loop for progress tracking
882
+ loop = asyncio.new_event_loop()
883
+ asyncio.set_event_loop(loop)
884
+
885
+ def progress_callback(value, desc):
886
+ progress(value, desc=desc)
887
+
888
+ try:
889
+ # Run async generation with progress
890
+ result = loop.run_until_complete(
891
+ system.generate_response_async(query, progress_callback)
892
+ )
893
+ finally:
894
+ loop.close()
895
+
896
+ # Prepare response with enhanced metadata
897
+ response = result["response"]
898
+
899
+ # Prepare detailed metadata
900
+ metadata_parts = [
901
+ f"🎯 Confidence: {result['confidence']:.1%}",
902
+ f"⏱️ Response: {result['response_time']}s",
903
+ f"πŸ“š Sources: {result['search_results_count']} found"
904
+ ]
905
+
906
+ if result.get('cached'):
907
+ metadata_parts.append("πŸ’Ύ Cached")
908
+
909
+ if result.get('sources'):
910
+ metadata_parts.append(f"πŸ“– Refs: {', '.join(result['sources'][:2])}")
911
+
912
+ metadata = " | ".join(metadata_parts)
913
+
914
+ # Prepare status with warnings/issues
915
+ status_parts = []
916
+ if result.get('safety_warnings'):
917
+ status_parts.append(f"⚠️ {len(result['safety_warnings'])} warnings")
918
+ if result.get('safety_issues'):
919
+ status_parts.append(f"🚨 {len(result['safety_issues'])} issues")
920
+ if not status_parts:
921
+ status_parts.append("βœ… Safe response")
922
+
923
+ status = " | ".join(status_parts)
924
+
925
+ return response, metadata, status
926
+
927
  except Exception as e:
928
+ logger.error(f"Error processing query: {e}")
929
+ error_response = f"⚠️ Error processing your query: {str(e)}\n\n🚨 For medical emergencies, seek immediate professional help."
930
+ error_metadata = f"❌ Error at {datetime.now().strftime('%H:%M:%S')}"
931
+ error_status = "🚨 System error occurred"
932
+ return error_response, error_metadata, error_status
933
 
934
+ def create_advanced_gradio_interface():
935
+ """Create advanced Gradio interface with modern design and enhanced UX"""
936
+
937
+ # Advanced CSS with medical theme and animations
938
  css = """
939
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
940
+
941
+ * {
942
+ font-family: 'Inter', sans-serif;
943
+ }
944
+
945
+ .gradio-container {
946
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
947
+ min-height: 100vh;
948
+ }
949
+
950
+ .main-container {
951
+ background: rgba(255, 255, 255, 0.95);
952
+ backdrop-filter: blur(10px);
953
+ border-radius: 20px;
954
+ padding: 30px;
955
+ margin: 20px;
956
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
957
+ border: 1px solid rgba(255,255,255,0.2);
958
+ }
959
+
960
+ .header-section {
961
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
962
+ color: white;
963
+ border-radius: 15px;
964
+ padding: 25px;
965
+ margin-bottom: 25px;
966
+ text-align: center;
967
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
968
+ }
969
+
970
+ .query-container {
971
+ background: linear-gradient(135deg, #f8f9ff 0%, #e8f2ff 100%);
972
+ border-radius: 15px;
973
+ padding: 20px;
974
+ margin: 15px 0;
975
+ border: 2px solid #667eea;
976
+ transition: all 0.3s ease;
977
+ }
978
+
979
+ .query-container:hover {
980
+ transform: translateY(-2px);
981
+ box-shadow: 0 10px 25px rgba(102, 126, 234, 0.2);
982
+ }
983
+
984
+ .query-input {
985
+ border: none !important;
986
+ background: white !important;
987
+ border-radius: 12px !important;
988
+ padding: 15px !important;
989
+ font-size: 16px !important;
990
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
991
+ transition: all 0.3s ease !important;
992
+ }
993
+
994
+ .query-input:focus {
995
+ transform: scale(1.02) !important;
996
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3) !important;
997
+ }
998
+
999
+ .response-container {
1000
+ background: linear-gradient(135deg, #fff 0%, #f8f9ff 100%);
1001
+ border-radius: 15px;
1002
+ padding: 20px;
1003
+ margin: 15px 0;
1004
+ border: 2px solid #4CAF50;
1005
+ min-height: 300px;
1006
+ }
1007
+
1008
+ .response-output {
1009
+ border: none !important;
1010
+ background: transparent !important;
1011
+ font-size: 15px !important;
1012
+ line-height: 1.7 !important;
1013
+ color: #2c3e50 !important;
1014
+ }
1015
+
1016
+ .metadata-container {
1017
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
1018
+ border-radius: 12px;
1019
+ padding: 15px;
1020
+ margin: 10px 0;
1021
+ border-left: 5px solid #2196F3;
1022
+ }
1023
+
1024
+ .metadata-output {
1025
+ border: none !important;
1026
+ background: transparent !important;
1027
+ font-size: 13px !important;
1028
+ color: #1565c0 !important;
1029
+ font-weight: 500 !important;
1030
+ }
1031
+
1032
+ .status-container {
1033
+ background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
1034
+ border-radius: 12px;
1035
+ padding: 15px;
1036
+ margin: 10px 0;
1037
+ border-left: 5px solid #4CAF50;
1038
+ }
1039
+
1040
+ .status-output {
1041
+ border: none !important;
1042
+ background: transparent !important;
1043
+ font-size: 13px !important;
1044
+ color: #2e7d32 !important;
1045
+ font-weight: 500 !important;
1046
+ }
1047
+
1048
+ .submit-btn {
1049
+ background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%) !important;
1050
+ color: white !important;
1051
+ border: none !important;
1052
+ border-radius: 12px !important;
1053
+ padding: 15px 30px !important;
1054
+ font-size: 16px !important;
1055
+ font-weight: 600 !important;
1056
+ cursor: pointer !important;
1057
+ transition: all 0.3s ease !important;
1058
+ box-shadow: 0 6px 20px rgba(76, 175, 80, 0.3) !important;
1059
+ }
1060
+
1061
+ .submit-btn:hover {
1062
+ transform: translateY(-3px) !important;
1063
+ box-shadow: 0 10px 30px rgba(76, 175, 80, 0.4) !important;
1064
+ }
1065
+
1066
+ .clear-btn {
1067
+ background: linear-gradient(135deg, #ff7043 0%, #ff5722 100%) !important;
1068
+ color: white !important;
1069
+ border: none !important;
1070
+ border-radius: 12px !important;
1071
+ padding: 15px 25px !important;
1072
+ font-size: 14px !important;
1073
+ font-weight: 500 !important;
1074
+ transition: all 0.3s ease !important;
1075
+ }
1076
+
1077
+ .clear-btn:hover {
1078
+ transform: translateY(-2px) !important;
1079
+ box-shadow: 0 8px 20px rgba(255, 87, 34, 0.3) !important;
1080
+ }
1081
+
1082
+ .emergency-notice {
1083
+ background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
1084
+ border: 2px solid #f44336;
1085
+ border-radius: 15px;
1086
+ padding: 20px;
1087
+ margin: 20px 0;
1088
+ color: #c62828;
1089
+ font-weight: 600;
1090
+ animation: pulse 2s infinite;
1091
+ }
1092
+
1093
+ @keyframes pulse {
1094
+ 0% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.4); }
1095
+ 70% { box-shadow: 0 0 0 10px rgba(244, 67, 54, 0); }
1096
+ 100% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0); }
1097
+ }
1098
+
1099
+ .gaza-context {
1100
+ background: linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%);
1101
+ border: 2px solid #4caf50;
1102
+ border-radius: 15px;
1103
+ padding: 20px;
1104
+ margin: 20px 0;
1105
+ color: #2e7d32;
1106
+ font-weight: 500;
1107
+ }
1108
+
1109
+ .sidebar-container {
1110
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
1111
+ border-radius: 15px;
1112
+ padding: 20px;
1113
+ margin: 10px 0;
1114
+ border: 1px solid rgba(0,0,0,0.1);
1115
+ }
1116
+
1117
+ .example-container {
1118
+ background: white;
1119
+ border-radius: 12px;
1120
+ padding: 20px;
1121
+ margin: 15px 0;
1122
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
1123
+ }
1124
+
1125
+ .progress-container {
1126
+ margin: 15px 0;
1127
+ padding: 10px;
1128
+ background: rgba(255,255,255,0.8);
1129
+ border-radius: 10px;
1130
+ }
1131
+
1132
+ .footer-section {
1133
+ background: linear-gradient(135deg, #37474f 0%, #263238 100%);
1134
+ color: white;
1135
+ border-radius: 15px;
1136
+ padding: 20px;
1137
+ margin-top: 30px;
1138
+ text-align: center;
1139
+ }
1140
  """
1141
 
1142
+ with gr.Blocks(
1143
+ css=css,
1144
+ title="πŸ₯ Advanced Gaza First Aid Assistant",
1145
+ theme=gr.themes.Soft(
1146
+ primary_hue="blue",
1147
+ secondary_hue="green",
1148
+ neutral_hue="slate"
1149
+ )
1150
+ ) as interface:
1151
 
1152
+ # Header Section
1153
+ with gr.Row(elem_classes=["main-container"]):
1154
+ gr.HTML("""
1155
+ <div class="header-section">
1156
+ <h1 style="margin: 0; font-size: 2.5em; font-weight: 700;">
1157
+ πŸ₯ Advanced Gaza First Aid Assistant
1158
+ </h1>
1159
+ <h2 style="margin: 10px 0 0 0; font-size: 1.2em; font-weight: 400; opacity: 0.9;">
1160
+ AI-Powered Medical Guidance for Gaza Healthcare Workers
1161
+ </h2>
1162
+ <p style="margin: 15px 0 0 0; font-size: 1em; opacity: 0.8;">
1163
+ Enhanced with 768-dimensional medical embeddings β€’ Advanced FAISS indexing β€’ Real-time safety validation
1164
+ </p>
1165
+ </div>
1166
+ """)
1167
 
1168
+ # Main Interface
1169
+ with gr.Row(elem_classes=["main-container"]):
1170
+ with gr.Column(scale=2):
1171
+ # Query Input Section
1172
+ with gr.Group(elem_classes=["query-container"]):
1173
+ gr.Markdown("### 🩺 Medical Query Input")
1174
+ query_input = gr.Textbox(
1175
+ label="Describe your medical situation",
1176
+ placeholder="Enter your first aid question or describe the medical emergency...",
1177
+ lines=4,
1178
+ elem_classes=["query-input"]
1179
+ )
1180
+
1181
+ with gr.Row():
1182
+ submit_btn = gr.Button(
1183
+ "πŸ” Get Medical Guidance",
1184
+ variant="primary",
1185
+ elem_classes=["submit-btn"],
1186
+ scale=3
1187
+ )
1188
+ clear_btn = gr.Button(
1189
+ "πŸ—‘οΈ Clear",
1190
+ variant="secondary",
1191
+ elem_classes=["clear-btn"],
1192
+ scale=1
1193
+ )
1194
+
1195
+ with gr.Column(scale=1):
1196
+ # Sidebar with Quick Access
1197
+ with gr.Group(elem_classes=["sidebar-container"]):
1198
+ gr.Markdown("""
1199
+ ### 🎯 Quick Access Guide
1200
+
1201
+ **🚨 Emergency Priorities:**
1202
+ - Severe bleeding control
1203
+ - Burn treatment protocols
1204
+ - Airway management
1205
+ - Trauma stabilization
1206
+ - Shock prevention
1207
+
1208
+ **πŸ₯ Gaza-Specific Scenarios:**
1209
+ - Limited water situations
1210
+ - Power outage medical care
1211
+ - Supply shortage alternatives
1212
+ - Mass casualty protocols
1213
+ - Improvised medical tools
1214
+
1215
+ **πŸ“Š System Status:**
1216
+ - βœ… Enhanced embeddings active
1217
+ - βœ… Advanced indexing enabled
1218
+ - βœ… Safety validation online
1219
+ - βœ… Gaza context aware
1220
+ """)
1221
 
1222
+ # Response Section
1223
+ with gr.Row(elem_classes=["main-container"]):
1224
+ with gr.Column():
1225
+ # Main Response
1226
+ with gr.Group(elem_classes=["response-container"]):
1227
+ gr.Markdown("### 🩹 Medical Guidance Response")
1228
+ response_output = gr.Textbox(
1229
+ label="AI Medical Guidance",
1230
+ lines=15,
1231
+ elem_classes=["response-output"],
1232
+ interactive=False,
1233
+ placeholder="Your medical guidance will appear here..."
1234
+ )
1235
+
1236
+ # Metadata and Status
1237
+ with gr.Row():
1238
+ with gr.Column(scale=1):
1239
+ with gr.Group(elem_classes=["metadata-container"]):
1240
+ metadata_output = gr.Textbox(
1241
+ label="πŸ“Š Response Metadata",
1242
+ lines=2,
1243
+ elem_classes=["metadata-output"],
1244
+ interactive=False,
1245
+ placeholder="Response metadata will appear here..."
1246
+ )
1247
+
1248
+ with gr.Column(scale=1):
1249
+ with gr.Group(elem_classes=["status-container"]):
1250
+ status_output = gr.Textbox(
1251
+ label="πŸ›‘οΈ Safety Status",
1252
+ lines=2,
1253
+ elem_classes=["status-output"],
1254
+ interactive=False,
1255
+ placeholder="Safety validation status will appear here..."
1256
+ )
1257
+
1258
+ # Important Notices
1259
+ with gr.Row(elem_classes=["main-container"]):
1260
+ gr.HTML("""
1261
+ <div class="emergency-notice">
1262
+ <h3 style="margin: 0 0 10px 0;">🚨 CRITICAL EMERGENCY DISCLAIMER</h3>
1263
+ <p style="margin: 0; font-size: 1.1em;">
1264
+ For life-threatening emergencies, seek immediate professional medical attention.<br>
1265
+ πŸ“ž <strong>Gaza Emergency Contacts:</strong> Palestinian Red Crescent (101) | Civil Defense (102)
1266
+ </p>
1267
+ </div>
1268
+ """)
1269
+
1270
+ with gr.Row(elem_classes=["main-container"]):
1271
+ gr.HTML("""
1272
+ <div class="gaza-context">
1273
+ <h3 style="margin: 0 0 10px 0;">πŸ“ Gaza Context Awareness</h3>
1274
+ <p style="margin: 0; font-size: 1em;">
1275
+ This advanced AI system is specifically designed for Gaza's challenging conditions including
1276
+ limited resources, frequent power outages, and restricted medical supply access. All guidance
1277
+ considers these constraints and provides practical alternatives when standard treatments are unavailable.
1278
+ </p>
1279
+ </div>
1280
+ """)
1281
 
1282
+ # Examples Section
1283
+ with gr.Row(elem_classes=["main-container"]):
1284
+ with gr.Group(elem_classes=["example-container"]):
1285
+ gr.Markdown("### πŸ’‘ Example Medical Scenarios")
1286
+
1287
+ example_queries = [
1288
+ "How to treat severe burns when clean water is extremely limited?",
1289
+ "Managing gunshot wounds with only basic household supplies",
1290
+ "Recognizing and treating infection in wounds without antibiotics",
1291
+ "Emergency care for children during extended power outages",
1292
+ "Treating compound fractures without proper medical equipment",
1293
+ "Managing diabetic emergencies when insulin is unavailable",
1294
+ "Stopping arterial bleeding with improvised tourniquets",
1295
+ "Recognizing and treating shock in mass casualty situations",
1296
+ "Airway management for unconscious patients without equipment",
1297
+ "Preventing infection in surgical wounds during siege conditions"
1298
+ ]
1299
+
1300
+ gr.Examples(
1301
+ examples=example_queries,
1302
+ inputs=query_input,
1303
+ label="Click any example to try it:",
1304
+ examples_per_page=5
1305
+ )
1306
+
1307
+ # Event Handlers
1308
  submit_btn.click(
1309
+ process_medical_query_with_progress,
1310
  inputs=query_input,
1311
+ outputs=[response_output, metadata_output, status_output],
1312
+ show_progress=True
1313
  )
1314
+
1315
  query_input.submit(
1316
+ process_medical_query_with_progress,
1317
  inputs=query_input,
1318
+ outputs=[response_output, metadata_output, status_output],
1319
+ show_progress=True
1320
+ )
1321
+
1322
+ clear_btn.click(
1323
+ lambda: ("", "", "", ""),
1324
+ outputs=[query_input, response_output, metadata_output, status_output]
1325
  )
1326
+
1327
+ # Footer
1328
+ with gr.Row(elem_classes=["main-container"]):
1329
+ gr.HTML("""
1330
+ <div class="footer-section">
1331
+ <h3 style="margin: 0 0 15px 0;">πŸ”¬ Advanced Technical Features</h3>
1332
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 20px;">
1333
+ <div>
1334
+ <strong>🧠 Enhanced AI:</strong><br>
1335
+ 768-dimensional medical embeddings<br>
1336
+ Advanced FAISS IVF indexing<br>
1337
+ Optimized LLM quantization
1338
+ </div>
1339
+ <div>
1340
+ <strong>πŸ›‘οΈ Safety Systems:</strong><br>
1341
+ Real-time medical validation<br>
1342
+ Contraindication detection<br>
1343
+ Gaza-specific risk assessment
1344
+ </div>
1345
+ <div>
1346
+ <strong>⚑ Performance:</strong><br>
1347
+ Async processing pipeline<br>
1348
+ Intelligent response caching<br>
1349
+ Progressive loading indicators
1350
+ </div>
1351
+ </div>
1352
+ <hr style="border: 1px solid rgba(255,255,255,0.2); margin: 20px 0;">
1353
+ <p style="margin: 0; opacity: 0.8;">
1354
+ <strong>βš•οΈ Medical Disclaimer:</strong> This AI assistant provides educational guidance based on established medical protocols.
1355
+ It is designed to support, not replace, medical professionals. Always consult qualified healthcare providers for definitive care.
1356
+ </p>
1357
+ </div>
1358
+ """)
1359
 
1360
  return interface
1361
 
1362
  def main():
1363
+ """Enhanced main function with comprehensive error handling and system monitoring"""
1364
+ logger.info("πŸš€ Starting Advanced Gaza First Aid Assistant")
1365
+
1366
+ try:
1367
+ # System initialization with detailed logging
1368
+ logger.info("πŸ”§ Pre-initializing enhanced RAG system...")
1369
+ system = initialize_enhanced_system()
1370
+
1371
+ # Verify system components
1372
+ logger.info("βœ… Knowledge base initialized")
1373
+ logger.info("βœ… Medical fact checker ready")
1374
+ logger.info("βœ… Enhanced embeddings loaded")
1375
+ logger.info("βœ… Advanced FAISS indexing active")
1376
+
1377
+ # Create and launch advanced interface
1378
+ logger.info("🎨 Creating advanced Gradio interface...")
1379
+ interface = create_advanced_gradio_interface()
1380
+
1381
+ logger.info("🌐 Launching advanced interface...")
1382
+ interface.launch(
1383
+ server_name="0.0.0.0",
1384
+ server_port=7860,
1385
+ share=False,
1386
+ max_threads=6, # Increased for better async performance
1387
+ show_error=True,
1388
+ quiet=False,
1389
+ favicon_path=None,
1390
+ ssl_verify=False
1391
+ )
1392
+
1393
+ except Exception as e:
1394
+ logger.error(f"❌ Failed to start Advanced Gaza First Aid Assistant: {e}")
1395
+ print(f"\n🚨 STARTUP ERROR: {e}")
1396
+ print("\nπŸ”§ Troubleshooting Steps:")
1397
+ print("1. Check if all dependencies are installed: pip install -r requirements.txt")
1398
+ print("2. Ensure sufficient memory is available (minimum 4GB RAM recommended)")
1399
+ print("3. Verify data directory exists and contains medical documents")
1400
+ print("4. Check system logs for detailed error information")
1401
+ print("\nπŸ“ž For technical support, check the application logs above.")
1402
+ sys.exit(1)
1403
 
1404
  if __name__ == "__main__":
1405
+ main()
1406
+