Skip to content

Repository files navigation

💊 PillWise

An agentic RAG system for querying FDA drug label information. Ask a question about a medicine and get a cited, self-verified answer pulled directly from official FDA drug labels.

Built as part of an AI engineering internship assignment focused on RAG pipeline design, vector storage, agentic graph architecture, and LLM integration.

Stack

Component Tool
Data DailyMed FDA drug labels (PDFs)
Embeddings Google Gemini (gemini-embedding-001, 3072d)
Vector Store pgvector + PostgreSQL 16 (Docker)
LLM Ollama llama3.2 (local)
Orchestration LangGraph (Corrective RAG agent)
API FastAPI
Frontend React + Tailwind CSS (Vite)
Package Manager uv

What Makes This Agentic, Not Just RAG

Most RAG tutorials retrieve once and generate — a fixed pipeline with no error correction. PillWise's pw-agent.py uses a LangGraph state machine that can detect a poor retrieval and self-correct before answering:

question → retrieve → grade ─── relevant? ──→ generate → answer
                          │
                          └── not relevant ──→ rewrite → retrieve (retry, capped at 2)
  • retrieve — embeds the question, runs pgvector similarity search
  • grade — a lightweight LLM call checks whether retrieved chunks actually answer the question
  • rewrite — if not, reformulates the question (e.g. brand name → generic name, since DailyMed indexes by generic names) and retries
  • generate — produces the final cited answer once context is judged sufficient

The API response includes a retries field, so the number of self-corrections is visible per request — e.g. a query for "Advil" (a brand name absent from the corpus) triggers a rewrite to "ibuprofen" before answering, while an exact match like "ibuprofen" answers on the first pass with zero retries.

Project Structure

File Description
01_scraper.ipynb DailyMed automated PDF downloader
02_load_and_chunk.ipynb PDF text extraction and chunking strategy comparison
03_embeddings.ipynb Gemini embedding pipeline
04_vectorstore.ipynb pgvector setup and chunk storage
05_retrieval.ipynb Semantic search and answer generation
pw-api.py Original fixed-chain FastAPI endpoint (retrieve → generate)
pw-agent.py LangGraph Corrective RAG agent + FastAPI endpoint (current primary system)
insert_remaining_chunks.py Production script to bulk-insert remaining medicines, with rate-limit retry handling
frontend/ React + Tailwind UI — search, citation-linked answers, source cards, agent trace
data/raw_pdfs/ Downloaded drug label PDFs organized by medicine

Setup

Prerequisites: Docker Desktop, Ollama with llama3.2, uv, Node.js, Gemini API key (free tier works)

1. Clone and install backend

git clone https://github.com/arinova2701/PillWise
cd PillWise
uv sync

2. Create a .env file in the project root

GEMINI_API_KEY=your_key_here

3. Start pgvector in Docker

docker run -d --name pillwise-pgvector -e POSTGRES_USER=pillwise -e POSTGRES_PASSWORD=pillwise123 -e POSTGRES_DB=pillwise -p 5432:5432 pgvector/pgvector:pg16
docker exec -it pillwise-pgvector psql -U pillwise -d pillwise -c "CREATE EXTENSION IF NOT EXISTS vector;"

4. Pull llama3.2

ollama pull llama3.2

5. Run notebooks 01 through 05 in order to download PDFs, chunk them, generate embeddings, store in pgvector, and verify retrieval. Use insert_remaining_chunks.py for bulk-inserting additional medicines beyond the initial set.

6. Start the agent API

uv run uvicorn pw-agent:app --reload

API runs at http://localhost:8000

7. Run the frontend

cd frontend
npm install
npm run dev

Frontend runs at http://localhost:5173

API Reference

POST /ask

Send a question and get a self-verified, cited answer from the drug label corpus.

Request:

{
  "question": "what are the warnings for ibuprofen?",
  "top_k": 5
}

Response:

{
  "answer": "According to [3] WARNINGS, the warnings for ibuprofen include cardiovascular thrombotic events [3][1] and severe heart failure precautions [5]...",
  "sources": [
    {
      "drug": "ibuprofen",
      "chunk": "Ibuprofen may cause a severe allergic reaction, especially in people allergic to aspirin...",
      "similarity": 0.8164
    }
  ],
  "retries": 0
}

retries reflects how many times the agent rewrote the question and re-retrieved before generating — 0 means the first retrieval was judged sufficient.

GET /health

{"status": "ok"}

How It Works

  1. PDFs are downloaded from DailyMed using their public API (3 per medicine, 10 medicines, 30 total)
  2. Text is extracted using pymupdf and split into 500 char chunks with 50 char overlap
  3. Each chunk is embedded using Gemini producing a 3072 dimensional vector
  4. Vectors are stored in pgvector alongside chunk text and metadata
  5. At query time, the LangGraph agent embeds the question and retrieves the top 5 most relevant chunks via cosine similarity
  6. A grading step checks relevance; if poor, the question is rewritten and retrieval is retried (capped at 2 retries)
  7. Once context is sufficient, chunks are passed to llama3.2 (local, via Ollama) to generate a cited answer
  8. The frontend renders the answer with inline citation badges, source cards, and the agent's self-correction trace

Chunking Experiments

Two strategies compared across 30 documents:

Metric Fixed Size Sentence Based
Total chunks 3213 1626
Avg chunk size 497 chars 883 chars
Std deviation 29 499
Min 6 46
Max 500 6904

Fixed size chunking with overlap performs better for FDA drug labels. Sentence chunking struggles because bullet points lack sentence-ending punctuation, producing chunks up to 6904 characters which are too large for effective retrieval.

Medicines Covered

Inserted (6 of 10): acetaminophen, ibuprofen, amoxicillin, aspirin, atorvastatin, azithromycin Downloaded, pending insertion: cetirizine, metformin, omeprazole, pantoprazole

Known Limitations

  • Gemini's free-tier embedding quota (1,000 requests/day) means bulk insertion of all ~3,213 chunks across 10 medicines spans multiple days. Currently 1,609 chunks stored across 6 medicines.
  • Embedding models cannot be mixed within one vector store — all chunks and queries must use the same model (gemini-embedding-001) to remain comparable in the same vector space.
  • Bullet characters from PDFs not yet stripped before storage.
  • LLM runs locally so response time depends on hardware.
  • Frontend currently targets localhost:8000 directly; not yet configured for a deployed backend URL.

TODO

  • Finish inserting remaining 4 medicines (~1,604 chunks)
  • Integrate LangSmith for agent tracing and evaluation
  • Deploy API to Railway
  • Update frontend to point at deployed API URL
  • Add Indian medicine data via 1mg scraper (see 1mg-scraper-project-brief.md — separate project, in planning)
  • Clean bullet characters from chunk text

About

RAG system for FDA drug label queries.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages