| """ |
| Phase 5: Gradio Chatbot Interface β Professional Edition (Hugging Face Version). |
| |
| Launches an interactive web UI where a patient can type symptoms |
| and receive an empathetic, medically-informed response. |
| """ |
| import os |
| import gradio as gr |
| import re |
| import json |
| import torch |
| import gc |
| from huggingface_hub import hf_hub_download |
|
|
| import sys |
| sys.path.insert(0, os.path.dirname(__file__)) |
| from config import EMOTION_LABELS, RAG_TOP_K |
| from model import EmotionAwareMedicalChatbot |
| from rag_engine import RAGEngine |
|
|
| |
| |
| |
| REPO_ID = "shreenikethjoshi/MedEmotion-BioGPT-Large" |
|
|
| print("Downloading SOTA artifacts from Hub...") |
| checkpoint_path = hf_hub_download(repo_id=REPO_ID, filename="best_model_inference.pt") |
| index_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_index.bin") |
| passages_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_index_passages.npy") |
|
|
|
|
| class ChatbotInference: |
| """Production-grade inference wrapper for the Emotion-Aware Medical Chatbot.""" |
|
|
| def __init__(self, ckpt_path: str, idx_path: str, pass_path: str): |
| print("Loading model...") |
| self.model = EmotionAwareMedicalChatbot() |
| ckpt = torch.load(ckpt_path, map_location="cpu") |
| self.model.load_state_dict(ckpt["model_state_dict"]) |
| self.model.to("cpu").eval() |
| |
| del ckpt |
| gc.collect() |
| print(" β
Model loaded") |
|
|
| print("Loading RAG engine...") |
| self.rag = RAGEngine(index_path=idx_path, passages_path=pass_path) |
| try: |
| self.rag.load_index() |
| self.use_rag = True |
| print(" β
RAG index loaded") |
| except Exception as e: |
| self.use_rag = False |
| print(f" β οΈ No RAG index found β running without retrieval ({str(e)})") |
|
|
| |
| |
| |
| INTENTS = { |
| "greeting": { |
| "patterns": [ |
| "hi", "hello", "hey", "good morning", "good evening", |
| "good afternoon", "howdy", "greetings", "sup", "yo", |
| "hii", "hiii", "hiiii", "helo", "namaste", "hola", |
| ], |
| "response": ( |
| "Hello! π I'm your **Emotion-Aware Medical Assistant**.\n\n" |
| "I can help you with:\n" |
| "β’ π©Ί Symptom analysis & medical advice\n" |
| "β’ π Emotional support & empathetic responses\n" |
| "β’ π Medication information\n\n" |
| "**How can I help you today?** Please describe your symptoms " |
| "or ask a health-related question." |
| ), |
| }, |
| "thanks": { |
| "patterns": [ |
| "thank", "thanks", "thank you", "thx", "appreciate", |
| "thanks a lot", "thank you so much", "great help", |
| ], |
| "response": ( |
| "You're welcome! π I'm glad I could help.\n\n" |
| "**Important reminders:**\n" |
| "β’ Always consult a qualified doctor for proper diagnosis\n" |
| "β’ Follow prescribed medications as directed\n" |
| "β’ Don't hesitate to seek emergency care if symptoms worsen\n\n" |
| "Take care of your health! π₯π" |
| ), |
| }, |
| "farewell": { |
| "patterns": [ |
| "bye", "goodbye", "see you", "take care", "good night", |
| "see ya", "gotta go", "leaving", "exit", "quit", |
| ], |
| "response": ( |
| "Goodbye! Take care of yourself π\n\n" |
| "**Remember:**\n" |
| "β’ Stay hydrated and get adequate rest\n" |
| "β’ Follow up with your doctor if any symptoms persist\n" |
| "β’ I'm available anytime you need medical guidance\n\n" |
| "Stay healthy! π" |
| ), |
| }, |
| "about": { |
| "patterns": [ |
| "who are you", "what are you", "what can you do", |
| "help", "about", "features", "capabilities", |
| "how do you work", "tell me about yourself", |
| ], |
| "response": ( |
| "π₯ **Emotion-Aware Medical Chatbot**\n\n" |
| "I'm an AI-powered healthcare assistant built with a SOTA architecture:\n\n" |
| "**π§ How I work:**\n" |
| "1. **Longformer Encoder** β Reads your full message (up to 4096 tokens)\n" |
| "2. **Syntax GCN** β Understands clinical grammar & relationships\n" |
| "3. **Emotion Detector** β Identifies your emotional state (fear, sadness, etc.)\n" |
| "4. **RAG Engine** β Retrieves relevant info from 180k+ real doctor conversations\n" |
| "5. **BioGPT Decoder** β Generates an empathetic, medically-informed response\n\n" |
| "β οΈ **Disclaimer:** I'm a research prototype. Always consult a real doctor for medical decisions." |
| ), |
| }, |
| "how_are_you": { |
| "patterns": [ |
| "how are you", "how r u", "how do you do", |
| "whats up", "what's up", "wassup", |
| ], |
| "response": ( |
| "I'm doing great, thank you for asking! π\n\n" |
| "I'm here and ready to help with any health concerns you may have. " |
| "Please feel free to describe your symptoms or ask a medical question." |
| ), |
| }, |
| "ok_ack": { |
| "patterns": [ |
| "ok", "okay", "fine", "alright", "got it", |
| "understood", "i see", "hmm", "ohh", "oh", |
| ], |
| "response": ( |
| "Got it! π Is there anything else you'd like to know about your health? " |
| "Feel free to ask me any medical question." |
| ), |
| }, |
| } |
|
|
| EMERGENCY_KEYWORDS = [ |
| "suicide", "kill myself", "want to die", "end my life", |
| "self harm", "self-harm", "cutting myself", |
| "heart attack", "chest pain and can't breathe", |
| "can't breathe", "choking", "unconscious", |
| "overdose", "poisoning", "severe bleeding", |
| ] |
|
|
| EMERGENCY_RESPONSE = ( |
| "π¨ **EMERGENCY β PLEASE SEEK IMMEDIATE HELP** π¨\n\n" |
| "If you or someone is in immediate danger:\n\n" |
| "π **Emergency Services:** Call **112** (India) or **911** (US)\n" |
| "π **Suicide Prevention Helpline (India):** 9820466726 (iCall)\n" |
| "π **Mental Health Helpline:** 1800-599-0019 (KIRAN)\n" |
| "π **National Suicide Prevention (US):** 988\n\n" |
| "β οΈ **I am an AI and cannot provide emergency medical care.** " |
| "Please reach out to a medical professional or emergency services immediately.\n\n" |
| "**You are not alone. Help is available. π**" |
| ) |
|
|
| MEDICAL_KEYWORDS = [ |
| "pain", "hurt", "ache", "fever", "cough", "cold", "sick", |
| "blood", "symptom", "disease", "medicine", "doctor", "treatment", |
| "diagnosed", "surgery", "infection", "allergy", "headache", |
| "stomach", "vomit", "nausea", "diarrhea", "rash", "swelling", |
| "dizzy", "fatigue", "weakness", "breathing", "chest", "heart", |
| "diabetes", "pressure", "cancer", "thyroid", "kidney", "liver", |
| "pregnant", "pregnancy", "period", "menstrual", "anxiety", |
| "depression", "insomnia", "sleep", "weight", "diet", "vitamin", |
| "tablet", "capsule", "dose", "mg", "prescription", "test", |
| "report", "scan", "xray", "mri", "ct scan", "biopsy", |
| "suffering", "problem", "condition", "disorder", "chronic", |
| "acute", "remedy", "cure", "medication", "drug", "antibiotic", |
| ] |
|
|
| def _detect_intent(self, message): |
| """Route non-medical messages to predefined responses.""" |
| msg = message.strip().lower() |
| msg_clean = re.sub(r'[!.,?;:\'"]+$', '', msg) |
|
|
| |
| for keyword in self.EMERGENCY_KEYWORDS: |
| if keyword in msg: |
| return self.EMERGENCY_RESPONSE |
|
|
| |
| for intent_data in self.INTENTS.values(): |
| for pattern in intent_data["patterns"]: |
| if (msg_clean == pattern or |
| msg_clean.startswith(pattern + " ") or |
| msg_clean.endswith(" " + pattern)): |
| return intent_data["response"] |
|
|
| |
| if len(msg.strip()) < 2: |
| return self.INTENTS["greeting"]["response"] |
|
|
| |
| alpha_only = re.sub(r'[^a-zA-Z\s]', '', msg) |
| if len(alpha_only.split()) == 0: |
| return ("I couldn't understand your message. π€\n\n" |
| "Could you please describe your symptoms or health concern in more detail? " |
| "For example: *\"I have a headache and fever for 2 days\"*") |
|
|
| |
| if (len(msg.split()) <= 2 and |
| not any(w in msg for w in self.MEDICAL_KEYWORDS)): |
| return self.INTENTS["greeting"]["response"] |
|
|
| return None |
|
|
| |
| |
| |
| @torch.no_grad() |
| def respond(self, patient_message: str, max_new_tokens: int = 150): |
| """ |
| Generate a response using the FULL trained pipeline. |
| Returns: (response_text, emotion_detected, emotion_scores, rag_passages) |
| """ |
| |
| intent_response = self._detect_intent(patient_message) |
| if intent_response: |
| return intent_response, "neutral", {}, [] |
|
|
| |
| emotion_probs = self.model.get_emotion_embedding([patient_message]) |
| probs = emotion_probs[0].cpu().tolist() |
| emotion_scores = { |
| EMOTION_LABELS[i]: round(probs[i], 4) |
| for i in range(len(EMOTION_LABELS)) |
| } |
| emotion_detected = max(emotion_scores, key=emotion_scores.get) |
|
|
| |
| rag_passages = [] |
| if getattr(self, 'use_rag', False): |
| results = self.rag.retrieve(patient_message, top_k=RAG_TOP_K) |
| rag_passages = [r["passage"] for r in results] |
|
|
| |
| dep_edges = json.dumps([]) |
| generated_texts, _ = self.model.generate_with_context( |
| patient_texts=[patient_message], |
| dep_edges_json=[dep_edges], |
| max_new_tokens=280, |
| temperature=0.45, |
| top_p=0.9, |
| do_sample=True, |
| ) |
|
|
| response_text = generated_texts[0] if generated_texts else "" |
|
|
| |
| response_text = self._clean_response(response_text) |
|
|
| |
| if not response_text or len(response_text.strip()) < 10: |
| response_text = ( |
| "Based on your symptoms, I recommend consulting a healthcare professional " |
| "for a proper diagnosis. In the meantime, stay hydrated and get adequate rest." |
| ) |
|
|
| return response_text, emotion_detected, emotion_scores, rag_passages |
|
|
| @staticmethod |
| def _clean_response(text): |
| if not text: return "" |
| |
| |
| text = re.sub(r'https?://\S+|www\.\S+', '', text, flags=re.IGNORECASE) |
| text = re.sub(r'urldefense\S+', '', text, flags=re.IGNORECASE) |
| |
| |
| text = re.sub(r'(Let me know if you have|Feel free to ask|You can ask a direct question|Please meet an oral).*$', '', text, flags=re.IGNORECASE | re.DOTALL) |
| |
| |
| text = re.sub(r'(Regards|Sincerely|Best wishes|Warm regards|Thanks|Wishing),?\s*(Dr\.?|Doctor)?\s*[A-Z][a-z]+.*$', '', text, flags=re.IGNORECASE | re.DOTALL) |
| |
| |
| sentences = re.split(r'(?<=[.!?])\s+', text.strip()) |
| |
| if len(sentences) > 1: |
| cleaned = [] |
| for sent in sentences: |
| if re.search(r'\b\d{1,2}\s*(year|yr)\s*old\b', sent, re.IGNORECASE): continue |
| if re.search(r'\b(visit(ing)?\s+her|visit(ing)?\s+him|his\s+wife|her\s+husband)\b', sent, re.IGNORECASE): continue |
| if re.search(r'^(I am|I have been|My name|Dear doctor)', sent, re.IGNORECASE): continue |
| if 'healthcaremagic' in sent.lower() or 'urldefense' in sent.lower(): continue |
| cleaned.append(sent) |
| text = ' '.join(cleaned) |
| |
| if len(text) > 600: |
| last_period = text[:600].rfind('.') |
| if last_period > 100: text = text[:last_period + 1] |
|
|
| |
| if text and text[-1] not in '.!?': |
| last_punct = max(text.rfind('.'), text.rfind('!'), text.rfind('?')) |
| if last_punct > 0: text = text[:last_punct + 1] |
| else: text += "." |
| |
| |
| text = re.sub(r'[_;\-\|]{2,}.*$', '', text) |
| |
| return text.strip() |
|
|
|
|
| |
| chatbot = ChatbotInference(checkpoint_path, index_path, passages_path) |
|
|
| |
| |
| |
| def launch_gradio(chatbot: ChatbotInference): |
| """Launch a professional Gradio web interface.""" |
|
|
| def chat_fn(message, history): |
| """Process user message and return formatted response.""" |
| if not message or not message.strip(): |
| return "Please type a message to get started! π" |
|
|
| response, emotion, scores, passages = chatbot.respond(message) |
|
|
| if not scores: |
| return response |
|
|
| emotion_bar = " | ".join([ |
| f"{k}: {v:.0%}" |
| for k, v in sorted(scores.items(), key=lambda x: -x[1])[:3] |
| ]) |
|
|
| output = f"{response}\n\n" |
| output += f"---\n" |
| output += f"π **Detected Emotion:** {emotion.upper()}\n" |
| output += f"π {emotion_bar}\n" |
|
|
| if passages: |
| output += f"\nπ **Retrieved Medical Context:**\n" |
| for i, p in enumerate(passages[:3], 1): |
| output += f" {i}. {p}\n\n" |
|
|
| return output |
|
|
| |
| demo = gr.ChatInterface( |
| fn=chat_fn, |
| title="π₯ Emotion-Aware Medical Chatbot", |
| description=( |
| "An AI-powered healthcare assistant that **detects your emotional state** " |
| "and provides **empathetic, medically-informed responses** powered by " |
| "Longformer, GCN, and BioGPT.\n\n" |
| "β οΈ **Disclaimer:** This is a research prototype. Always consult a qualified doctor " |
| "for medical diagnosis and treatment." |
| ), |
| examples=[ |
| "I have been having severe chest pain for the last 3 days and I'm really scared", |
| "My child has a high fever of 103Β°F and won't stop crying. What should I do?", |
| "I've been feeling very depressed lately and can't sleep at night", |
| "What are the side effects of metformin for diabetes?", |
| "I have persistent headache with blurred vision since morning", |
| "I'm 6 months pregnant and experiencing severe back pain", |
| ], |
| ) |
|
|
| demo.launch(server_name="0.0.0.0", server_port=7860, share=True) |
|
|
| if __name__ == "__main__": |
| launch_gradio(chatbot) |
|
|