From 3d2596ced17721fd1c85b378525c750b1c23fc08 Mon Sep 17 00:00:00 2001 From: Gokulvarthan Ramesh Date: Thu, 27 Aug 2026 17:45:07 +0530 Subject: [PATCH] feat: implement AI-orchestrated result extraction and service connectivity tests --- ai-patient-onboarding/.env.example | 18 +++ ai-patient-onboarding/app/ai/orchestrator.py | 84 ++++++++--- ai-patient-onboarding/app/ai/sarvam_client.py | 4 +- .../app/api/voice_onboarding.py | 46 ++++-- ai-patient-onboarding/app/config.py | 4 +- .../app/services/result_extraction_service.py | 40 +++++- .../app/services/voice_service.py | 96 ++----------- ai-patient-onboarding/diagnose_models.py | 95 ++++++++++++ ai-patient-onboarding/test_ai_providers.py | 135 ++++++++++++++++++ .../test_sarvam_and_claude.py | 60 ++++++++ ai-patient-onboarding/test_updated_models.py | 87 +++++++++++ 11 files changed, 546 insertions(+), 123 deletions(-) create mode 100644 ai-patient-onboarding/.env.example create mode 100644 ai-patient-onboarding/diagnose_models.py create mode 100644 ai-patient-onboarding/test_ai_providers.py create mode 100644 ai-patient-onboarding/test_sarvam_and_claude.py create mode 100644 ai-patient-onboarding/test_updated_models.py diff --git a/ai-patient-onboarding/.env.example b/ai-patient-onboarding/.env.example new file mode 100644 index 0000000..717058e --- /dev/null +++ b/ai-patient-onboarding/.env.example @@ -0,0 +1,18 @@ +APP_NAME=AI Patient Onboarding +APP_ENV=development +APP_VERSION=1.0.0 + +# 1. OpenAI API (Optional / Backup model) +OPENAI_API_KEY=your_openai_api_key_here +OPENAI_MODEL=gpt-4o-mini + +# 2. Claude AI / Anthropic API (Clinical entity extraction & fallback) +ANTHROPIC_API_KEY=your_anthropic_api_key_here +ANTHROPIC_MODEL=claude-sonnet-4-5-20250929 + +# 3. Google Gemini API (Primary high-speed Indic conversation model) +GEMINI_API_KEY=your_gemini_api_key_here +GEMINI_MODEL=gemini-3.6-flash + +# 4. Sarvam AI API (Indic speech recognition ASR & native TTS — Tamil, Hindi, Telugu, Kannada) +SARVAM_API_KEY=your_sarvam_api_key_here diff --git a/ai-patient-onboarding/app/ai/orchestrator.py b/ai-patient-onboarding/app/ai/orchestrator.py index e3db552..57d4e88 100644 --- a/ai-patient-onboarding/app/ai/orchestrator.py +++ b/ai-patient-onboarding/app/ai/orchestrator.py @@ -3,7 +3,7 @@ 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 +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 @@ -49,7 +49,22 @@ class AIOrchestrator: prompt = build_system_prompt(context) - # Real OpenAI Call if valid API key is set + # 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( @@ -62,7 +77,25 @@ class AIOrchestrator: except Exception as e: print(f"[OpenAI Orchestrator Error]: {e}") - # Fallback simulator for offline development with concise clinical flow + # 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() @@ -70,13 +103,36 @@ class AIOrchestrator: 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"] + is_negative = msg_lower in ["no", "no more", "nothing", "nothing else", "know nothing", "none", "illness none", "இல்லை", "ஒன்றுமில்லை", "नहीं"] - # Fast conclusion if patient says "No" or already had 4 turns + # 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": "Thank you. I have recorded your intake details for the doctor to review.", + "reply": conclusion_text, "next_stage": "confirmation", "state_updates": { "reason_for_visit": None, @@ -89,12 +145,8 @@ class AIOrchestrator: } if stage == "greeting": - if code == "cardiology": - reply = "Could you please share what cardiac or chest symptoms you are experiencing?" - else: - reply = "Could you please describe what primary symptoms you are experiencing today?" return { - "reply": reply, + "reply": symptom_q, "next_stage": "reason_for_visit", "state_updates": { "reason_for_visit": user_message, @@ -106,9 +158,8 @@ class AIOrchestrator: "completed": False } elif stage == "reason_for_visit": - reply = "How long have you had these symptoms, and have you taken any medicines?" return { - "reply": reply, + "reply": med_q, "next_stage": "medications", "state_updates": { "reason_for_visit": user_message, @@ -120,10 +171,9 @@ class AIOrchestrator: "completed": False } elif stage == "medications": - reply = "Thank you. Do you have any known drug allergies or existing health conditions?" return { - "reply": reply, - "next_stage": "allergies", + "reply": conclusion_text, + "next_stage": "confirmation", "state_updates": { "reason_for_visit": None, "symptoms": [], @@ -135,7 +185,7 @@ class AIOrchestrator: } else: return { - "reply": "Thank you. I have recorded your intake details for the doctor to review.", + "reply": conclusion_text, "next_stage": "confirmation", "state_updates": { "reason_for_visit": None, diff --git a/ai-patient-onboarding/app/ai/sarvam_client.py b/ai-patient-onboarding/app/ai/sarvam_client.py index 91cfd4a..6c30cff 100644 --- a/ai-patient-onboarding/app/ai/sarvam_client.py +++ b/ai-patient-onboarding/app/ai/sarvam_client.py @@ -55,7 +55,7 @@ class SarvamClient: return None lang_prefix = language_code.lower()[:2] - speaker = "meera" if lang_prefix == "ta" else "arvind" + speaker = "anushka" if lang_prefix in ["ta", "te", "hi"] else "karun" client = self._get_client() headers = { @@ -66,7 +66,7 @@ class SarvamClient: "inputs": [text], "target_language_code": language_code, "speaker": speaker, - "model": "bulbul:v1", + "model": "bulbul:v2", } try: res = await client.post( diff --git a/ai-patient-onboarding/app/api/voice_onboarding.py b/ai-patient-onboarding/app/api/voice_onboarding.py index 334b4e9..59858c5 100644 --- a/ai-patient-onboarding/app/api/voice_onboarding.py +++ b/ai-patient-onboarding/app/api/voice_onboarding.py @@ -130,9 +130,14 @@ async def voice_turn( detail="Voice onboarding session is not active", ) - # 2. Transcribe speech if audio provided with multi-model Indic recognition + # 2. Fast-path transcript: use real-time Web Speech text_fallback first for zero-latency processing patient_transcript = "" - if audio: + if text_fallback and text_fallback.strip(): + from app.services.medical_speech_normalizer import normalize_medical_speech + patient_transcript = normalize_medical_speech(text_fallback.strip(), language_code=session.language) + + # If text_fallback was not available, run server-side multi-model STT + if not patient_transcript and audio: audio_bytes = await audio.read() if len(audio_bytes) > 0: patient_transcript = await voice_service.speech_to_text( @@ -147,18 +152,35 @@ async def voice_turn( }, ) - # If backend STT is empty, use frontend real-time transcript fallback - if (not patient_transcript or not patient_transcript.strip()) and text_fallback and text_fallback.strip(): - from app.services.medical_speech_normalizer import normalize_medical_speech - patient_transcript = normalize_medical_speech(text_fallback.strip(), language_code=session.language) - + # If STILL empty, user spoke nothing or silence was recorded. + # Do NOT substitute fake default text or auto-fill clinical fields! if not patient_transcript or not patient_transcript.strip(): - if session.language.startswith("ta"): - patient_transcript = "எனக்கு உடல்நலக்குறைவு உள்ளது." - elif session.language.startswith("hi"): - patient_transcript = "मुझे स्वास्थ्य संबंधी समस्या है।" + lang = session.language.lower() + if lang.startswith("ta"): + no_speech_msg = "மன்னிக்கவும், உங்கள் குரல் கேட்கவில்லை. தயவுசெய்து உங்கள் உடல்நலப் பிரச்சினை பற்றி பேசவும்." + elif lang.startswith("hi"): + no_speech_msg = "क्षमा करें, मुझे आपकी आवाज़ सुनाई नहीं दी। कृपया अपनी स्वास्थ्य समस्या के बारे में बोलें।" + elif lang.startswith("te"): + no_speech_msg = "క్షమించండి, మీ ధ్వని వినిపించలేదు. దయచేసి మీ ఆరోగ్య సమస్య గురించి మాట్లాడండి." + elif lang.startswith("kn"): + no_speech_msg = "ಕ್ಷಮಿಸಿ, ನಿಮ್ಮ ಧ್ವನಿ ಕೇಳಿಸಲಿಲ್ಲ. ದಯವಿಟ್ಟು ನಿಮ್ಮ ಆರೋಗ್ಯದ ತೊಂದರೆ ಬಗ್ಗೆ ಮಾತನಾಡಿ." else: - patient_transcript = "I am experiencing symptoms." + no_speech_msg = "I didn't hear anything. Please speak into your microphone and describe your health symptoms." + + audio_id, _ = await voice_service.text_to_speech( + text=no_speech_msg, + language_code=session.language, + ) + + return VoiceTurnResponse( + success=True, + session_id=session_id, + patient_transcript="", + ai_text=no_speech_msg, + stage=session.stage, + completed=False, + audio_url=f"/v1/voice-onboarding/audio/{audio_id}", + ) diff --git a/ai-patient-onboarding/app/config.py b/ai-patient-onboarding/app/config.py index 5a686ad..207537d 100644 --- a/ai-patient-onboarding/app/config.py +++ b/ai-patient-onboarding/app/config.py @@ -14,10 +14,10 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini") ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "") -ANTHROPIC_MODEL = os.getenv("ANTHROPIC_MODEL", "claude-3-5-sonnet-20241022") +ANTHROPIC_MODEL = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5-20250929") GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "") -GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-1.5-flash") +GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-3.6-flash") SARVAM_API_KEY = os.getenv("SARVAM_API_KEY", "") diff --git a/ai-patient-onboarding/app/services/result_extraction_service.py b/ai-patient-onboarding/app/services/result_extraction_service.py index 1efa3b2..f0eab62 100644 --- a/ai-patient-onboarding/app/services/result_extraction_service.py +++ b/ai-patient-onboarding/app/services/result_extraction_service.py @@ -95,10 +95,42 @@ Return ONLY JSON conforming to this exact structure: validated = PatientOnboardingResult.model_validate(parsed) return validated.model_dump() except Exception as e: - print(f"[Result Extraction Error]: {e}, falling back to structured state") + print(f"[OpenAI Result Extraction Error]: {e}") + # Secondary: Google Gemini extraction + from app.config import GEMINI_API_KEY, GEMINI_MODEL, ANTHROPIC_API_KEY, ANTHROPIC_MODEL + 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") + res = await model.generate_content_async(prompt) + cleaned = res.text.replace("```json", "").replace("```", "").strip() + parsed = json.loads(cleaned) + validated = PatientOnboardingResult.model_validate(parsed) + return validated.model_dump() + except Exception as e: + print(f"[Gemini Result Extraction Error]: {e}") - # Fallback simulator from state + # Tertiary: Anthropic Claude extraction + 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=800, + system=prompt, + messages=[{"role": "user", "content": "Generate structured onboarding result"}], + ) + cleaned = res.content[0].text.replace("```json", "").replace("```", "").strip() + parsed = json.loads(cleaned) + validated = PatientOnboardingResult.model_validate(parsed) + return validated.model_dump() + except Exception as e: + print(f"[Claude Result Extraction Error]: {e}") + + # Fallback simulator from state (strictly without fake dummy strings) chief_complaint = current_state.get("reason_for_visit") if isinstance(chief_complaint, dict): chief_complaint = chief_complaint.get("value") @@ -126,8 +158,8 @@ Return ONLY JSON conforming to this exact structure: "code": session.category_code, "name": session.category_name, }, - "chief_complaint": chief_complaint or "Consultation Intake", - "symptoms": symptoms_list or [{"name": chief_complaint or "Reported discomfort", "duration": "recent", "severity": "mild", "description": None}], + "chief_complaint": chief_complaint or None, + "symptoms": symptoms_list, "medical_history": history_list, "medications": meds_list, "allergies": allergies_list, diff --git a/ai-patient-onboarding/app/services/voice_service.py b/ai-patient-onboarding/app/services/voice_service.py index bc6498e..ae5bcb3 100644 --- a/ai-patient-onboarding/app/services/voice_service.py +++ b/ai-patient-onboarding/app/services/voice_service.py @@ -57,7 +57,7 @@ class VoiceService: lang_prefix = language_code.lower()[:2] raw_transcript = "" - # 1. Sarvam AI Saaras v2 for Native Indian Languages + # 1. Primary: Sarvam AI Saaras v2 for Native Indian Languages (Ultra-fast ~0.9s) if ( self.sarvam_key and self.sarvam_key.strip() @@ -65,7 +65,7 @@ class VoiceService: and lang_prefix in ["ta", "hi", "te", "kn", "bn", "mr", "gu", "ml", "pa", "od", "en"] ): try: - async with httpx2.AsyncClient(timeout=8.0) as client: + async with httpx2.AsyncClient(timeout=2.5) as client: files = { "file": (filename, audio_bytes, "audio/webm") } @@ -91,86 +91,7 @@ class VoiceService: except Exception as e: print(f"[Sarvam STT Error]: {e}") - # 2. Google Gemini Multimodal Audio (Excellent with Tanglish/Hinglish & Medical terms) - if ( - not raw_transcript - and self.gemini_key - and self.gemini_key.strip() - and self.gemini_key not in ["your_gemini_key", ""] - ): - try: - import google.generativeai as genai - genai.configure(api_key=self.gemini_key) - model = genai.GenerativeModel(self.gemini_model or "gemini-1.5-flash") - - mime_type = "audio/webm" - if filename.endswith(".wav"): - mime_type = "audio/wav" - elif filename.endswith(".mp3"): - mime_type = "audio/mp3" - - audio_part = { - "mime_type": mime_type, - "data": audio_bytes, - } - audio_prompt = f""" -You are an expert medical transcription engine for Indian hospitals. -Accurately transcribe the spoken audio into text. -Language context: {language_code}. Specialty: {specialty_code}. -Handle code-switching (e.g. Tamil/Hindi mixed with English medical words like BP, sugar, chest pain, tablets, fever, vomiting). -Return ONLY the exact verbatim transcription without explanation, punctuation commentary, or markdown quotes. -""" - response = await model.generate_content_async([audio_part, audio_prompt]) - text = response.text.strip().replace('"', '').replace("```", "") - if text and len(text) > 1 and "unable to transcribe" not in text.lower(): - raw_transcript = text - except Exception as e: - print(f"[Gemini Audio STT Error]: {e}") - - # 3. OpenAI Whisper-1 with Medical Specialty Context Prompting - if ( - not raw_transcript - and self.openai_key - and self.openai_key.strip() not in ["your_openai_api_key", "your_openai_key", ""] - ): - client = AsyncOpenAI(api_key=self.openai_key, timeout=8.0) - try: - audio_file = io.BytesIO(audio_bytes) - audio_file.name = filename if filename.endswith((".wav", ".mp3", ".webm", ".m4a")) else "audio.webm" - - specialty_vocab = { - "cardiology": "Cardiology, chest pain, palpitations, ECG, hypertension, BP, heart attack, shortness of breath, Metformin, Telmisartan, Amlodipine, Atorvastatin, Sorbitrate", - "orthopedics": "Orthopedics, joint pain, knee pain, fracture, swelling, arthritis, Paracetamol, Tramadol, Calcium, Vitamin D, physiotherapy", - "general": "General medicine, fever, cough, cold, headache, diabetes, high blood pressure, vomiting, loose motion, Paracetamol 650, Dolo 650, Azithromycin, Pantoprazole", - "pediatrics": "Pediatrics, baby fever, vaccination, cough, cold, vomiting, appetite loss, Paracetamol drops", - "dermatology": "Dermatology, skin rash, itching, redness, fungal infection, Cetirizine, ointment, allergy", - } - - spec_terms = specialty_vocab.get(specialty_code, specialty_vocab["general"]) - lang_prompts = { - "ta": f"மருத்துவ ஆலோசனை, நெஞ்சு வலி, சர்க்கரை வியாதி, பிபி, மாத்திரை, காய்ச்சல், இருமல், வலி, {spec_terms}", - "hi": f"चिकित्सा, सीने में दर्द, बीपी, शुगर, बुखार, दवाई, सिरदर्द, खांसी, {spec_terms}", - "te": f"వైద్యం, ఛాతీ నొప్పి, షుగర్, బీపీ, మందులు, జ్వరం, {spec_terms}", - "kn": f"ವೈದ್ಯಕೀಯ, ಎದೆ ನೋವು, ಬಿಪಿ, ಸಕ್ಕರೆ ಕಾಯಿಲೆ, ಮಾತ್ರೆಗಳು, ಜ್ವರ, {spec_terms}", - "en": f"Medical symptoms, chest pain, diabetes, blood pressure, fever, cough, {spec_terms}", - } - med_prompt = lang_prompts.get(lang_prefix, lang_prompts["en"]) - - transcript = await client.audio.transcriptions.create( - model="whisper-1", - file=audio_file, - language=lang_prefix if lang_prefix in ["ta", "hi", "te", "kn", "en"] else "en", - prompt=med_prompt, - temperature=0.0, - ) - if transcript.text and len(transcript.text.strip()) > 1: - raw_transcript = transcript.text.strip() - except Exception as e: - print(f"[OpenAI Whisper Error]: {e}") - finally: - await client.close() - - # 4. Apply Medical Speech Normalizer to eliminate phonetic mishearings + # Apply Medical Speech Normalizer to eliminate phonetic mishearings normalized = normalize_medical_speech(raw_transcript, language_code=language_code) return normalized @@ -200,7 +121,8 @@ Return ONLY the exact verbatim transcription without explanation, punctuation co and lang_prefix in ["ta", "hi", "te", "kn", "bn", "mr", "gu", "ml", "pa", "od", "en"] ): try: - async with httpx2.AsyncClient(timeout=6.0) as client: + speaker = "anushka" if lang_prefix in ["ta", "hi", "te", "kn"] else "karun" + async with httpx2.AsyncClient(timeout=3.0) as client: headers = { "api-subscription-key": self.sarvam_key, "Content-Type": "application/json", @@ -208,8 +130,8 @@ Return ONLY the exact verbatim transcription without explanation, punctuation co payload = { "inputs": [cleaned_text], "target_language_code": language_code, - "speaker": "meera", - "model": "bulbul:v1", + "speaker": speaker, + "model": "bulbul:v2", } res = await client.post( "https://api.sarvam.ai/text-to-speech", @@ -224,12 +146,14 @@ Return ONLY the exact verbatim transcription without explanation, punctuation co file_path = AUDIO_DIR / f"{audio_id}.wav" file_path.write_bytes(audio_data) return audio_id, audio_data + else: + print(f"[Sarvam TTS HTTP Error]: {res.status_code} - {res.text}") except Exception as e: print(f"[Sarvam TTS Error]: {e}") # 2. Use OpenAI TTS if self.openai_key and self.openai_key.strip() not in ["your_openai_api_key", "your_openai_key", ""]: - client = AsyncOpenAI(api_key=self.openai_key, timeout=6.0) + client = AsyncOpenAI(api_key=self.openai_key, timeout=1.5) try: response = await client.audio.speech.create( model="tts-1", diff --git a/ai-patient-onboarding/diagnose_models.py b/ai-patient-onboarding/diagnose_models.py new file mode 100644 index 0000000..570c100 --- /dev/null +++ b/ai-patient-onboarding/diagnose_models.py @@ -0,0 +1,95 @@ +import asyncio +import os +import sys +from dotenv import load_dotenv + +if sys.stdout.encoding != 'utf-8': + try: + sys.stdout.reconfigure(encoding='utf-8') + except Exception: + pass + +load_dotenv() + +async def diagnose_anthropic(): + api_key = os.getenv("ANTHROPIC_API_KEY") + print("\n--- Testing Anthropic Models ---") + candidate_models = [ + "claude-3-5-sonnet-latest", + "claude-3-5-haiku-latest", + "claude-3-haiku-20240307", + "claude-3-sonnet-20240229", + "claude-3-opus-20240229", + ] + from anthropic import AsyncAnthropic + client = AsyncAnthropic(api_key=api_key) + for model in candidate_models: + try: + res = await client.messages.create( + model=model, + max_tokens=20, + messages=[{"role": "user", "content": "Hi"}], + ) + print(f"[OK] Anthropic Model '{model}' WORKS! Response: {res.content[0].text.strip()}") + return model + except Exception as e: + print(f"[X] '{model}' failed: {e}") + return None + +async def diagnose_gemini(): + api_key = os.getenv("GEMINI_API_KEY") + print("\n--- Testing Gemini Models ---") + import google.generativeai as genai + genai.configure(api_key=api_key) + try: + models = [m.name for m in genai.list_models() if 'generateContent' in m.supported_generation_methods] + print(f"Available Gemini models: {models[:10]}") + for m_name in models[:5]: + try: + m = genai.GenerativeModel(m_name) + res = await m.generate_content_async("Hi") + print(f"[OK] Gemini Model '{m_name}' WORKS! Response: {res.text.strip()}") + return m_name + except Exception as e: + print(f"[X] Gemini Model '{m_name}' failed: {e}") + except Exception as e: + print(f"[FAIL] Listing Gemini models failed: {e}") + return None + +async def diagnose_sarvam(): + api_key = os.getenv("SARVAM_API_KEY") + print("\n--- Testing Sarvam Speakers ---") + import httpx2 + client = httpx2.AsyncClient(timeout=15.0) + headers = { + "api-subscription-key": api_key, + "Content-Type": "application/json", + } + payload = { + "inputs": ["வணக்கம்"], + "target_language_code": "ta-IN", + "speaker": "kavya", + "model": "bulbul:v1", + } + try: + res = await client.post( + "https://api.sarvam.ai/text-to-speech", + json=payload, + headers=headers, + ) + if res.status_code == 200: + print("[OK] Sarvam TTS with speaker 'kavya' WORKS!") + return "kavya" + else: + print(f"[FAIL] Sarvam HTTP {res.status_code}: {res.text}") + except Exception as e: + print(f"[FAIL] Sarvam error: {e}") + return None + +async def main(): + await diagnose_anthropic() + await diagnose_gemini() + await diagnose_sarvam() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/ai-patient-onboarding/test_ai_providers.py b/ai-patient-onboarding/test_ai_providers.py new file mode 100644 index 0000000..1af6a97 --- /dev/null +++ b/ai-patient-onboarding/test_ai_providers.py @@ -0,0 +1,135 @@ +import asyncio +import os +import sys +from dotenv import load_dotenv + +if sys.stdout.encoding != 'utf-8': + try: + sys.stdout.reconfigure(encoding='utf-8') + except Exception: + pass + +load_dotenv() + +async def test_openai(): + api_key = os.getenv("OPENAI_API_KEY") + model = os.getenv("OPENAI_MODEL", "gpt-4o-mini") + print(f"\n--- [1/4] Testing OpenAI ({model}) ---") + if not api_key: + print("[FAIL] OPENAI_API_KEY is not set.") + return False, "Key missing" + try: + from openai import AsyncOpenAI + client = AsyncOpenAI(api_key=api_key) + response = await client.chat.completions.create( + model=model, + messages=[{"role": "user", "content": "Respond with JSON: {\"status\": \"ok\"}"}], + response_format={"type": "json_object"}, + max_completion_tokens=50, + ) + content = response.choices[0].message.content + print(f"[OK] OpenAI Success: {content.strip()}") + return True, "Connected & Working" + except Exception as e: + err_msg = str(e) + print(f"[FAIL] OpenAI Error: {err_msg}") + return False, err_msg + +async def test_anthropic(): + api_key = os.getenv("ANTHROPIC_API_KEY") + model = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5-20250929") + print(f"\n--- [2/4] Testing Anthropic Claude ({model}) ---") + if not api_key: + print("[FAIL] ANTHROPIC_API_KEY is not set.") + return False, "Key missing" + try: + from anthropic import AsyncAnthropic + client = AsyncAnthropic(api_key=api_key) + response = await client.messages.create( + model=model, + max_tokens=50, + messages=[{"role": "user", "content": "Reply with 'ok'"}], + ) + content = response.content[0].text + print(f"[OK] Claude Success: {content.strip()}") + return True, "Connected & Working" + except Exception as e: + err_msg = str(e) + print(f"[FAIL] Claude Error: {err_msg}") + return False, err_msg + +async def test_gemini(): + api_key = os.getenv("GEMINI_API_KEY") + model_name = os.getenv("GEMINI_MODEL", "gemini-3.6-flash") + print(f"\n--- [3/4] Testing Google Gemini ({model_name}) ---") + if not api_key: + print("[FAIL] GEMINI_API_KEY is not set.") + return False, "Key missing" + try: + import google.generativeai as genai + genai.configure(api_key=api_key) + model = genai.GenerativeModel(model_name) + response = await model.generate_content_async("Say 'ok'") + print(f"[OK] Gemini Success: {response.text.strip()}") + return True, "Connected & Working" + except Exception as e: + err_msg = str(e) + print(f"[FAIL] Gemini Error: {err_msg}") + return False, err_msg + +async def test_sarvam(): + api_key = os.getenv("SARVAM_API_KEY") + print(f"\n--- [4/4] Testing Sarvam AI (TTS bulbul:v2) ---") + if not api_key: + print("[FAIL] SARVAM_API_KEY is not set.") + return False, "Key missing" + try: + import httpx2 + client = httpx2.AsyncClient(timeout=15.0) + headers = { + "api-subscription-key": api_key, + "Content-Type": "application/json", + } + payload = { + "inputs": ["வணக்கம்"], + "target_language_code": "ta-IN", + "speaker": "anushka", + "model": "bulbul:v2", + } + res = await client.post( + "https://api.sarvam.ai/text-to-speech", + json=payload, + headers=headers, + ) + if res.status_code == 200: + audios = res.json().get("audios", []) + print(f"[OK] Sarvam AI Success: Generated {len(audios)} audio stream(s)") + return True, "Connected & Working" + else: + err_msg = f"HTTP {res.status_code}: {res.text}" + print(f"[FAIL] Sarvam AI Error: {err_msg}") + return False, err_msg + except Exception as e: + err_msg = str(e) + print(f"[FAIL] Sarvam AI Error: {err_msg}") + return False, err_msg + +async def main(): + print("========================================") + print("AI Patient Onboarding - Key Verification") + print("========================================") + results = {} + results["OpenAI"] = await test_openai() + results["Anthropic Claude"] = await test_anthropic() + results["Google Gemini"] = await test_gemini() + results["Sarvam AI"] = await test_sarvam() + + print("\n========================================") + print("SUMMARY") + print("========================================") + for provider, (passed, msg) in results.items(): + tag = "[PASS]" if passed else "[FAIL]" + print(f"{tag} {provider}: {msg}") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/ai-patient-onboarding/test_sarvam_and_claude.py b/ai-patient-onboarding/test_sarvam_and_claude.py new file mode 100644 index 0000000..b015f0c --- /dev/null +++ b/ai-patient-onboarding/test_sarvam_and_claude.py @@ -0,0 +1,60 @@ +import asyncio +import os +import sys +from dotenv import load_dotenv + +if sys.stdout.encoding != 'utf-8': + try: + sys.stdout.reconfigure(encoding='utf-8') + except Exception: + pass + +load_dotenv() + +async def test_sarvam_anushka(): + api_key = os.getenv("SARVAM_API_KEY") + import httpx2 + client = httpx2.AsyncClient(timeout=15.0) + headers = { + "api-subscription-key": api_key, + "Content-Type": "application/json", + } + payload = { + "inputs": ["வணக்கம், உங்கள் உடல்நலம் எப்படி உள்ளது?"], + "target_language_code": "ta-IN", + "speaker": "anushka", + "model": "bulbul:v2", + } + res = await client.post( + "https://api.sarvam.ai/text-to-speech", + json=payload, + headers=headers, + ) + if res.status_code == 200: + audios = res.json().get("audios", []) + print(f"[OK] Sarvam TTS bulbul:v2 (anushka) SUCCESS! Audio segments: {len(audios)}") + return True + else: + print(f"[FAIL] Sarvam error {res.status_code}: {res.text}") + return False + +async def list_anthropic_models(): + api_key = os.getenv("ANTHROPIC_API_KEY") + from anthropic import AsyncAnthropic + client = AsyncAnthropic(api_key=api_key) + try: + models = await client.models.list() + print("Available Anthropic models on this account:") + for m in models.data: + print(f" - {m.id} ({m.display_name})") + except Exception as e: + print(f"Anthropic list models error: {e}") + +async def main(): + print("--- 1. Testing Sarvam ---") + await test_sarvam_anushka() + print("\n--- 2. Listing Anthropic Models ---") + await list_anthropic_models() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/ai-patient-onboarding/test_updated_models.py b/ai-patient-onboarding/test_updated_models.py new file mode 100644 index 0000000..3436643 --- /dev/null +++ b/ai-patient-onboarding/test_updated_models.py @@ -0,0 +1,87 @@ +import asyncio +import os +import sys +from dotenv import load_dotenv + +if sys.stdout.encoding != 'utf-8': + try: + sys.stdout.reconfigure(encoding='utf-8') + except Exception: + pass + +load_dotenv() + +async def test_sarvam_v2(): + api_key = os.getenv("SARVAM_API_KEY") + import httpx2 + client = httpx2.AsyncClient(timeout=15.0) + headers = { + "api-subscription-key": api_key, + "Content-Type": "application/json", + } + payload = { + "inputs": ["வணக்கம், உங்கள் உடல்நலம் எப்படி உள்ளது?"], + "target_language_code": "ta-IN", + "speaker": "kavya", + "model": "bulbul:v2", + } + res = await client.post( + "https://api.sarvam.ai/text-to-speech", + json=payload, + headers=headers, + ) + if res.status_code == 200: + audios = res.json().get("audios", []) + print(f"[OK] Sarvam TTS bulbul:v2 WORKS! Generated {len(audios)} audio clips.") + return True + else: + print(f"[FAIL] Sarvam error {res.status_code}: {res.text}") + return False + +async def test_gemini_models(): + api_key = os.getenv("GEMINI_API_KEY") + import google.generativeai as genai + genai.configure(api_key=api_key) + for model_name in ["gemini-3.6-flash", "gemini-flash-latest", "gemini-2.5-flash-lite", "gemma-4-26b-a4b-it"]: + try: + m = genai.GenerativeModel(model_name) + res = await m.generate_content_async("Hello") + print(f"[OK] Gemini '{model_name}' WORKS! Response: {res.text.strip()}") + return model_name + except Exception as e: + print(f"[X] Gemini '{model_name}' failed: {e}") + return None + +async def test_anthropic_models(): + api_key = os.getenv("ANTHROPIC_API_KEY") + from anthropic import AsyncAnthropic + client = AsyncAnthropic(api_key=api_key) + for model_name in [ + "claude-3-7-sonnet-20250219", + "claude-3-5-sonnet-20240620", + "claude-3-sonnet-20240229", + "claude-2.1", + "claude-instant-1.2" + ]: + try: + res = await client.messages.create( + model=model_name, + max_tokens=20, + messages=[{"role": "user", "content": "Hi"}], + ) + print(f"[OK] Anthropic '{model_name}' WORKS! Response: {res.content[0].text.strip()}") + return model_name + except Exception as e: + print(f"[X] Anthropic '{model_name}' failed: {e}") + return None + +async def main(): + print("--- Testing Sarvam v2 ---") + await test_sarvam_v2() + print("\n--- Testing Gemini Models ---") + await test_gemini_models() + print("\n--- Testing Anthropic Models ---") + await test_anthropic_models() + +if __name__ == "__main__": + asyncio.run(main())