import json from app.ai.openai_client import OpenAIClient from app.ai.prompts import build_system_prompt from app.ai.validator import validate_ai_response from app.config import OPENAI_API_KEY, GEMINI_API_KEY, GEMINI_MODEL, ANTHROPIC_API_KEY, ANTHROPIC_MODEL from app.specialties.registry import get_specialty_config class AIOrchestrator: def __init__(self): self.openai = OpenAIClient() async def process( self, session, state, conversation_history, user_message, ): specialty = get_specialty_config( session.category_code ) current_state = json.loads( state.state_json ) context = { "patient": { "id": session.patient_id, "name": session.patient_name, "age": session.age, "gender": session.gender, }, "category": { "type": session.category_type, "code": session.category_code, "name": session.category_name, }, "specialty_config": specialty, "language": session.language, "current_stage": session.stage, "current_state": current_state, "conversation_history": conversation_history, "new_patient_message": user_message, } prompt = build_system_prompt(context) # 1. Primary: Google Gemini (High-Speed Multilingual Indic Reasoning ~0.8s) if GEMINI_API_KEY and GEMINI_API_KEY.strip() not in ["your_gemini_key", ""]: try: import google.generativeai as genai genai.configure(api_key=GEMINI_API_KEY) model = genai.GenerativeModel(GEMINI_MODEL or "gemini-3.6-flash") full_prompt = f"{prompt}\n\nPATIENT MESSAGE: {user_message}\n\nRespond strictly with JSON matching the schema above." res = await model.generate_content_async(full_prompt) raw_text = res.text.replace("```json", "").replace("```", "").strip() parsed = json.loads(raw_text) validated = validate_ai_response(parsed) return validated.model_dump() except Exception as e: print(f"[Gemini Orchestrator Error]: {e}") # 2. Secondary: OpenAI Call if valid API key is set if OPENAI_API_KEY and OPENAI_API_KEY.strip() not in ["your_openai_api_key", "your_openai_key", ""]: try: raw_response = await self.openai.generate_response( system_prompt=prompt, user_message=user_message, ) parsed = json.loads(raw_response) validated = validate_ai_response(parsed) return validated.model_dump() except Exception as e: print(f"[OpenAI Orchestrator Error]: {e}") # 3. Tertiary: Anthropic Claude if ANTHROPIC_API_KEY and ANTHROPIC_API_KEY.strip() not in ["your_anthropic_key", ""]: try: from anthropic import AsyncAnthropic client = AsyncAnthropic(api_key=ANTHROPIC_API_KEY) res = await client.messages.create( model=ANTHROPIC_MODEL or "claude-sonnet-4-5-20250929", max_tokens=600, system=prompt, messages=[{"role": "user", "content": user_message}], ) raw_text = res.content[0].text.replace("```json", "").replace("```", "").strip() parsed = json.loads(raw_text) validated = validate_ai_response(parsed) return validated.model_dump() except Exception as e: print(f"[Claude Orchestrator Error]: {e}") # 4. Fallback simulator for offline development with localized language support parsed = self._simulate_specialty_response(session, specialty, user_message, len(conversation_history)) validated = validate_ai_response(parsed) return validated.model_dump() def _simulate_specialty_response(self, session, specialty, user_message, turn_count=0): stage = session.stage code = specialty.get("code", "general") lang = (session.language or "ta-IN").lower() msg_lower = user_message.lower().strip() is_negative = msg_lower in ["no", "no more", "nothing", "nothing else", "know nothing", "none", "illness none", "இல்லை", "ஒன்றுமில்லை", "नहीं"] # Localized clinical phrases if lang.startswith("ta"): conclusion_text = "நன்றி. உங்கள் உடல்நல விவரங்கள் பதிவு செய்யப்பட்டுள்ளன. மருத்துவர் விரைவில் பரிசீலிப்பார்." symptom_q = "இந்த அறிகுறிகள் எவ்வளவு நாட்களாக இருக்கிறது, மற்றும் ஏதேனும் மாத்திரைகள் சாப்பிட்டீர்களா?" med_q = "நன்றி. உங்களுக்கு ஏதேனும் மருந்து ஒவ்வாமை (Allergy) அல்லது வேறு உடல்நல பிரச்சனைகள் உள்ளதா?" elif lang.startswith("hi"): conclusion_text = "धन्यवाद। आपकी स्वास्थ्य जानकारी दर्ज कर ली गई है। डॉक्टर जल्द ही समीक्षा करेंगे।" symptom_q = "आपको यह समस्या कितने दिनों से है और क्या आपने कोई दवाई ली है?" med_q = "धन्यवाद। क्या आपको किसी दवाई से एलर्जी या कोई अन्य बीमारी है?" elif lang.startswith("te"): conclusion_text = "ధన్యవాదాలు. మీ ఆరోగ్య వివరాలు నమోదు చేయబడ్డాయి." symptom_q = "ఈ సమస్య మీకు ఎన్ని రోజుల నుండి ఉంది మరియు ఏవైనా మందులు వాడారా?" med_q = "ధన్యవాదాలు. మీకు ఏవైనా మందుల పడనితనం (Allergy) ఉందా?" elif lang.startswith("kn"): conclusion_text = "ಧನ್ಯವಾದಗಳು. ನಿಮ್ಮ ಆರೋಗ್ಯದ ಮಾಹಿತಿ ದಾಖಲಾಗಿದೆ." symptom_q = "ಈ ಸಮಸ್ಯೆ ನಿಮಗೆ ಎಷ್ಟು ದಿನಗಳಿಂದ ಇದೆ ಮತ್ತು ಯಾವುದೇ ಮಾತ್ರೆಗಳನ್ನು ತೆಗೆದುಕೊಂಡಿದ್ದೀರಾ?" med_q = "ಧನ್ಯವಾದಗಳು. ನಿಮಗೆ ಯಾವುದೇ ಔಷಧಿ ಅಲರ್ಜಿ ಇದೆಯೇ?" else: conclusion_text = "Thank you. I have recorded your intake details for the doctor to review." symptom_q = "How long have you had these symptoms, and have you taken any medicines?" med_q = "Thank you. Do you have any known drug allergies or existing health conditions?" # Fast conclusion if patient says "No" or already had 6 turns if is_negative or turn_count >= 6: return { "reply": conclusion_text, "next_stage": "confirmation", "state_updates": { "reason_for_visit": None, "symptoms": [], "medical_history": ["None reported"] if is_negative else [], "medications": [], "allergies": ["None"] if is_negative else [] }, "completed": True } if stage == "greeting": return { "reply": symptom_q, "next_stage": "reason_for_visit", "state_updates": { "reason_for_visit": user_message, "symptoms": [{"name": user_message, "duration": "recent"}], "medical_history": [], "medications": [], "allergies": [] }, "completed": False } elif stage == "reason_for_visit": return { "reply": med_q, "next_stage": "medications", "state_updates": { "reason_for_visit": user_message, "symptoms": [{"name": user_message, "duration": "reported"}], "medical_history": [], "medications": [], "allergies": [] }, "completed": False } elif stage == "medications": return { "reply": conclusion_text, "next_stage": "confirmation", "state_updates": { "reason_for_visit": None, "symptoms": [], "medical_history": [], "medications": [user_message] if not is_negative else [], "allergies": [] }, "completed": False } else: return { "reply": conclusion_text, "next_stage": "confirmation", "state_updates": { "reason_for_visit": None, "symptoms": [], "medical_history": [], "medications": [], "allergies": ["None"] if is_negative else [user_message] }, "completed": True }