| title | Medirag |
|---|---|
| emoji | 🐨 |
| colorFrom | yellow |
| colorTo | red |
| sdk | gradio |
| sdk_version | 6.15.0 |
| python_version | 3.12 |
| app_file | app.py |
| pinned | false |
| license | mit |
| short_description | Plain-language Q&A grounded in FDA DailyMed labels. |
|
MediRAG replaces the tiny-print patient information leaflet that comes with your medication. Ask anything about a drug — side effects, ingredients, dosing, contraindications, drug interactions, what the pill looks like — and get plain-language answers grounded in the FDA's DailyMed structured product labels.
- Patient-friendly Q&A: Conversational answers, not regulatory wall-of-text.
- Full SPL coverage: Every section (narrative + structured product data) is indexed — ingredients with strengths, NDC codes, pill color/shape/imprint, manufacturer, route.
- Hybrid retrieval: Vector search (PubMedBERT) + BM25 full-text in one query, so brand names and ingredient names match exactly while paraphrased questions still hit.
- Cross-drug queries: Metadata-filtered search ("which other drugs contain atorvastatin?", "is there an IV form?") via ingredient UNII codes and SQL filters.
- Input and output guardrails: DSPy-based safety checks on both the user's question and the model's answer.
- Streaming answers: Token-level streaming via
dspy.streamify. - Semantic cache: Reuses answers for semantically similar queries.
- Model picker: GPT-4o-mini, GPT-4o.
flowchart TD
classDef user fill:#fde7c1,stroke:#b06a00,color:#222
classDef step fill:#e9f0ff,stroke:#3868c1,color:#0a2a66
classDef store fill:#ffe7e7,stroke:#c43b3b,color:#5a0d0d
classDef model fill:#e7f7e9,stroke:#2a8a3e,color:#103d1c
classDef decision fill:#fff3b0,stroke:#b08a00,color:#3a2a00
classDef dspy fill:#ece1ff,stroke:#5b3fbf,color:#1f0c5a
%% Build pipeline (medirag.index.runner + publisher)
DM["DailyMed SPL zips"]:::store --> P["parse_spl"]:::step
P --> PB["PubMedBERT<br/>NeuML/pubmedbert-base-embeddings · 768d"]:::model
PB --> L[("LanceDB<br/>hybrid: vector + BM25")]:::store
L -- "publisher.py · sync_bucket" --> HF[("Hugging Face Hub bucket")]:::store
HF -. "sync on startup" .-> L
%% Runtime (app.py)
U(["User"]):::user --> Q["Question"]:::step
Q --> IG["Input guardrail · DSPy"]:::dspy
IG -- blocked --> R(["Streamed answer · Gradio"]):::user
IG -- allowed --> C{"Semantic cache<br/>hit?"}:::decision
C -- "YES (cached)" --> R
C -- NO --> RT["Hybrid retrieval"]:::step
RT --> L
L --> CT["ChainOfThought · DSPy<br/>GenerateAnswer"]:::dspy
CT --> OG["Output guardrail · DSPy"]:::dspy
OG --> R
C -. "embed query" .-> MP["sentence-transformers/<br/>all-mpnet-base-v2"]:::model
Key tech:
- DSPy — module composition, prompting, streaming, guardrails
- LanceDB — embedded vector store with native embeddings + hybrid search
- PubMedBERT — biomedical embeddings
- Gradio — UI
- DailyMed — FDA drug labels (free, public)
-
Clone the repository:
git clone https://github.com/alvinhenrick/medirag.git cd medirag -
Install dependencies (uses uv):
uv sync
-
Create a
.envfile with your model API key:OPENAI_API_KEY=... HF_TOKEN=... # optional, only for publishing the index -
Get the index. Either pull the prebuilt one from a Hugging Face Bucket:
HF_BUCKET=alvinhenrick/dailymed-embeddings uv run app.py # → app syncs lance_db/ from the bucket on first start…or build it yourself from DailyMed (official release page):
# Quick smoke build: 100 SPLs from one part (~1-2 min) uv run python -m medirag.index.runner \ --source path/to/dm_spl_release_human_rx_part1.zip \ --db ./lance_db \ --limit 100 # Full single-part build (~30-60 min on a Mac) uv run python -m medirag.index.runner \ --source path/to/dm_spl_release_human_rx_part1.zip \ --db ./lance_db # All 6 parts from official URLs (streams downloads, peak disk ~5 GB) uv run python -m medirag.index.runner --all --db ./lance_db
-
Run the app:
LANCE_DB_PATH=./lance_db uv run app.py
Open the URL printed by Gradio, pick a model, ask a question.
The built index is a self-contained directory (lance_db/). Publish it to a
Hugging Face Bucket
(S3-like Xet-backed storage — re-publishing only transfers changed chunks):
export HF_TOKEN=hf_xxx
uv run python -m medirag.index.publisher \
--db ./lance_db --bucket alvinhenrick/dailymed-embeddings
# → uploads to hf://buckets/alvinhenrick/dailymed-embeddings/lance_db/v1/Consumers point at it via HF_BUCKET=alvinhenrick/dailymed-embeddings
(defaults to prefix lance_db/v1; override with HF_BUCKET_PREFIX=lance_db/v2).
To publish a new version side-by-side with v1, pass --prefix:
uv run python -m medirag.index.publisher \
--db ./lance_db --bucket alvinhenrick/dailymed-embeddings --prefix lance_db/v2uv run pytest tests/22 tests covering:
- SPL XML extraction (
tests/core/test_xml_reader.py) - LanceDB indexer + retrieval scenarios (
tests/index/test_lance.py) - End-to-end runner against a synthetic zip (
tests/index/test_runner.py) - Semantic cache (
tests/cache/test_semantic_cache.py)
Tests run on the sample SPL XML in tests/data/ — no DailyMed download needed.
medirag/
├── core/
│ └── reader.py # SPL XML → ProductCard + SectionRecord dataclasses
├── index/
│ ├── lance.py # LanceIndexer (PubMedBERT + LanceDB + hybrid search)
│ └── runner.py # CLI to build/publish the index from DailyMed zips
├── rag/
│ ├── dspy.py # DspyRAG module + DailyMedRetrieve + stream_answer
│ └── qa_rag.py # Cache + streaming pipeline
├── cache/
│ └── local.py # Semantic cache (numpy-backed)
└── guardrail/
├── input.py # Block harmful/off-topic inputs
└── output.py # Block unsafe model outputs
app.py # Gradio app
tests/ # Unit + integration tests
- Index all 6 DailyMed parts and publish to HF (via Storage Buckets)
- LLM evaluation harness on a curated patient-question benchmark
- Optional reranker for top-k results
- OpenTelemetry traces for retrieval + LM calls
This project is licensed under the MIT License — see the LICENSE file for details.
