I built local_healthcare_rag as a 48-hour AI Engineer assignment: a fully local, offline healthcare chatbot that answers questions on symptoms, diseases, nutrition, lifestyle, preventive care, and first aid — while never, under any circumstance, giving an actual diagnosis.
The chatbot itself was the easy part. Streamlit UI, FastAPI backend, Phi3 running locally via Ollama, a RAG pipeline over a curated medical knowledge base using FAISS and sentence-transformers. No API key, no cloud dependency, zero cost per query. Standard stack for this kind of project.
The interesting part — the part that actually matters if you've spent time doing LLM security research — was designing guardrails that don't collapse the moment the model does something you didn't expect.
The naive approach fails silently
The obvious first move is a system prompt: "You are a helpful medical assistant. Never provide a diagnosis. Always recommend consulting a doctor." This works most of the time. It also fails in exactly the situations where failure matters most — edge-case phrasing, multi-turn context where the instruction gets diluted, or just the model deciding to be unusually helpful at the wrong moment.
If your only defense is "hope the LLM follows instructions," you don't have a safety layer. You have a suggestion.
Two-layer defense
I split the guardrail into two independent layers that don't rely on each other to work:
- Layer 1 — Explicit prompt rules. The system prompt still tells the model not to diagnose. This catches the majority of cases cheaply, before generation even happens.
- Layer 2 — Deterministic post-processing. Every model output passes through a rule-based scanner that catches diagnostic language patterns regardless of what the model actually said, and force-injects a disclaimer if triggered. This layer doesn't trust the model at all — it's plain string/pattern matching, the same category of logic as the site assistant on this portfolio.
Why this matters beyond one project
This is the same principle that shows up throughout my actual security research: prompt injection and jailbreak vulnerabilities exist precisely because systems trust the model's output as if it were sanitized input. A model instruction is not a security boundary. If a behavior is genuinely non-negotiable — no diagnosis, no leaking a system prompt, no executing a destructive tool call — it needs to be enforced by code that runs independently of whether the model "listened."
Prompting is a UX layer. Deterministic checks are the security layer. Conflating the two is how agentic AI systems end up with vulnerabilities that look obvious in hindsight.