Live demo: http://rxgraph.duckdns.org
A drug interaction knowledge graph agent. Upload a prescription label or type medication names — RxGraph queries a Neo4j knowledge graph populated from FDA data and returns a structured safety report with source citations and an interactive drug relationship graph.
| ChatGPT | RxGraph | |
|---|---|---|
| Data freshness | Knowledge cutoff | Synced from FDA |
| Answer traceability | Generated text, no source | Every claim linked to FDA record |
| Multi-drug reasoning | Text inference | Graph traversal, deterministic |
| Severity | Inferred | Stored as-is from source; never guessed |
React Frontend
│ REST
FastAPI Backend
│
LangGraph Agent (5 nodes)
1. Entity Extraction — GPT-4o-mini (text) or GPT-4o vision (image)
2. Normalization — Neo4j lookup + RxNorm fallback
3. Graph Query — Cypher pairwise interaction + contraindication query
4. Safety Guardrail — drop unsupported claims, rank by severity
5. Report Generation — GPT-4o-mini narrates Neo4j results
│
Neo4j Knowledge Graph
│
OpenFDA Ingestion Pipeline
- Agent: LangGraph, OpenAI GPT-4o / GPT-4o-mini
- Graph DB: Neo4j 5
- Backend: FastAPI, Python 3.14
- Frontend: React, TypeScript, React Flow, Tailwind CSS
- Storage: AWS S3 (prescription label images + raw FDA data)
- Drug normalization: RxNorm API (NIH)
- Data source: OpenFDA API
- Docker Desktop
- Python 3.11+
- Node.js 20+
- AWS account (for image upload)
- OpenAI API key
docker compose up -dNeo4j browser: http://localhost:7474 (login: neo4j / rxgraph_dev)
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcp .env.example .env
# fill in OPENAI_API_KEY, AWS credentials, Neo4j passwordcd ingest
OPENAI_API_KEY=sk-... python openfda_ingest.pyPopulates Neo4j with ~650 drug nodes and ~1,700 interactions from FDA labels.
uvicorn api.main:app --reloadAPI docs: http://localhost:8000/docs
cd frontend
npm install
npm startApp: http://localhost:3000
Why Neo4j instead of PostgreSQL? Multi-drug interaction queries are graph traversal problems. A 3-drug combination check in Cypher is a single pattern match; in SQL it requires complex self-joins. Phase 2's multi-hop reasoning (A affects B's metabolism, B interacts with C) is native to graph databases and impractical in relational SQL.
Why LangGraph instead of a plain pipeline?
The safety guardrail is a routing gate — it must block output if any claim lacks a source_id. Modeling it as an explicit node with a conditional edge makes it independently testable and impossible to accidentally skip.
Why is severity never inferred by the LLM?
FDA label text rarely uses the words "major/moderate/minor" explicitly — ~92% of interactions in the current dataset have severity: "unknown". A hallucinated severity in a medical context is a patient harm risk. Surfacing "unknown" explicitly is the safe behavior.
Why is session context never persisted? Conditions, allergies, and age group are passed as Cypher parameters at query time and exist only in memory for that request. Medication combinations are sensitive health data — storing them without explicit user consent is a privacy risk.
(:Drug)-[:INTERACTS_WITH {severity, description, source_id}]->(:Drug)
(:Drug)-[:CONTRAINDICATED_WITH]->(:Condition)
(:Drug)-[:USE_WITH_CAUTION_IN]->(:Condition)
(:Drug)-[:BELONGS_TO]->(:DrugClass)
(:Drug)-[:ACTS_VIA]->(:Mechanism)
.venv/bin/pytest tests/ -v- ~650 drug nodes (96 seeded + stub nodes from interaction text)
- ~1,700 interactions
- Source: OpenFDA drug label API
- Severity coverage: ~8% explicit (major/moderate/minor), ~92% unknown
Phase 2 will migrate to DrugBank (1.4M+ interactions, structured severity fields).
- Scheduled AWS Lambda + EventBridge sync for new FDA approvals
- Deploy to AWS Lightsail with Docker
- DrugBank migration — structured severity + mechanism data
- Multi-hop interaction chain analysis (A→B→C indirect risk)
- Redis caching for frequent drug pair queries
- Multilingual OCR support