"""Prompt construction utilities for future LLM-driven policies.""" from __future__ import annotations import json from typing import Any from .models import PulsePhysiologyObservation from .tool_availability import validate_tool_availability from .tool_catalog import build_tool_catalog as build_shared_tool_catalog def _mental_status_value(mental_status: Any) -> str: return getattr(mental_status, "value", str(mental_status)) def observation_snapshot(observation: PulsePhysiologyObservation) -> dict[str, Any]: """Extract a concise but useful observation snapshot for prompting.""" return { "scenario_id": observation.scenario_id, "patient_id": observation.patient_id, "sim_time_s": observation.sim_time_s, "heart_rate_bpm": observation.heart_rate_bpm, "systolic_bp_mmhg": observation.systolic_bp_mmhg, "diastolic_bp_mmhg": observation.diastolic_bp_mmhg, "spo2": observation.spo2, "spo2_percent": round(observation.spo2 * 100, 1) if observation.spo2 is not None else None, "respiration_rate_bpm": observation.respiration_rate_bpm, "blood_volume_ml": observation.blood_volume_ml, "mental_status": _mental_status_value(observation.mental_status), "active_alerts": observation.active_alerts, "done": observation.done, "position": observation.position, "oxygen_device": observation.oxygen_device, "oxygen_flow_lpm": observation.oxygen_flow_lpm, "airway_support": observation.airway_support, "tool_result": observation.tool_result.model_dump() if observation.tool_result else None, "error": observation.error.model_dump() if observation.error else None, } def build_tool_catalog(available_tools: list[str] | None = None) -> list[dict[str, Any]]: """Build a prompt-safe catalog from a validated backend tool list.""" validated_tools = validate_tool_availability(available_tools) return build_shared_tool_catalog(validated_tools) def build_policy_prompt( observation: PulsePhysiologyObservation, *, available_tools: list[str] | None = None, objective: str | None = None, recent_history: list[dict[str, Any]] | None = None, ) -> str: """Render a prompt asking a model to choose the next tool action.""" validated_tools = validate_tool_availability( available_tools if available_tools is not None else observation.available_tools ) snapshot = observation_snapshot(observation) tool_catalog = build_tool_catalog(validated_tools) history = recent_history or [] objective_text = objective or ( "Choose the single best next tool call to stabilize the patient and avoid deterioration." ) return "\n".join( [ "You are a clinical reasoning agent operating in Pulse-ER.", objective_text, "", "Return exactly one JSON object with this schema:", '{', ' "tool_name": "",', ' "arguments": { ... },', ' "reasoning": ""', '}', "", "Current patient snapshot:", json.dumps(snapshot, indent=2), "", "Recent decision history:", json.dumps(history, indent=2), "", "Available tools:", json.dumps(tool_catalog, indent=2), "", "Rules:", "- Output JSON only.", "- Use only supported tool names.", "- Keep arguments structured, not prose.", "- Include every required argument for the selected tool.", "- Prefer actions that address active alerts or worsening vitals.", "- Avoid repeating the same read-only tool when nothing material has changed.", ] )