A retrieval-augmented chatbot that answers health questions from a curated, synthetic medical corpus — every answer carries citations, an evidence-quality tier, and refuses to fabricate a response when the evidence isn't there. Built to demonstrate RAG systems designed around trust, not just relevance: the kind of judgment a regulated or high-stakes domain actually requires.
| Metric | Value |
|---|---|
| Pipeline | Retrieve → Re-rank → Confidence-Tier, kept as three separate stages |
| Backend | FastAPI (/chat endpoint — the real deliverable/API) |
| Demo UI | Streamlit, calling the FastAPI backend |
| Vector DB | ChromaDB, behind a swappable VectorStore interface + factory |
| AI | OpenAI (embeddings + generation) |
| Corpus | 129 chunks — 35 PubMed abstract chunks + 94 curated Kaggle Q&A chunks |
| Tests | 56 passing, zero network calls in the default suite |
| Status | All 9 build phases complete, thresholds calibrated against the real corpus |
Medical RAG Chatbot is a retrieval-augmented generation system that answers health questions strictly from a curated, synthetic/public medical corpus — PubMed abstracts and a manually-vetted subset of a Kaggle medical Q&A dataset. Every response carries citations back to the retrieved source material, an explicit evidence-quality tier, and a heuristic flag when the top sources disagree. It was built as the first project in a healthcare AI portfolio pipeline, chosen for the fastest build time and the highest Upwork demand, to demonstrate the exact judgment a healthcare RAG engagement requires: trust-first design, honest confidence signaling, and the discipline to refuse an answer rather than fabricate one.
The core RAG pipeline, the FastAPI /chat endpoint, the Streamlit demo, and the corpus ingestion/calibration scripts are all real and run end to end against the actual ingested corpus — no mocked retrieval, no canned responses.
Generic LLM chatbots answer medical questions from parametric memory, and that creates a real liability even in low-stakes patient-education use cases:
A confident-sounding answer with no traceable source — no way for the user to check where it came from.
No way to tell a well-supported answer from a confident-sounding guess — every response reads with the same authority.
No distinction between the model genuinely not knowing and the model quietly making something up to fill the gap.
This project builds a RAG pipeline where every claim is traceable to a retrieved source, and every response is honest about how much to trust it.
Retrieval, re-ranking, and confidence tiering are kept as three separate, testable stages rather than one conflated relevance score — a common RAG mistake this system deliberately avoids:
| Stage | Job |
|---|---|
| Retrieve | Embed the query, search ChromaDB, return candidate chunks |
| Re-rank | Weight similarity + evidence level + recency to decide which chunks to trust |
| Tier | Decide how much the final answer deserves — VERIFIED / LOW_CONFIDENCE / INSUFFICIENT_EVIDENCE / NO_MATCH |
Four confidence tiers, each shown to the user with the answer:
Top retrieved chunk carries HIGH/MEDIUM evidence — "verified" means backed by higher-quality evidence, not just high similarity.
Multiple independent sources agree, but all are lower-evidence — an answer is given, with the evidence level shown honestly.
The corpus has something related, but not enough to answer confidently — a deliberately distinct failure mode from NO_MATCH.
The corpus has nothing relevant — the system says so and refuses to fabricate an answer outside its knowledge base.
A heuristic (non-LLM) CONFLICTING_EVIDENCE flag can accompany any tier when top sources span multiple provenance types with a wide score spread — surfaced to the user rather than auto-resolved, since auto-resolution would need its own LLM call and cross into agentic behavior this v1 deliberately defers.
| Layer | Technology |
|---|---|
| Backend / API | FastAPI |
| Vector Store | ChromaDB, behind a VectorStore interface + factory (in-memory for tests) |
| AI | OpenAI (embeddings + generation) |
| Demo UI | Streamlit |
| Testing | pytest (56 tests, zero network calls by default) |
Every citation carries source type, evidence level, and publication year — because a citation should say where an answer came from, and the evidence metadata should say how much to trust it.
Confidence-tier boundaries (tier_no_match_floor, tier_verified_floor) were measured from the actual embedding-score distribution of the ingested corpus via scripts/calibrate_thresholds.py, not hardcoded tutorial defaults.
The system prompt explicitly instructs the model to answer only from retrieved evidence and say so when evidence is missing; the medical disclaimer is appended in code after generation on every response — not left to the LLM to remember, and not confined to the UI layer.
Retrieval sits behind a VectorStore interface + factory (in-memory for tests, ChromaDB for the running system) — moving to a managed vector DB for a client with different scale needs is a config change, not a rewrite.
A single-turn RAG pipeline (embed query → search → prompt) doesn't need an orchestration framework. LangChain or the OpenAI Agents SDK would earn their place for a triage agent, a self-critique/retry loop, or multi-agent routing — none of which v1 needs. Judgment about when not to add complexity, not just what to add.
The corpus's Kaggle Q&A content is phrased like patient questions and out-embeds PubMed's dense scientific prose on shared topics (diabetes, hypertension, asthma) — at the current corpus scale (94 Kaggle chunks vs. 35 PubMed chunks), PubMed content can be crowded out of the retrieved candidate set before re-ranking ever sees it, regardless of retrieval_top_k. Bumping retrieval_top_k to 4x the shipped value was tested first as the cheapest fix and empirically disproven — a bigger pool just returns more Kaggle.
Rather than ship a silent inaccuracy, the tiering logic (_has_sufficient_coverage) was hardened so an all-LOW-evidence result only reaches LOW_CONFIDENCE — never a false INSUFFICIENT_EVIDENCE — when enough independent sources genuinely agree within a tight score spread. A companion guard (_top_evidence_qualifies_for_verified) stops a high-scoring all-Kaggle result from reaching VERIFIED on similarity alone, so "verified" keeps meaning backed by higher-quality evidence.
The recommended long-term direction is querying the vector store separately per source type and merging before re-ranking, so HIGH-evidence content always reaches the evidence-weighted rank stage instead of being crowded out at the raw-ANN stage. It's deferred because the tiering-side fix already prevents the two failure modes that matter at portfolio scale (false INSUFFICIENT_EVIDENCE, false VERIFIED), and per-source quotas add real complexity — query fan-out, per-source tuning — that isn't worth carrying until the corpus grows or a real client requirement calls for it.
This is the kind of trade-off documentation a client should expect on a real engagement — not just a working demo, but a record of what's solid, what's a known limitation, and why, kept in the project's decision log rather than silently worked around.
Stated plainly, because a system that hides its posture is worth less than one that states it:
MIMIC-III Demo was evaluated for inclusion and dropped from scope after discovering its public demo release ships an empty NOTEEVENTS table — no free-text clinical notes, only structured tables. Real clinical notes require full credentialed MIMIC-III access (CITI training + a signed Data Use Agreement), which is out of scope for a portfolio build; this is documented in the project's decision log rather than silently worked around. Real patient data of any kind would require encryption, access control, and a signed BAA — not applicable to this project, and not claimed.
Every query below was run against the live API and confirmed to produce the tier shown:
Stated deliberately, because a system that hides its limitations is worth less than one that states them:
Whether you're building a patient-education chatbot, a clinical knowledge-retrieval tool, or any RAG system where trust matters as much as relevance, we're always interested in discussing secure, evidence-grounded AI architectures.