feat: complete production hardening implementation, specialty canonicalization, google-genai migration, voice reliability, sqlite concurrency, and test suite consolidation
This commit is contained in:
@@ -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 ---")
|
||||
from google import 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())
|
||||
@@ -0,0 +1,37 @@
|
||||
import httpx
|
||||
|
||||
def main():
|
||||
client = httpx.Client(timeout=60.0)
|
||||
|
||||
start_res = client.post('http://127.0.0.1:8000/v1/voice-onboarding/start', json={
|
||||
'patient': {'id': 'PAT-INTAKE-5', 'first_name': 'Arun', 'last_name': 'Kumar'},
|
||||
'category': {'type': 'general', 'code': 'general_medicine', 'name': 'General Medicine'},
|
||||
'language': 'en-IN'
|
||||
}).json()
|
||||
|
||||
sid = start_res['session_id']
|
||||
print("=== DEBUGGING 5-STAGE DIALOGUE ===")
|
||||
print("Session ID:", sid)
|
||||
|
||||
dialogue = [
|
||||
"Chest pain and shortness of breath",
|
||||
"2 days, pain severity is 8 out of 10",
|
||||
"Yes, sweating and dizziness",
|
||||
"High blood pressure and Diabetes",
|
||||
"Taking Amlodipine, no drug allergies"
|
||||
]
|
||||
|
||||
for i, turn_input in enumerate(dialogue, 1):
|
||||
res = client.post(f"http://127.0.0.1:8000/v1/voice-onboarding/{sid}/turn", data={"text_fallback": turn_input})
|
||||
print(f"Turn {i} Status Code: {res.status_code}")
|
||||
if res.status_code != 200:
|
||||
print("FULL ERROR BODY:", res.text)
|
||||
break
|
||||
data = res.json()
|
||||
print(f"Turn {i} Input: '{turn_input}'")
|
||||
print(f" --> Next Stage: {data['stage']} | Completed: {data['completed']}")
|
||||
print("-" * 70)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -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:
|
||||
from google import 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,35 @@
|
||||
import httpx
|
||||
|
||||
def main():
|
||||
client = httpx.Client(timeout=30.0)
|
||||
|
||||
test_cases = [
|
||||
("Cardiology Test", "எனக்கு நெஞ்சு வலி மற்றும் மூச்சு திணறல் இருக்கு"),
|
||||
("Dermatology Test", "தோல் அரிப்பு மற்றும் படை உள்ளது"),
|
||||
("Orthopedics Test", "மூட்டு வலி நடக்க முடியல"),
|
||||
("Neurology Test", "headache and dizziness for 3 days"),
|
||||
("Gastroenterology Test", "வயிற்று வலி மற்றும் வாந்தி"),
|
||||
("Pulmonology Test", "இருமல் மற்றும் சளி அதிகமாக உள்ளது"),
|
||||
("ENT Test", "காது வலி மற்றும் தொண்டை வலி")
|
||||
]
|
||||
|
||||
print("=== AUTOMATIC SPECIALTY TRIAGE ENGINE TEST ===")
|
||||
for label, symptom in test_cases:
|
||||
start_res = client.post("http://127.0.0.1:8000/v1/voice-onboarding/start", json={
|
||||
"patient": {"id": "PAT-AUTO", "first_name": "Arun", "last_name": "Kumar"},
|
||||
"category": {"type": "general", "code": "general_medicine", "name": "General Medicine"},
|
||||
"language": "ta-IN"
|
||||
})
|
||||
sid = start_res.json()["session_id"]
|
||||
|
||||
turn_res = client.post(f"http://127.0.0.1:8000/v1/voice-onboarding/{sid}/turn", data={"text_fallback": symptom})
|
||||
spec = turn_res.json().get("suggested_specialty", {})
|
||||
|
||||
print(f"Label: {label}")
|
||||
print(f" Patient Symptom: '{symptom}'")
|
||||
print(f" --> Auto-Assigned Specialty: {spec.get('name')} (Code: {spec.get('code')})")
|
||||
print(f" --> Reasoning: {spec.get('reason')}\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,16 @@
|
||||
from app.ai.orchestrator import classify_specialty_from_symptoms
|
||||
|
||||
test_cases = [
|
||||
("Cardiology", "நெஞ்சு வலி மற்றும் மூச்சு திணறல்"),
|
||||
("Dermatology", "தோல் அரிப்பு மற்றும் படை"),
|
||||
("Orthopedics", "மூட்டு வலி"),
|
||||
("Neurology", "headache and dizziness"),
|
||||
("Gastroenterology", "வயிற்று வலி மற்றும் வாந்தி"),
|
||||
("Pulmonology", "இருமல் மற்றும் சளி"),
|
||||
("ENT", "காது வலி மற்றும் தொண்டை வலி")
|
||||
]
|
||||
|
||||
print("=== DIRECT SYMPTOM SPECIALTY CLASSIFIER TEST ===")
|
||||
for label, text in test_cases:
|
||||
res = classify_specialty_from_symptoms(text)
|
||||
print("Test:", label, "| Code:", res["code"], "| Specialty:", res["name"])
|
||||
@@ -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")
|
||||
from google import 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())
|
||||
Reference in New Issue
Block a user