Skip to content

Repository files navigation

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

MediRAG

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.

Features

  • 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.

Architecture

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
Loading

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)

Getting Started

  1. Clone the repository:

    git clone https://github.com/alvinhenrick/medirag.git
    cd medirag
  2. Install dependencies (uses uv):

    uv sync
  3. Create a .env file with your model API key:

    OPENAI_API_KEY=...
    HF_TOKEN=...             # optional, only for publishing the index
  4. 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
  5. Run the app:

    LANCE_DB_PATH=./lance_db uv run app.py

    Open the URL printed by Gradio, pick a model, ask a question.

Publishing the index to Hugging Face

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/v2

Testing

uv 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.

Project Layout

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

Roadmap

  • 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

License

This project is licensed under the MIT License — see the LICENSE file for details.

About

DailyMed RAG Capstone

Resources

Stars

9 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages