DEV Community

shashank ms
shashank ms

Posted on

LLM for Medical Diagnosis: Engineering and Product Considerations

We are building a clinical decision support agent that ingests structured patient history and emits a ranked differential diagnosis with recommended workup steps. This tutorial is for engineering teams prototyping LLM-backed triage tools, not for replacing clinicians. I chose Oxlo.ai because its flat per-request pricing keeps costs predictable even when patient notes run long, which they always do in real workflows.

What you'll need

Step 1: Set up the Oxlo.ai client

First, instantiate the OpenAI-compatible client pointing at Oxlo.ai. I use Llama 3.3 70B as the backbone because it handles lengthy context windows well for long patient narratives. With Oxlo.ai, the cost stays flat per request even when those notes grow, so you do not need to truncate history to save tokens.

from openai import OpenAI
import json

client = OpenAI(
    base_url="https://api.oxlo.ai/v1",
    api_key="YOUR_OXLO_API_KEY"
)

Step 2: Write the system prompt

The system prompt is the contract. It forces probabilistic language, a strict JSON schema, and an automatic escalation path when red flags appear. Treat this as its own configuration file in your repo.

SYSTEM_PROMPT = """You are a clinical decision support engine. Your job is to analyze structured patient history and return a ranked differential diagnosis in strict JSON format.

Rules:
1. Never state a definitive diagnosis. Use probabilistic language only.
2. If the presentation suggests a life-threatening condition, set "urgent_referral" to true and list the suspected emergency.
3. Output only valid JSON matching the requested schema. No markdown fencing.
4. Include a disclaimer that this tool supports, not replaces, clinical judgment.

Required JSON schema:
{
  "differential_diagnosis": [
    {"condition": "string", "confidence": "low|medium|high", "rationale": "string"}
  ],
  "recommended_workup": ["string"],
  "red_flags": ["string"],
  "urgent_referral": boolean,
  "disclaimer": "string"
}
"""

Step 3: Enforce JSON mode

Medical software needs machine-readable output. We use JSON mode so the model emits a parseable object every time. I keep temperature at 0.1 to reduce hallucination of conditions that do not fit the presentation.

def get_structured_diagnosis(patient_input: str) -> dict:
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": patient_input},
        ],
        response_format={"type": "json_object"},
        temperature=0.1,
    )
    return json.loads(response.choices[0].message.content)

Step 4: Ground with retrieved context

Raw LLM knowledge drifts. In production you would query a vector store of clinical guidelines, but here we simulate retrieval by prepending a relevant textbook snippet. This keeps the model anchored to institutional protocols.

def load_guidelines() -> str:
    # Replace with vector retrieval from your medical KB in production
    return (
        "Guideline: Acute chest pain protocol. "
        "First rule out MI, PE, aortic dissection, pneumothorax. "
        "Obtain ECG within 10 minutes if cardiac origin suspected."
    )

def diagnose_with_context(patient_input: str) -> dict:
    context = load_guidelines()
    user_message = f"Retrieved context:\n{context}\n\nPatient presentation:\n{patient_input}"
    
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        response_format={"type": "json_object"},
        temperature=0.1,
    )
    return json.loads(response.choices[0].message.content)

Step 5: Add safety guardrails

Before we ever hit the model, we run a fast keyword filter for obvious emergencies. If the patient mentions crushing chest pain or severe bleeding, we bypass the LLM entirely and return an immediate escalation payload. This pattern prevents the model from over-analyzing a time-critical presentation.

EMERGENCY_KEYWORDS = [
    "chest pain",
    "cannot breathe",
    "unconscious",
    "severe bleeding",
    "stroke symptoms"
]

def triage(patient_input: str) -> dict:
    lowered = patient_input.lower()
    if any(k in lowered for k in EMERGENCY_KEYWORDS):
        return {
            "differential_diagnosis": [],
            "recommended_workup": ["Immediate emergency department evaluation"],
            "red_flags": ["Potential emergency detected by guardrail"],
            "urgent_referral": True,
            "disclaimer": "This is not a substitute for emergency care. Call emergency services."
        }
    
    return diagnose_with_context(patient_input)

Run it

Here is a realistic test case involving dyspnea on exertion. We call the pipeline and print the structured result.

if __name__ == "__main__":
    case = (
        "67-year-old male with hypertension and hyperlipidemia. "
        "Reports progressive dyspnea on exertion over 3 weeks, "
        "orthopnea, and bilateral ankle edema. No chest pain currently. "
        "Vitals: BP 150/92, HR 88, SpO2 94% on room air."
    )
    
    result = triage(case)
    print(json.dumps(result, indent=2))

Example output:

{
  "differential_diagnosis": [
    {
      "condition": "Acute decompensated heart failure",
      "confidence": "high",
      "rationale": "Orthopnea, exertional dyspnea, and peripheral edema in a patient with cardiovascular risk factors."
    },
    {
      "condition": "Chronic obstructive pulmonary disease exacerbation",
      "confidence": "medium",
      "rationale": "Dyspnea and reduced oxygen saturation, though edema is less typical."
    }
  ],
  "recommended_workup": [
    "NT-proBNP or BNP",
    "Chest X-ray",
    "12-lead ECG",
    "Echocardiogram"
  ],
  "red_flags": [
    "Progressive dyspnea",
    "Hypoxemia",
    "Elevated blood pressure"
  ],
  "urgent_referral": false,
  "disclaimer": "This tool supports, but does not replace, clinical judgment. Consult a qualified clinician."
}

Wrap-up

Swap in deepseek-v3.2 or kimi-k2.6 on Oxlo.ai if you need deeper chain-of-thought reasoning for complex multi-system cases. The next concrete steps are to replace load_guidelines with a real RAG pipeline over your institution's clinical pathways, and to add a human-in-the-loop review queue before any output reaches a provider.

Top comments (0)