Spaces:
Sleeping
Sleeping
| """ | |
| ============================================================ | |
| Mentallico Orchestrator - The Brain | |
| ============================================================ | |
| هنا بنحط كل المكونات مع بعض: | |
| flow على رسالة جديدة: | |
| 1. (لو صوت) → STT | |
| 2. Classifier ensemble → prediction | |
| 3. Conversation manager → should_diagnose? | |
| 4a. لو لسه → probing/chat response | |
| 4b. لو نعم → final diagnosis + recommendations | |
| 5. حفظ الـ history وارجاع response | |
| الـ orchestrator هو الـ entry point الوحيد للـ API. | |
| """ | |
| from typing import Optional, Dict, List | |
| from dataclasses import dataclass | |
| from config import CLASSIFICATION_MODELS, LABELS, MODELS_DIR, DIAGNOSIS_CONFIG | |
| from classifier_models import EnsembleDiagnoser | |
| from conversation_manager import ConversationManager | |
| from rag_engine import get_rag | |
| from speech_to_text import get_transcriber | |
| import os | |
| import torch | |
| class MentallicoBrain: | |
| """المنسّق الرئيسي للنظام كله""" | |
| def __init__(self, primary_model_path: str = None, secondary_model_path: str = None, | |
| enable_stt: bool = True): | |
| # Default paths | |
| if primary_model_path is None: | |
| primary_model_path = os.path.join(MODELS_DIR, "primary_model.pt") | |
| if secondary_model_path is None: | |
| secondary_model_path = os.path.join(MODELS_DIR, "secondary_model.pt") | |
| # ===== Classifier Ensemble ===== | |
| self.diagnoser = EnsembleDiagnoser(CLASSIFICATION_MODELS, LABELS) | |
| primary = primary_model_path if os.path.exists(primary_model_path) else None | |
| secondary = secondary_model_path if os.path.exists(secondary_model_path) else None | |
| if primary or secondary: | |
| self.diagnoser.load_pretrained(primary_path=primary, secondary_path=secondary) | |
| else: | |
| print("⚠ No trained models found. Only emotion model + RAG will work.") | |
| self.diagnoser.load_pretrained() # just emotion | |
| # ===== RAG ===== | |
| try: | |
| self.rag = get_rag() | |
| except Exception as e: | |
| print(f"⚠ RAG init failed: {e}") | |
| self.rag = None | |
| # ===== Conversation Manager ===== | |
| self.conv_mgr = ConversationManager() | |
| # ===== STT ===== | |
| self.transcriber = get_transcriber() if enable_stt else None | |
| # ============= Sessions ============= | |
| def create_session(self) -> str: | |
| return self.conv_mgr.create_session() | |
| def reset_session(self, session_id: str): | |
| self.conv_mgr.reset(session_id) | |
| # ============= Audio ============= | |
| def process_audio(self, audio_path: str, language: str = None) -> dict: | |
| """يحوّل صوت لـ text""" | |
| if self.transcriber is None: | |
| return {"error": "STT not enabled"} | |
| return self.transcriber.transcribe(audio_path, language) | |
| # ============= Main Flow ============= | |
| def process_message(self, session_id: str, user_text: str, | |
| user_audio_path: str = None) -> dict: | |
| """ | |
| أهم function - يدير التفاعل كله. | |
| ترجع: | |
| { | |
| "type": "chat" | "probing" | "diagnosis", | |
| "response": "...", | |
| "diagnosis": "..." (لو type==diagnosis), | |
| "confidence": 0.85, | |
| "urgency": "normal" | "critical", | |
| "session_id": "...", | |
| "internal": {...} (للـ debugging) | |
| } | |
| """ | |
| # ===== 1. Audio processing لو موجود ===== | |
| audio_info = None | |
| if user_audio_path: | |
| audio_result = self.process_audio(user_audio_path) | |
| if audio_result.get('text'): | |
| user_text = audio_result['text'] | |
| audio_info = audio_result | |
| elif not user_text: | |
| return { | |
| "type": "error", | |
| "response": "Sorry, I couldn't understand the audio. Could you type your message?", | |
| "error": audio_result.get('error', 'Audio processing failed') | |
| } | |
| if not user_text or not user_text.strip(): | |
| return {"type": "error", "response": "Empty input received."} | |
| # ===== 2. Classifier prediction (على النص الجديد + التراكمي) ===== | |
| # نحلل النص التراكمي عشان نحصل على signal أقوى | |
| combined_text = self.conv_mgr.get_combined_text(session_id) + " " + user_text | |
| prediction = self.diagnoser.predict(combined_text.strip()) | |
| # نحفظ الرسالة مع التنبؤ | |
| self.conv_mgr.add_user_message(session_id, user_text, prediction=prediction) | |
| # ===== 3. هل نشخّص؟ ===== | |
| decision = self.conv_mgr.should_diagnose(session_id, prediction) | |
| # ===== 4. ولّد الرد المناسب ===== | |
| history = [ | |
| {"role": m.role, "content": m.content} | |
| for m in self.conv_mgr.get_session(session_id).messages | |
| ] | |
| if decision['diagnose']: | |
| # ===== التشخيص النهائي ===== | |
| label = decision['label'] | |
| confidence = decision['confidence'] | |
| urgency = decision.get('urgency', 'normal') | |
| if self.rag is not None: | |
| response_text = self.rag.final_diagnosis_response( | |
| diagnosis=label, | |
| confidence=confidence, | |
| history=history, | |
| urgency=urgency | |
| ) | |
| else: | |
| response_text = self._fallback_diagnosis_text(label, confidence) | |
| self.conv_mgr.add_assistant_message(session_id, response_text) | |
| self.conv_mgr.confirm_diagnosis(session_id, label, confidence) | |
| return { | |
| "type": "diagnosis", | |
| "response": response_text, | |
| "diagnosis": label, | |
| "confidence": round(confidence, 3), | |
| "urgency": urgency, | |
| "session_id": session_id, | |
| "audio_info": audio_info, | |
| "internal": { | |
| "decision_reason": decision['reason'], | |
| "consistency": decision.get('consistency'), | |
| "current_prediction": prediction, | |
| } | |
| } | |
| else: | |
| # ===== لسه بنجمّع معلومات ===== | |
| # لو الموديل عنده توقع قوي نعمل probing، غير كده chat عادي | |
| confidence = prediction['confidence'] | |
| if confidence >= 0.5 and self.rag is not None: | |
| response_text = self.rag.probing_response( | |
| user_input=user_text, | |
| history=history, | |
| predicted_label=prediction['label'], | |
| confidence=confidence | |
| ) | |
| response_type = "probing" | |
| else: | |
| if self.rag is not None: | |
| response_text = self.rag.chat_response(user_text, history) | |
| else: | |
| response_text = self._fallback_chat_text() | |
| response_type = "chat" | |
| self.conv_mgr.add_assistant_message(session_id, response_text) | |
| return { | |
| "type": response_type, | |
| "response": response_text, | |
| "diagnosis": None, | |
| "confidence": round(confidence, 3), | |
| "urgency": "normal", | |
| "session_id": session_id, | |
| "audio_info": audio_info, | |
| "internal": { | |
| "decision_reason": decision['reason'], | |
| "progress": decision.get('progress'), | |
| "current_prediction": prediction, | |
| } | |
| } | |
| def _fallback_diagnosis_text(self, label: str, confidence: float) -> str: | |
| return (f"Based on our conversation, the analysis suggests **{label}** " | |
| f"(confidence: {confidence:.0%}).\n\n" | |
| f"⚠ The full RAG system is not available, so detailed recommendations " | |
| f"can't be generated. Please consult a mental health professional for " | |
| f"a proper evaluation and treatment plan.") | |
| def _fallback_chat_text(self) -> str: | |
| return ("I'm here to listen. Could you tell me more about how you've been feeling lately? " | |
| "When did these feelings start, and how have they been affecting your daily life?") | |
| # ============= Knowledge base management ============= | |
| def add_pdf(self, pdf_path: str) -> dict: | |
| if self.rag is None: | |
| return {"status": "error", "message": "RAG not initialized"} | |
| return self.rag.add_pdf_to_knowledge_base(pdf_path) | |
| # ===== Global singleton (lazy) ===== | |
| _brain = None | |
| def get_brain() -> MentallicoBrain: | |
| global _brain | |
| if _brain is None: | |
| _brain = MentallicoBrain() | |
| return _brain | |