Source-grounded RAG over a medical-tourism corpus — a retrieval API that answers only from indexed documents and returns the passages it used, so every answer can be traced back to a source.
Live landing page: medtourismatlas.com
Status: retired prototype. Built solo in ~2.5 weeks as pre-contract proof of competence in an unfamiliar domain, before joining a Korean medical-tourism company — a working system in their industry instead of slides. It was built to do that, it did it, and it was retired afterwards.
What it ran on: 264 practitioner interview videos, two industry books and seven academic papers, ingested end to end. 24 Postgres-backed integration tests cover the retrieval API. Hybrid keyword-plus-vector search and parent-document retrieval were deferred as known next steps — written up in ingestion/docs/architecture-review.md rather than quietly dropped — and the auth shortcuts that made the deadline are documented in SECURITY_RISKS.md rather than hidden.
The system splits the two halves of RAG that usually get tangled together:
- This repo is the retrieval half. It ingests documents, chunks them semantically, embeds them into Postgres + pgvector, and exposes a search endpoint that returns ranked passages with their source metadata.
- Generation is delegated to an external chat front-end (OpenWebUI), wired through
openwebui_tool.py. The model is forced to call the search tool, gets back passages below a configurable relevance threshold or nothing at all, and runs under a cite-or-refuse system prompt (docs/SYSTEM_PROMPT.md) that tells it to say "I don't know" rather than answer unsupported.
The consequence is that the interesting engineering — chunking, embedding, ranking, provenance — lives in code that can be tested, rather than in a prompt.
ingestion/ app/
PDF ─┐ ┌── FastAPI + async SQLAlchemy
├─ parse ─ clean ─ semantic chunk ─ embed ┤
YouTube┘ (spaCy, ~300 tok) (BGE-M3 / OpenAI) └── Postgres 16 + pgvector
│
├── HNSW cosine index
└── MMR re-ranking (λ = 0.5)
│
OpenWebUI ── openwebui_tool.py ─────┘
(forced tool-use, cite-or-refuse)
Data model: Source → Document → Chunk, with per-source reliability tiers and SHA-256 content
hashing so re-ingesting the same material is idempotent.
Retrieval: the query is embedded, pgvector returns candidates by cosine distance over an HNSW index, then Maximal Marginal Relevance re-ranks them to trade off relevance against redundancy so several near-identical passages from one document don't crowd out the rest.
API surface: JWT auth for the dashboard, and separate mt_-prefixed API keys for machine
clients, both rate-limited. Chunk search accepts a score threshold and returns chunks enriched with
document title, source name and reliability tier — that metadata is what the chat client turns into
citation pills.
Dashboard: React 19 + Chakra UI for managing sources, documents, chunks and API keys.
| Path | What's in it |
|---|---|
app/ |
FastAPI backend — endpoints, services, repositories, SQLAlchemy models |
ingestion/ |
LangChain ingestion pipeline, PDF/YouTube → chunks → pgvector. See ingestion/README.md |
frontend/ |
React 19 admin dashboard (Vite + Chakra UI) |
landing_page/ |
Static marketing site — Jinja2 templates + Tailwind, rendered to index.html |
alembic/ |
Database migrations, including the pgvector HNSW indexes |
tests/ |
Integration tests against a live Postgres |
openwebui_tool.py |
The OpenWebUI tool definition that calls the search API |
The corpus is not in this repo and will not be. It was assembled from published books,
whitepapers, research papers and recorded talks; redistributing those verbatim is not mine to do.
ingestion/input/ and ingestion/output/ are gitignored.
cp .env.example .env # fill in POSTGRES_*, SECRET_KEY, FIRST_SUPERUSER*, OPENAI_API_KEY
docker compose up -d # backend :8000, dashboard :3001, pgvector :5432scripts/prestart.sh waits for the database, runs alembic upgrade head and seeds the first
superuser. API docs are then at http://localhost:8000/docs.
Tests need a running Postgres with the pgvector extension:
pytestFor the ingestion pipeline, see ingestion/README.md — it has its own
dependencies (spaCy models, and optionally a local llama.cpp build for figure-heavy PDFs).
MIT — see LICENSE.