ML Engineer Technical Assignment — AI-powered RAG pipeline to upload, process, retrieve, and explain health report data.
A FastAPI backend that allows users to upload health reports (PDF/TXT), extracts text and structured health parameters, stores content in a vector database for semantic retrieval, and answers questions about the report using a Retrieval-Augmented Generation (RAG) pipeline — all while maintaining strict medical safety guardrails.
The system also includes an advanced AI Doctor module with ensemble models (Meditron, Mistral, BioGPT), image analysis, drug lookup, and RLHF training for continuous learning.
Patients often receive health/blood test reports with medical terminology they don't understand. This API:
| Layer | Technology | Reason |
|---|---|---|
| Language | Python 3.10+ | Mandatory per assignment |
| Backend | FastAPI | Async, auto-docs, Pydantic validation |
| PDF Parsing | pdfplumber / PyPDF2 / PyMuPDF | Triple fallback for maximum compatibility |
| Embeddings | sentence-transformers (all-MiniLM-L6-v2) | Lightweight, good quality, runs on CPU |
| Vector DB | ChromaDB | Easy setup, metadata filtering, cosine similarity |
| LLM | Gemini 1.5 Flash / HuggingFace Inference API / Local models | Tiered fallback: Gemini → HF → Local |
| Parameter Extraction | Regex-based (30+ patterns) | Deterministic, no API dependency |
| Optional | Docker, Streamlit UI, OCR (pytesseract), SQLite | Bonus features |
┌──────────────────────────────────────────────────────────────┐
│ HEALTH REPORT INTELLIGENCE API │
├──────────────────────────────────────────────────────────────┤
│ │
│ POST /upload-report │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────────┐ │
│ │ PDF/TXT │──▸│ Extract │──▸│ Chunk │──▸│ Embed │ │
│ │ Upload │ │ Text │ │ (500c/ │ │ Store │ │
│ │ │ │ (3 libs) │ │ 100 ovl)│ │ Chroma │ │
│ └──────────┘ └───────────┘ └──────────┘ └────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ ┌─────────────────────────┐ │ │
│ │ Parameter │ │ ChromaDB Collection │◂───┘ │
│ │ Extraction │ │ (report_chunks) │ │
│ │ (30+ regex) │ │ - Cosine similarity │ │
│ └──────────────┘ │ - Metadata filtering │ │
│ └─────────────────────────┘ │
│ │ │
│ POST /ask-report │ │
│ ┌──────────┐ ┌───────────┐ │ ┌──────────────────┐ │
│ │ Question │──▸│ Retrieve │◂──┘──▸│ LLM (Safe │ │
│ │ + Report │ │ Top-K │ │ Prompt - NO │ │
│ │ ID │ │ Chunks │ │ diagnosis) │ │
│ └──────────┘ └───────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Answer + │ │
│ │ Sources + │ │
│ │ Disclaimer │ │
│ └────────────────┘ │
└──────────────────────────────────────────────────────────────┘
# 1. Clone the repository
git clone https://github.com/YOUR_USERNAME/ai-doctor.git
cd ai-doctor
# 2. Create virtual environment (recommended)
python -m venv venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
# 3. Install dependencies
pip install -r requirements.txt
# 4. Configure environment
cp .env.example .env
# Edit .env and add your API keys
# 5. Run the server
python api_simple.py
Server starts at: http://localhost:8000
API docs at: http://localhost:8000/docs
docker-compose up --build
Copy .env.example to .env and configure:
| Variable | Required | Description |
|---|---|---|
HF_TOKEN | Recommended | HuggingFace token (get here) |
GEMINI_API_KEY | Optional | Google Gemini key (get here) |
OPEN_FDA_API_KEY | Optional | OpenFDA drug data key |
LLM_PROVIDER | Optional | auto (default) / gemini / hf / local |
The system works in three tiers:
GET /health
Response:
{"status": "ok"}
POST /upload-report
Content-Type: multipart/form-data
Request: Form-data with file field (PDF or TXT).
curl -X POST http://localhost:8000/upload-report \
-F "file=@sample_report.pdf"
Response:
{
"status": "success",
"report_id": "report_a1b2c3d4e5f6",
"filename": "sample_report.pdf",
"total_chunks": 15,
"extracted_parameters_count": 12
}
Error responses:
{"status": "error", "message": "Unsupported file type. Only PDF and TXT files are allowed."}
{"status": "error", "message": "Empty file."}
{"status": "error", "message": "PDF extraction failed: ..."}
POST /ask-report
Content-Type: application/json
Request:
{
"report_id": "report_a1b2c3d4e5f6",
"question": "Summarize my report in simple words."
}
curl -X POST http://localhost:8000/ask-report \
-H "Content-Type: application/json" \
-d '{"report_id": "report_a1b2c3d4e5f6", "question": "Summarize my report in simple words."}'
Response:
{
"answer": "Your report contains blood sugar, cholesterol, and thyroid-related values. Based on the uploaded report, some values appear to be outside the reference range...",
"sources": [
{
"chunk_id": "chunk_002",
"page_number": 1,
"text": "Relevant report text used for the answer."
}
],
"disclaimer": "This is an AI-generated explanation based on the uploaded report and should not be treated as medical advice. Please consult a qualified doctor for diagnosis or treatment."
}
Diagnosis guard response:
{
"answer": "I cannot provide a medical diagnosis or treatment plan. I can only explain the uploaded report content in simple terms. Please consult a qualified doctor for medical advice.",
"sources": [],
"disclaimer": "This is an AI-generated explanation..."
}
GET /reports/{report_id}/parameters
curl http://localhost:8000/reports/report_a1b2c3d4e5f6/parameters
Response:
{
"report_id": "report_a1b2c3d4e5f6",
"parameters": [
{
"parameter": "Total Cholesterol",
"value": "245",
"unit": "mg/dL",
"reference_range": "< 200.0",
"status": "high"
},
{
"parameter": "Hemoglobin",
"value": "13.5",
"unit": "g/dL",
"reference_range": "13.0 - 17.0",
"status": "normal"
}
]
}
See section 9 (Sample Questions) for full end-to-end examples with evaluation.
page_numbersentence-transformers/all-MiniLM-L6-v2all-mpnet-base-v2 (768-dim) — better quality but 2x slower; overkill for this use case.where={"report_id": "..."}) — perfect for per-report retrievalhigh / low / normalThe system follows strict safety guidelines:
_is_diagnosis_seeking() detects keywords like "diagnose", "prescribe", "treatment for" and returns a safe refusal./ask-report response includes: "This is an AI-generated explanation based on the uploaded report and should not be treated as medical advice. Please consult a qualified doctor for diagnosis or treatment."| Error Case | HTTP Status | Response |
|---|---|---|
| Unsupported file type | 200 | {"status": "error", "message": "Unsupported file type. Only PDF and TXT files are allowed."} |
| Empty file | 200 | {"status": "error", "message": "Empty file."} |
| PDF extraction failure | 200 | {"status": "error", "message": "PDF extraction failed: ..."} |
| Invalid report ID | 200 | {"status": "error", "message": "Invalid report ID: ... Report not found."} |
| Empty question | 200 | {"status": "error", "message": "question is required."} |
| No relevant chunks | 200 | {"answer": "No relevant content found...", "sources": [], "disclaimer": "..."} |
| LLM API failure | 200 | Falls back through tiers (Gemini → HF → Local → rule-based) |
| Vector DB failure | 200 | Falls back to first N chunks from stored report |
| Not enough info | 200 | {"status": "error", "message": "Report does not contain enough information."} |
| Diagnosis-seeking | 200 | {"answer": "I cannot provide a medical diagnosis...", "disclaimer": "..."} |
Beyond the core assignment, this project includes:
Dockerfile + docker-compose.ymlusers_dbThis AI system is for EDUCATIONAL AND INFORMATIONAL PURPOSES ONLY.
8 commits
Python
92.6%
Shell
3.9%
TypeScript
2.1%
ML Engineer Technical Assignment — AI-powered RAG pipeline to upload, process, retrieve, and explain health report data.
A FastAPI backend that allows users to upload health reports (PDF/TXT), extracts text and structured health parameters, stores content in a vector database for semantic retrieval, and answers questions about the report using a Retrieval-Augmented Generation (RAG) pipeline — all while maintaining strict medical safety guardrails.
The system also includes an advanced AI Doctor module with ensemble models (Meditron, Mistral, BioGPT), image analysis, drug lookup, and RLHF training for continuous learning.
Patients often receive health/blood test reports with medical terminology they don't understand. This API:
| Layer | Technology | Reason |
|---|---|---|
| Language | Python 3.10+ | Mandatory per assignment |
| Backend | FastAPI | Async, auto-docs, Pydantic validation |
| PDF Parsing | pdfplumber / PyPDF2 / PyMuPDF | Triple fallback for maximum compatibility |
| Embeddings | sentence-transformers (all-MiniLM-L6-v2) | Lightweight, good quality, runs on CPU |
| Vector DB | ChromaDB | Easy setup, metadata filtering, cosine similarity |
| LLM | Gemini 1.5 Flash / HuggingFace Inference API / Local models | Tiered fallback: Gemini → HF → Local |
| Parameter Extraction | Regex-based (30+ patterns) | Deterministic, no API dependency |
| Optional | Docker, Streamlit UI, OCR (pytesseract), SQLite | Bonus features |
┌──────────────────────────────────────────────────────────────┐
│ HEALTH REPORT INTELLIGENCE API │
├──────────────────────────────────────────────────────────────┤
│ │
│ POST /upload-report │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────────┐ │
│ │ PDF/TXT │──▸│ Extract │──▸│ Chunk │──▸│ Embed │ │
│ │ Upload │ │ Text │ │ (500c/ │ │ Store │ │
│ │ │ │ (3 libs) │ │ 100 ovl)│ │ Chroma │ │
│ └──────────┘ └───────────┘ └──────────┘ └────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ ┌─────────────────────────┐ │ │
│ │ Parameter │ │ ChromaDB Collection │◂───┘ │
│ │ Extraction │ │ (report_chunks) │ │
│ │ (30+ regex) │ │ - Cosine similarity │ │
│ └──────────────┘ │ - Metadata filtering │ │
│ └─────────────────────────┘ │
│ │ │
│ POST /ask-report │ │
│ ┌──────────┐ ┌───────────┐ │ ┌──────────────────┐ │
│ │ Question │──▸│ Retrieve │◂──┘──▸│ LLM (Safe │ │
│ │ + Report │ │ Top-K │ │ Prompt - NO │ │
│ │ ID │ │ Chunks │ │ diagnosis) │ │
│ └──────────┘ └───────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ Answer + │ │
│ │ Sources + │ │
│ │ Disclaimer │ │
│ └────────────────┘ │
└──────────────────────────────────────────────────────────────┘
# 1. Clone the repository
git clone https://github.com/YOUR_USERNAME/ai-doctor.git
cd ai-doctor
# 2. Create virtual environment (recommended)
python -m venv venv
source venv/bin/activate # Linux/Mac
# or
venv\Scripts\activate # Windows
# 3. Install dependencies
pip install -r requirements.txt
# 4. Configure environment
cp .env.example .env
# Edit .env and add your API keys
# 5. Run the server
python api_simple.py
Server starts at: http://localhost:8000
API docs at: http://localhost:8000/docs
docker-compose up --build
Copy .env.example to .env and configure:
| Variable | Required | Description |
|---|---|---|
HF_TOKEN | Recommended | HuggingFace token (get here) |
GEMINI_API_KEY | Optional | Google Gemini key (get here) |
OPEN_FDA_API_KEY | Optional | OpenFDA drug data key |
LLM_PROVIDER | Optional | auto (default) / gemini / hf / local |
The system works in three tiers:
GET /health
Response:
{"status": "ok"}
POST /upload-report
Content-Type: multipart/form-data
Request: Form-data with file field (PDF or TXT).
curl -X POST http://localhost:8000/upload-report \
-F "file=@sample_report.pdf"
Response:
{
"status": "success",
"report_id": "report_a1b2c3d4e5f6",
"filename": "sample_report.pdf",
"total_chunks": 15,
"extracted_parameters_count": 12
}
Error responses:
{"status": "error", "message": "Unsupported file type. Only PDF and TXT files are allowed."}
{"status": "error", "message": "Empty file."}
{"status": "error", "message": "PDF extraction failed: ..."}
POST /ask-report
Content-Type: application/json
Request:
{
"report_id": "report_a1b2c3d4e5f6",
"question": "Summarize my report in simple words."
}
curl -X POST http://localhost:8000/ask-report \
-H "Content-Type: application/json" \
-d '{"report_id": "report_a1b2c3d4e5f6", "question": "Summarize my report in simple words."}'
Response:
{
"answer": "Your report contains blood sugar, cholesterol, and thyroid-related values. Based on the uploaded report, some values appear to be outside the reference range...",
"sources": [
{
"chunk_id": "chunk_002",
"page_number": 1,
"text": "Relevant report text used for the answer."
}
],
"disclaimer": "This is an AI-generated explanation based on the uploaded report and should not be treated as medical advice. Please consult a qualified doctor for diagnosis or treatment."
}
Diagnosis guard response:
{
"answer": "I cannot provide a medical diagnosis or treatment plan. I can only explain the uploaded report content in simple terms. Please consult a qualified doctor for medical advice.",
"sources": [],
"disclaimer": "This is an AI-generated explanation..."
}
GET /reports/{report_id}/parameters
curl http://localhost:8000/reports/report_a1b2c3d4e5f6/parameters
Response:
{
"report_id": "report_a1b2c3d4e5f6",
"parameters": [
{
"parameter": "Total Cholesterol",
"value": "245",
"unit": "mg/dL",
"reference_range": "< 200.0",
"status": "high"
},
{
"parameter": "Hemoglobin",
"value": "13.5",
"unit": "g/dL",
"reference_range": "13.0 - 17.0",
"status": "normal"
}
]
}
See section 9 (Sample Questions) for full end-to-end examples with evaluation.
page_numbersentence-transformers/all-MiniLM-L6-v2all-mpnet-base-v2 (768-dim) — better quality but 2x slower; overkill for this use case.where={"report_id": "..."}) — perfect for per-report retrievalhigh / low / normalThe system follows strict safety guidelines:
_is_diagnosis_seeking() detects keywords like "diagnose", "prescribe", "treatment for" and returns a safe refusal./ask-report response includes: "This is an AI-generated explanation based on the uploaded report and should not be treated as medical advice. Please consult a qualified doctor for diagnosis or treatment."| Error Case | HTTP Status | Response |
|---|---|---|
| Unsupported file type | 200 | {"status": "error", "message": "Unsupported file type. Only PDF and TXT files are allowed."} |
| Empty file | 200 | {"status": "error", "message": "Empty file."} |
| PDF extraction failure | 200 | {"status": "error", "message": "PDF extraction failed: ..."} |
| Invalid report ID | 200 | {"status": "error", "message": "Invalid report ID: ... Report not found."} |
| Empty question | 200 | {"status": "error", "message": "question is required."} |
| No relevant chunks | 200 | {"answer": "No relevant content found...", "sources": [], "disclaimer": "..."} |
| LLM API failure | 200 | Falls back through tiers (Gemini → HF → Local → rule-based) |
| Vector DB failure | 200 | Falls back to first N chunks from stored report |
| Not enough info | 200 | {"status": "error", "message": "Report does not contain enough information."} |
| Diagnosis-seeking | 200 | {"answer": "I cannot provide a medical diagnosis...", "disclaimer": "..."} |
Beyond the core assignment, this project includes:
Dockerfile + docker-compose.ymlusers_dbThis AI system is for EDUCATIONAL AND INFORMATIONAL PURPOSES ONLY.
8 commits
Python
92.6%
Shell
3.9%
TypeScript
2.1%