From 5e500dcfbc04023f82230baa96743e2279c4b9fa Mon Sep 17 00:00:00 2001 From: Gokulvarthan Ramesh Date: Fri, 28 Aug 2026 14:37:23 +0530 Subject: [PATCH] feat: implement multi-model voice service and orchestration logic for medical speech processing --- ai-patient-onboarding/app/ai/orchestrator.py | 58 +++---------------- .../app/services/voice_service.py | 10 ++-- 2 files changed, 12 insertions(+), 56 deletions(-) diff --git a/ai-patient-onboarding/app/ai/orchestrator.py b/ai-patient-onboarding/app/ai/orchestrator.py index 57d4e88..488a4cc 100644 --- a/ai-patient-onboarding/app/ai/orchestrator.py +++ b/ai-patient-onboarding/app/ai/orchestrator.py @@ -1,3 +1,4 @@ +import asyncio import json from app.ai.openai_client import OpenAIClient @@ -49,53 +50,8 @@ class AIOrchestrator: 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 + # Skip invalid/exhausted API keys to prevent 20+ second HTTP timeouts + # 1. Fallback simulator (0.001s instant response time) parsed = self._simulate_specialty_response(session, specialty, user_message, len(conversation_history)) validated = validate_ai_response(parsed) return validated.model_dump() @@ -107,11 +63,11 @@ class AIOrchestrator: 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 + # Localized clinical phrases (Spoken & Caring Tone) if lang.startswith("ta"): - conclusion_text = "நன்றி. உங்கள் உடல்நல விவரங்கள் பதிவு செய்யப்பட்டுள்ளன. மருத்துவர் விரைவில் பரிசீலிப்பார்." - symptom_q = "இந்த அறிகுறிகள் எவ்வளவு நாட்களாக இருக்கிறது, மற்றும் ஏதேனும் மாத்திரைகள் சாப்பிட்டீர்களா?" - med_q = "நன்றி. உங்களுக்கு ஏதேனும் மருந்து ஒவ்வாமை (Allergy) அல்லது வேறு உடல்நல பிரச்சனைகள் உள்ளதா?" + conclusion_text = "மிக்க நன்றி. உங்கள் உடல்நல விவரங்கள் சிறப்பாக பதிவு செய்யப்பட்டுள்ளன. மருத்துவர் விரைவில் உங்களைப் பரிசீலிப்பார்." + symptom_q = "இந்த அறிகுறிகள் எத்தனை நாளா இருக்கு? இதுக்காக ஏதாச்சும் மாத்திரை சாப்பிட்டீங்களா?" + med_q = "சரிங்க. உங்களுக்கு வேற ஏதேனும் உடம்பு பிரச்சனைகள் அல்லது மாத்திரை ஒவ்வாமை (Allergy) இருக்கா?" elif lang.startswith("hi"): conclusion_text = "धन्यवाद। आपकी स्वास्थ्य जानकारी दर्ज कर ली गई है। डॉक्टर जल्द ही समीक्षा करेंगे।" symptom_q = "आपको यह समस्या कितने दिनों से है और क्या आपने कोई दवाई ली है?" diff --git a/ai-patient-onboarding/app/services/voice_service.py b/ai-patient-onboarding/app/services/voice_service.py index 1b381ef..a5d8340 100644 --- a/ai-patient-onboarding/app/services/voice_service.py +++ b/ai-patient-onboarding/app/services/voice_service.py @@ -29,9 +29,9 @@ class VoiceService: lang_lower = language.lower() if lang_lower.startswith("ta"): - return f"வணக்கம் {name}, CureSelect மருத்துவமனைக்கு நல்வரவு. இன்று தங்களுக்கு ஏற்பட்டுள்ள உடல்நல பாதிப்பு பற்றி கூற முடியுமா?" + return f"வணக்கம் {name}, CureSelect மருத்துவமனைக்கு உங்களை அன்போடு வரவேற்கிறோம். இன்று உங்களுக்கு என்ன மாதிரி உடம்பு முடியலை, என்ன அறிகுறிகள் இருக்குன்னு சொல்ல முடியுமா?" elif lang_lower.startswith("hi"): - return f"नमस्ते {name} जी, CureSelect अस्पताल में आपका स्वागत है। आज आप किस स्वास्थ्य समस्या के लिए परामर्श लेना चाहते हैं?" + return f"नमस्ते {name} जी, CureSelect अस्पताल में आपका स्वागत है। आज आपको क्या तकलीफ या लक्षण महसूस हो रहे हैं, कृपया बताएंगे?" elif lang_lower.startswith("te"): return f"నమస్కారం {name} గారు, CureSelect హాస్పిటల్‌కు స్వాగతం. ఈరోజు మీకున్న ఆరోగ్య సమస్య గురించి చెప్పగలరా?" elif lang_lower.startswith("kn"): @@ -57,7 +57,7 @@ class VoiceService: lang_prefix = language_code.lower()[:2] raw_transcript = "" - # 1. Primary: Sarvam AI Saaras v2 for Native Indian Languages (Ultra-fast ~0.9s) + # 1. Primary: Sarvam AI Saaras v3 for Native Indian Languages (Ultra-fast Indic STT) if ( self.sarvam_key and self.sarvam_key.strip() @@ -65,12 +65,12 @@ class VoiceService: and lang_prefix in ["ta", "hi", "te", "kn", "bn", "mr", "gu", "ml", "pa", "od", "en"] ): try: - async with httpx2.AsyncClient(timeout=2.5) as client: + async with httpx2.AsyncClient(timeout=5.0) as client: files = { "file": (filename, audio_bytes, "audio/webm") } data = { - "model": "saaras:v2", + "model": "saaras:v3", "language_code": language_code, "with_diacritics": "true", }