feat: implement AI-orchestrated result extraction and service connectivity tests

This commit is contained in:
2026-08-27 17:45:07 +05:30
parent 59c1372fe3
commit 3d2596ced1
11 changed files with 546 additions and 123 deletions
+18
View File
@@ -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
+67 -17
View File
@@ -3,7 +3,7 @@ import json
from app.ai.openai_client import OpenAIClient from app.ai.openai_client import OpenAIClient
from app.ai.prompts import build_system_prompt from app.ai.prompts import build_system_prompt
from app.ai.validator import validate_ai_response 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 from app.specialties.registry import get_specialty_config
@@ -49,7 +49,22 @@ class AIOrchestrator:
prompt = build_system_prompt(context) 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", ""]: if OPENAI_API_KEY and OPENAI_API_KEY.strip() not in ["your_openai_api_key", "your_openai_key", ""]:
try: try:
raw_response = await self.openai.generate_response( raw_response = await self.openai.generate_response(
@@ -62,7 +77,25 @@ class AIOrchestrator:
except Exception as e: except Exception as e:
print(f"[OpenAI Orchestrator Error]: {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)) parsed = self._simulate_specialty_response(session, specialty, user_message, len(conversation_history))
validated = validate_ai_response(parsed) validated = validate_ai_response(parsed)
return validated.model_dump() return validated.model_dump()
@@ -70,13 +103,36 @@ class AIOrchestrator:
def _simulate_specialty_response(self, session, specialty, user_message, turn_count=0): def _simulate_specialty_response(self, session, specialty, user_message, turn_count=0):
stage = session.stage stage = session.stage
code = specialty.get("code", "general") code = specialty.get("code", "general")
lang = (session.language or "ta-IN").lower()
msg_lower = user_message.lower().strip() 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: if is_negative or turn_count >= 6:
return { return {
"reply": "Thank you. I have recorded your intake details for the doctor to review.", "reply": conclusion_text,
"next_stage": "confirmation", "next_stage": "confirmation",
"state_updates": { "state_updates": {
"reason_for_visit": None, "reason_for_visit": None,
@@ -89,12 +145,8 @@ class AIOrchestrator:
} }
if stage == "greeting": 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 { return {
"reply": reply, "reply": symptom_q,
"next_stage": "reason_for_visit", "next_stage": "reason_for_visit",
"state_updates": { "state_updates": {
"reason_for_visit": user_message, "reason_for_visit": user_message,
@@ -106,9 +158,8 @@ class AIOrchestrator:
"completed": False "completed": False
} }
elif stage == "reason_for_visit": elif stage == "reason_for_visit":
reply = "How long have you had these symptoms, and have you taken any medicines?"
return { return {
"reply": reply, "reply": med_q,
"next_stage": "medications", "next_stage": "medications",
"state_updates": { "state_updates": {
"reason_for_visit": user_message, "reason_for_visit": user_message,
@@ -120,10 +171,9 @@ class AIOrchestrator:
"completed": False "completed": False
} }
elif stage == "medications": elif stage == "medications":
reply = "Thank you. Do you have any known drug allergies or existing health conditions?"
return { return {
"reply": reply, "reply": conclusion_text,
"next_stage": "allergies", "next_stage": "confirmation",
"state_updates": { "state_updates": {
"reason_for_visit": None, "reason_for_visit": None,
"symptoms": [], "symptoms": [],
@@ -135,7 +185,7 @@ class AIOrchestrator:
} }
else: else:
return { return {
"reply": "Thank you. I have recorded your intake details for the doctor to review.", "reply": conclusion_text,
"next_stage": "confirmation", "next_stage": "confirmation",
"state_updates": { "state_updates": {
"reason_for_visit": None, "reason_for_visit": None,
@@ -55,7 +55,7 @@ class SarvamClient:
return None return None
lang_prefix = language_code.lower()[:2] 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() client = self._get_client()
headers = { headers = {
@@ -66,7 +66,7 @@ class SarvamClient:
"inputs": [text], "inputs": [text],
"target_language_code": language_code, "target_language_code": language_code,
"speaker": speaker, "speaker": speaker,
"model": "bulbul:v1", "model": "bulbul:v2",
} }
try: try:
res = await client.post( res = await client.post(
@@ -130,9 +130,14 @@ async def voice_turn(
detail="Voice onboarding session is not active", 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 = "" 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() audio_bytes = await audio.read()
if len(audio_bytes) > 0: if len(audio_bytes) > 0:
patient_transcript = await voice_service.speech_to_text( 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 STILL empty, user spoke nothing or silence was recorded.
if (not patient_transcript or not patient_transcript.strip()) and text_fallback and text_fallback.strip(): # Do NOT substitute fake default text or auto-fill clinical fields!
from app.services.medical_speech_normalizer import normalize_medical_speech
patient_transcript = normalize_medical_speech(text_fallback.strip(), language_code=session.language)
if not patient_transcript or not patient_transcript.strip(): if not patient_transcript or not patient_transcript.strip():
if session.language.startswith("ta"): lang = session.language.lower()
patient_transcript = "எனக்கு உடல்நலக்குறைவு உள்ளது." if lang.startswith("ta"):
elif session.language.startswith("hi"): no_speech_msg = "மன்னிக்கவும், உங்கள் குரல் கேட்கவில்லை. தயவுசெய்து உங்கள் உடல்நலப் பிரச்சினை பற்றி பேசவும்."
patient_transcript = "मुझे स्वास्थ्य संबंधी समस्या है।" elif lang.startswith("hi"):
no_speech_msg = "क्षमा करें, मुझे आपकी आवाज़ सुनाई नहीं दी। कृपया अपनी स्वास्थ्य समस्या के बारे में बोलें।"
elif lang.startswith("te"):
no_speech_msg = "క్షమించండి, మీ ధ్వని వినిపించలేదు. దయచేసి మీ ఆరోగ్య సమస్య గురించి మాట్లాడండి."
elif lang.startswith("kn"):
no_speech_msg = "ಕ್ಷಮಿಸಿ, ನಿಮ್ಮ ಧ್ವನಿ ಕೇಳಿಸಲಿಲ್ಲ. ದಯವಿಟ್ಟು ನಿಮ್ಮ ಆರೋಗ್ಯದ ತೊಂದರೆ ಬಗ್ಗೆ ಮಾತನಾಡಿ."
else: 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}",
)
+2 -2
View File
@@ -14,10 +14,10 @@ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini") OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "") 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_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", "") SARVAM_API_KEY = os.getenv("SARVAM_API_KEY", "")
@@ -95,10 +95,42 @@ Return ONLY JSON conforming to this exact structure:
validated = PatientOnboardingResult.model_validate(parsed) validated = PatientOnboardingResult.model_validate(parsed)
return validated.model_dump() return validated.model_dump()
except Exception as e: 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") chief_complaint = current_state.get("reason_for_visit")
if isinstance(chief_complaint, dict): if isinstance(chief_complaint, dict):
chief_complaint = chief_complaint.get("value") chief_complaint = chief_complaint.get("value")
@@ -126,8 +158,8 @@ Return ONLY JSON conforming to this exact structure:
"code": session.category_code, "code": session.category_code,
"name": session.category_name, "name": session.category_name,
}, },
"chief_complaint": chief_complaint or "Consultation Intake", "chief_complaint": chief_complaint or None,
"symptoms": symptoms_list or [{"name": chief_complaint or "Reported discomfort", "duration": "recent", "severity": "mild", "description": None}], "symptoms": symptoms_list,
"medical_history": history_list, "medical_history": history_list,
"medications": meds_list, "medications": meds_list,
"allergies": allergies_list, "allergies": allergies_list,
@@ -57,7 +57,7 @@ class VoiceService:
lang_prefix = language_code.lower()[:2] lang_prefix = language_code.lower()[:2]
raw_transcript = "" 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 ( if (
self.sarvam_key self.sarvam_key
and self.sarvam_key.strip() 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"] and lang_prefix in ["ta", "hi", "te", "kn", "bn", "mr", "gu", "ml", "pa", "od", "en"]
): ):
try: try:
async with httpx2.AsyncClient(timeout=8.0) as client: async with httpx2.AsyncClient(timeout=2.5) as client:
files = { files = {
"file": (filename, audio_bytes, "audio/webm") "file": (filename, audio_bytes, "audio/webm")
} }
@@ -91,86 +91,7 @@ class VoiceService:
except Exception as e: except Exception as e:
print(f"[Sarvam STT Error]: {e}") print(f"[Sarvam STT Error]: {e}")
# 2. Google Gemini Multimodal Audio (Excellent with Tanglish/Hinglish & Medical terms) # Apply Medical Speech Normalizer to eliminate phonetic mishearings
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
normalized = normalize_medical_speech(raw_transcript, language_code=language_code) normalized = normalize_medical_speech(raw_transcript, language_code=language_code)
return normalized 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"] and lang_prefix in ["ta", "hi", "te", "kn", "bn", "mr", "gu", "ml", "pa", "od", "en"]
): ):
try: 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 = { headers = {
"api-subscription-key": self.sarvam_key, "api-subscription-key": self.sarvam_key,
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -208,8 +130,8 @@ Return ONLY the exact verbatim transcription without explanation, punctuation co
payload = { payload = {
"inputs": [cleaned_text], "inputs": [cleaned_text],
"target_language_code": language_code, "target_language_code": language_code,
"speaker": "meera", "speaker": speaker,
"model": "bulbul:v1", "model": "bulbul:v2",
} }
res = await client.post( res = await client.post(
"https://api.sarvam.ai/text-to-speech", "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 = AUDIO_DIR / f"{audio_id}.wav"
file_path.write_bytes(audio_data) file_path.write_bytes(audio_data)
return audio_id, audio_data return audio_id, audio_data
else:
print(f"[Sarvam TTS HTTP Error]: {res.status_code} - {res.text}")
except Exception as e: except Exception as e:
print(f"[Sarvam TTS Error]: {e}") print(f"[Sarvam TTS Error]: {e}")
# 2. Use OpenAI TTS # 2. Use OpenAI TTS
if self.openai_key and self.openai_key.strip() not in ["your_openai_api_key", "your_openai_key", ""]: 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: try:
response = await client.audio.speech.create( response = await client.audio.speech.create(
model="tts-1", model="tts-1",
+95
View File
@@ -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())
+135
View File
@@ -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())
@@ -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())
@@ -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())