ReportRaahat / backend /app /ml /openrouter.py
ReportRaahat CI
Deploy from GitHub: 7c7b0b67e1dae2679d91ef948e246730d7d10fbf
ee7023b
import os
import httpx
from app.ml.enhanced_chat import get_enhanced_mock_response, rag_retriever
try:
from app.ml.rag import retrieve_doctor_context
except ImportError:
retrieve_doctor_context = None
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
BASE_URL = "https://openrouter.ai/api/v1"
# Free models available on OpenRouter — fallback chain
MODELS = [
"deepseek/deepseek-chat-v3-0324:free",
"google/gemma-3-27b-it:free",
"meta-llama/llama-4-maverick:free",
]
def build_system_prompt(guc: dict) -> str:
"""
Builds the Dr. Raahat system prompt by injecting
the full Global User Context.
"""
name = guc.get("name", "Patient")
age = guc.get("age", "")
gender = guc.get("gender", "")
language = guc.get("language", "EN")
location = guc.get("location", "India")
report = guc.get("latestReport", {})
summary_en = report.get("overall_summary_english", "No report uploaded yet.")
organs = ", ".join(report.get("affected_organs", [])) or "None identified"
severity = report.get("severity_level", "NORMAL")
dietary_flags = ", ".join(report.get("dietary_flags", [])) or "None"
exercise_flags = ", ".join(report.get("exercise_flags", [])) or "None"
findings = report.get("findings", [])
abnormal = [
f"{f['parameter']}: {f['value']} {f['unit']} ({f['status']})"
for f in findings
if f.get("status") in ["HIGH", "LOW", "CRITICAL"]
]
abnormal_str = "\n".join(f" - {a}" for a in abnormal) or " - None"
medications = guc.get("medicationsActive", [])
meds_str = ", ".join(medications) if medications else "None reported"
allergy_flags = guc.get("allergyFlags", [])
allergies_str = ", ".join(allergy_flags) if allergy_flags else "None reported"
stress = guc.get("mentalWellness", {}).get("stressLevel", 5)
sleep = guc.get("mentalWellness", {}).get("sleepQuality", 5)
lang_instruction = (
"Always respond in Hindi (Devanagari script). "
"Use simple everyday Hindi words, not medical jargon."
if language == "HI"
else "Always respond in simple English."
)
empathy_note = (
"\nNOTE: This patient has high stress levels. "
"Be extra gentle, reassuring and empathetic in your responses. "
"Acknowledge their feelings before giving medical information."
if int(stress) <= 3 else ""
)
prompt = f"""You are Dr. Raahat, a friendly and empathetic Indian doctor. You speak both Hindi and English fluently.
PATIENT PROFILE:
- Name: {name}
- Age: {age}, Gender: {gender}
- Location: {location}
LATEST MEDICAL REPORT SUMMARY:
- Overall: {summary_en}
- Organs affected: {organs}
- Severity: {severity}
ABNORMAL FINDINGS:
{abnormal_str}
DIETARY FLAGS: {dietary_flags}
EXERCISE FLAGS: {exercise_flags}
ACTIVE MEDICATIONS: {meds_str}
ALLERGIES/RESTRICTIONS: {allergies_str}
STRESS LEVEL: {stress}/10 | SLEEP QUALITY: {sleep}/10
LANGUAGE: {lang_instruction}
{empathy_note}
IMPORTANT RULES:
- Never make up diagnoses or prescribe medications
- If asked something outside your knowledge, say "Please see a doctor in person for this"
- Always reference the patient's actual report data when answering
- Keep answers concise — 3-5 sentences maximum
- End every response with one actionable tip
- Be like a caring family doctor, not a cold clinical system
- Never create panic. Always give hope alongside facts."""
return prompt
def _call_openrouter(messages: list[dict]) -> str | None:
"""
Call OpenRouter API with the given messages.
Tries each model in MODELS until one succeeds.
Returns the reply string, or None on failure.
"""
if not OPENROUTER_API_KEY or OPENROUTER_API_KEY.startswith("placeholder"):
return None
headers = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
"HTTP-Referer": "https://reportraahat.app",
"X-Title": "ReportRaahat",
}
for model in MODELS:
try:
payload = {
"model": model,
"messages": messages,
"max_tokens": 500,
"temperature": 0.7,
}
with httpx.Client(timeout=30.0) as client:
resp = client.post(
f"{BASE_URL}/chat/completions",
headers=headers,
json=payload,
)
if resp.status_code == 200:
data = resp.json()
reply = data["choices"][0]["message"]["content"]
print(f"✅ OpenRouter reply via {model}: {len(reply)} chars")
return reply.strip()
else:
print(f"⚠️ OpenRouter {model} returned {resp.status_code}: {resp.text[:200]}")
continue
except Exception as e:
print(f"⚠️ OpenRouter {model} error: {e}")
continue
return None
def chat(
message: str,
history: list[dict],
guc: dict
) -> str:
"""
Send a message to Dr. Raahat via OpenRouter.
Injects GUC context + RAG-retrieved knowledge.
Falls back to enhanced mock responses if API fails.
"""
# Build system prompt with full GUC context
system_prompt = build_system_prompt(guc)
# Build conversation messages
messages = [{"role": "system", "content": system_prompt}]
# Add RAG-retrieved context if available
try:
if retrieve_doctor_context:
docs = retrieve_doctor_context(message, top_k=3)
if docs:
context = "\n".join(f"- {d['text']}" for d in docs)
messages.append({
"role": "system",
"content": f"Relevant medical knowledge:\n{context}"
})
except Exception as e:
print(f"⚠️ RAG retrieval failed: {e}")
# Add chat history
for msg in history[-10:]: # Last 10 messages for context
role = msg.get("role", "user")
content = msg.get("content", msg.get("text", ""))
if content:
messages.append({"role": role, "content": content})
# Add current message
messages.append({"role": "user", "content": message})
# Try OpenRouter API first
reply = _call_openrouter(messages)
if reply:
return reply
# Fallback to enhanced mock responses
print("⚠️ OpenRouter unavailable, using mock responses")
retrieved_docs = []
return get_enhanced_mock_response(message, guc, retrieved_docs)