A production FastAPI backend + static JS frontend that answers medical questions with Gemini 2.5 Flash, plus a drug interaction checker and a PDF/image medical-report analyzer. Backend runs on Render, frontend on Vercel.
For educational purposes only. Not a substitute for professional medical advice.
Browser (Vercel: medical-ai-drab.vercel.app)
│ fetch() calls
▼
FastAPI Backend (Render: medical-ai-backend-00qv.onrender.com)
├── /auth → JWT register / login
├── /chat
│ ├── POST /chat/ask → non-streaming answer (fallback)
│ ├── POST /chat/feedback → thumbs up/down logging
│ └── GET/DELETE /chat/history, /chat/sessions
├── /stream
│ ├── POST /stream/ask → SSE streaming answer (used by the UI)
│ └── POST /stream/suggestions → follow-up question suggestions
├── /drugs
│ ├── POST /drugs/check → multi-drug interaction check (RxNorm + Gemini)
│ ├── GET /drugs/search → drug name autocomplete (RxNorm)
│ └── GET /drugs/info/{name} → single drug label info (OpenFDA)
├── /report
│ ├── POST /report/analyze → upload a PDF/image lab report, get AI analysis
│ └── GET /report/history → past uploaded reports
└── /admin
├── GET /admin/metrics, /admin/diseases, /admin/users
├── GET /admin/api-keys/status
└── POST /admin/users/{id}/action
│
▼
SQLite / Postgres (users · chat_history · feedback · report_uploads)
Every chat/stream/drug/report request goes through an intent classifier (TF-IDF + Logistic Regression, trained via ml/train_classifiers.py) that tags a question as medical, off_topic, or emergency before it's routed to Gemini. Emergency and off-topic questions get canned safe responses instead of a model call.
Note on retrieval: the
indexes/FAISS files,category_router.py, and PubMedBERT embeddings from the original RAG prototype are still in the repo but are not wired into the live/chat/askor/stream/askpaths — both call Gemini directly with conversation history, no retrieval step. Re-enabling retrieval would mean adding a call tocategory_router/ FAISS search back intostream.pybefore the Gemini prompt is built.
Gemini calls rotate across multiple API keys (GEMINI_API_KEY_1/2/3) via services/api_key_manager.py to survive per-key rate limits (TPM/TPD), automatically marking a key as exhausted and falling back to the next.
| Layer | Technology |
|---|---|
| Frontend | Vanilla JS + HTML (frontend/index.html, frontend/app.js), Vite dev server |
| Backend API | FastAPI, Uvicorn |
| Auth | JWT (python-jose), bcrypt (passlib) |
| LLM | Google Gemini 2.5 Flash, multi-key rotation |
| Drug data | RxNorm (interactions/autocomplete), OpenFDA (drug labels) |
| Report analysis | PyMuPDF (PDF text extraction) + Gemini |
| Database | SQLite (local) / Postgres (Render), SQLAlchemy ORM |
| Tests | Pytest, FastAPI TestClient |
| Hosting | Render (backend), Vercel (frontend) |
| CI | GitHub Actions |
medical-AI/
├── backend/ ← Render root directory — run from here
│ ├── main.py ← FastAPI app entry point, seeds admin user on boot
│ ├── dependencies.py ← JWT auth dependencies (get_current_user, get_admin_user)
│ ├── requirements.txt
│ ├── routers/
│ │ ├── auth.py ← POST /auth/register, /auth/login
│ │ ├── chat.py ← non-streaming chat + history + feedback
│ │ ├── stream.py ← SSE streaming chat (used by the frontend)
│ │ ├── drugs.py ← drug interaction checker
│ │ ├── report.py ← PDF/image lab report analyzer
│ │ └── admin.py ← analytics dashboard + user/key management
│ ├── services/
│ │ ├── gemini_client.py ← non-streaming Gemini calls, emergency/off-topic responses
│ │ ├── gemini_streamer.py ← streaming Gemini calls
│ │ ├── intent_classifier.py ← medical / off_topic / emergency classification
│ │ ├── api_key_manager.py ← multi-key rotation, rate-limit tracking
│ │ ├── drug_checker.py ← RxNorm + OpenFDA + Gemini drug interaction logic
│ │ ├── report_analyzer.py ← PDF/image report analysis
│ │ ├── category_router.py ← (legacy) disease-category routing for FAISS retrieval
│ │ └── rag_pipeline.py ← (unused placeholder from the original RAG prototype)
│ ├── models/
│ │ └── schemas.py ← Pydantic request/response models
│ └── db/
│ ├── database.py ← SQLAlchemy engine + session (SQLite/Postgres)
│ ├── db_models.py ← User, ChatMessage, Feedback, ReportUpload tables
│ └── crud.py ← DB read/write operations
│
├── frontend/
│ ├── index.html ← single-page app shell, sets the `API` base URL
│ └── app.js ← all UI logic + fetch calls to the backend
│
├── ml/ ← legacy RAG-prototype tooling (index building, classifier training)
│ ├── create_indexes.py
│ ├── train_classifiers.py
│ └── evaluate.py
│
├── tests/
│ ├── conftest.py
│ ├── test_api.py
│ ├── test_intent_classifier.py
│ ├── test_rag_pipeline.py
│ └── test_retrieval.py
│
├── scripts/
│ ├── seed_db.py ← creates tables + admin user
│ └── download_assets.py
│
├── indexes/ ← FAISS index files (legacy, git-ignored, unused at runtime)
├── models_saved/ ← trained classifier .pkl files (git-ignored)
├── data/ ← MedQuAD CSVs (git-ignored)
├── render.yaml ← Render Blueprint (rootDir: backend)
├── vite.config.js ← proxies /auth,/chat,/stream,/drugs,/report,/admin to :8000 in dev
├── Dockerfile.backend / Dockerfile.frontend / docker-compose.yml ← legacy, not used by the current Render+Vercel deploy
├── .github/workflows/ci.yml
├── requirements.frontend.txt
└── .env.example
git clone https://github.com/kashish334/medical-AI.git
cd medical-AI
cp .env.example .env
# Edit .env: add GEMINI_API_KEY_1 (and optionally _2, _3), set SECRET_KEYcd backend
pip install -r requirements.txt
cd ..python scripts/seed_db.pycd backend
uvicorn main:app --reload --port 8000API docs available at: http://localhost:8000/docs
npm install
npm run devApp available at: http://localhost:5173 — Vite proxies API calls to localhost:8000 (see vite.config.js).
The deployed frontend (
frontend/index.html) instead hits the live backend directly via a hardcodedAPIconstant — update that URL if you redeploy the backend elsewhere.
- Backend → Render.
render.yamlsetsrootDir: backend, so every import inbackend/(from db...,from routers...,from services...) resolves relative to that directory, not the repo root. - Frontend → Vercel, serving
frontend/index.html+app.jsas a static site. - CORS is restricted to the origins listed in
backend/main.py'sALLOWED_ORIGINS(plus an optionalALLOWED_ORIGINenv var) — add any new frontend domain there.
cd backend
pip install -r requirements.txt pytest pytest-cov
cd ..
pytest tests/ -v --cov=backend --cov-report=term-missing
ci.ymlcurrently installs fromrequirements.backend.txtat the repo root, which doesn't exist (the real file isbackend/requirements.txt) — CI needs that path fixed before it will pass.
GEMINI_API_KEY_1=... # primary Gemini key
GEMINI_API_KEY_2=... # optional — used for rotation on rate limits
GEMINI_API_KEY_3=... # optional
SECRET_KEY=... # JWT signing secret
DATABASE_URL=sqlite:///./medical_chatbot.db # or a Postgres URL on Render
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
ALLOWED_ORIGIN=https://your-frontend-domain.com # extra CORS origin, beyond the defaults
Log in with the admin credentials to access the Analytics Dashboard (/admin/*).
The ml/ and indexes/ folders build on MedQuAD (Medical Question Answering Dataset, sourced from NIH, National Cancer Institute, CDC, NHLBI, and more) from an earlier retrieval-based version of this project. They're kept for reference but aren't part of the current live answer pipeline.