clinical-mind / backend /app /api /agents.py
Claude
Fix 500 error on /api/agents/action and NaN in vitals sparklines
c9e706e unverified
"""API routes for the multi-agent hospital simulation."""
import logging
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import Optional
from app.core.agents.orchestrator import orchestrator
from app.core.rag.shared import case_generator
logger = logging.getLogger(__name__)
router = APIRouter()
# --- Request Models ---
class InitializeRequest(BaseModel):
case_id: str
student_level: str = "intern"
hospital_setting: str = "medical_college"
class AgentActionRequest(BaseModel):
session_id: str
action_type: str
student_input: Optional[str] = None
class TreatmentRequest(BaseModel):
session_id: str
treatment: str
class AdvanceTimeRequest(BaseModel):
session_id: str
minutes: int = 30
# --- Endpoints ---
@router.post("/initialize")
async def initialize_agents(request: InitializeRequest):
"""Initialize multi-agent simulation session for a case.
Accepts student_level to calibrate teaching intensity:
- mbbs_2nd: Pre-clinical, more guidance
- mbbs_3rd: Clinical posting, standard
- intern: Hands-on, less hand-holding
- pg_aspirant: NEET-PG focus, exam patterns
- pg_resident: Advanced, minimal guidance
Returns initial messages from patient, nurse, and senior doctor,
plus simulation state (vitals, timeline, investigations).
"""
case = case_generator.get_case(request.case_id)
if not case:
raise HTTPException(status_code=404, detail="Case not found")
result = orchestrator.initialize_session(
case_data=case,
student_level=request.student_level,
hospital_setting=request.hospital_setting,
)
return result
@router.post("/action")
async def agent_action(request: AgentActionRequest):
"""Process a student action through the simulation pipeline.
action_type options:
- talk_to_patient: Talk to the patient (family may interject)
- ask_nurse: Ask Nurse Priya
- consult_senior: Consult Dr. Sharma (Socratic teaching)
- talk_to_family: Talk to patient's family member (cultural context, Hinglish)
- ask_lab: Talk to Lab Tech Ramesh (investigation status, sample info)
- examine_patient: Perform physical examination (triggers exam modal)
- order_investigation: Order tests (Lab Tech processes, realistic delays)
- order_treatment: Order treatment (safety validated, effects modeled)
- team_huddle: All 5 agents discuss the case together
Each action advances the simulation clock and checks the complication engine.
Returns agent responses, updated vitals, timeline events, investigation status,
and any triggered complications.
"""
try:
result = orchestrator.process_action(
session_id=request.session_id,
action_type=request.action_type,
student_input=request.student_input,
)
except Exception as e:
logger.error(f"process_action failed for session={request.session_id}, action={request.action_type}: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=f"Internal error processing action: {e}")
if "error" in result:
raise HTTPException(status_code=404, detail=result["error"])
return result
@router.post("/advance-time")
async def advance_time(request: AdvanceTimeRequest):
"""Advance simulation time (e.g., waiting for investigation results).
Evolves vitals, checks for ready investigations, triggers events.
"""
result = orchestrator.advance_time(
session_id=request.session_id,
minutes=request.minutes,
)
if "error" in result:
raise HTTPException(status_code=404, detail=result["error"])
return result
@router.get("/vitals/{session_id}")
async def get_vitals(session_id: str):
"""Get current vital signs with trends and trajectory."""
vitals = orchestrator.get_session_vitals(session_id)
if not vitals:
raise HTTPException(status_code=404, detail="Session not found")
return vitals
@router.get("/investigations/{session_id}")
async def get_investigations(session_id: str):
"""Get status of all ordered investigations."""
investigations = orchestrator.get_investigation_status(session_id)
if investigations is None:
raise HTTPException(status_code=404, detail="Session not found")
return {"investigations": investigations}
@router.get("/timeline/{session_id}")
async def get_timeline(session_id: str):
"""Get complete simulation timeline."""
timeline = orchestrator.get_timeline(session_id)
if timeline is None:
raise HTTPException(status_code=404, detail="Session not found")
return {"timeline": timeline}