Instructions to use nsr51324/CortexRAG with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use nsr51324/CortexRAG with sentence-transformers:
from sentence_transformers import CrossEncoder model = CrossEncoder("nsr51324/CortexRAG") query = "Which planet is known as the Red Planet?" passages = [ "Venus is often called Earth's twin because of its similar size and proximity.", "Mars, known for its reddish appearance, is often referred to as the Red Planet.", "Jupiter, the largest planet in our solar system, has a prominent red spot.", "Saturn, famous for its rings, is sometimes mistaken for the Red Planet." ] scores = model.predict([(query, passage) for passage in passages]) print(scores) - Notebooks
- Google Colab
- Kaggle
Upload 21 files
Browse files- .gitattributes +1 -0
- API_DEPLOYMENT_PLAN.md +93 -0
- README.md +183 -106
- __pycache__/app.cpython-310.pyc +0 -0
- app.py +376 -0
- cloudflared.exe +3 -0
- gold_set_for_manual_review.xlsx +0 -0
- notebooks/Medical_RAG_Sytem.ipynb +0 -0
- rag_model/data/metadata_store.json +0 -0
.gitattributes
CHANGED
|
@@ -37,3 +37,4 @@ AHD_english.xlsx filter=lfs diff=lfs merge=lfs -text
|
|
| 37 |
notebooks/AHD_english_cleaned.xlsx filter=lfs diff=lfs merge=lfs -text
|
| 38 |
questions.index filter=lfs diff=lfs merge=lfs -text
|
| 39 |
rag_model/faiss/questions.index filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 37 |
notebooks/AHD_english_cleaned.xlsx filter=lfs diff=lfs merge=lfs -text
|
| 38 |
questions.index filter=lfs diff=lfs merge=lfs -text
|
| 39 |
rag_model/faiss/questions.index filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
cloudflared.exe filter=lfs diff=lfs merge=lfs -text
|
API_DEPLOYMENT_PLAN.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CortexRAG - API Deployment & Integration Guide
|
| 2 |
+
|
| 3 |
+
This document outlines the step-by-step roadmap to wrap the **CortexRAG** model into a production-ready FastAPI service, run it locally, expose it publicly via Cloudflare Tunnel, and provide integration documentation for external developers.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Workflow Overview
|
| 8 |
+
|
| 9 |
+
```mermaid
|
| 10 |
+
flowchart LR
|
| 11 |
+
A[Local RAG Model] --> B[FastAPI Wrapper]
|
| 12 |
+
B --> C[Swagger Testing /docs]
|
| 13 |
+
C --> D[Cloudflare Tunnel]
|
| 14 |
+
D --> E[Public HTTPS Endpoint]
|
| 15 |
+
E --> F[Client Applications]
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## Step-by-Step Implementation Roadmap
|
| 21 |
+
|
| 22 |
+
### Step 1: Model Code Verification & Preparation
|
| 23 |
+
- Verify model initialization, FAISS index loading, sentence-transformer embedding model, reranker, and LLM (e.g., Groq API / local LLM).
|
| 24 |
+
- Structure model code cleanly into a reusable class/module (e.g., `rag_pipeline.py`).
|
| 25 |
+
|
| 26 |
+
### Step 2: FastAPI Web Service (`app.py` / `main.py`)
|
| 27 |
+
- Create lightweight FastAPI application.
|
| 28 |
+
- Define request model (`QueryRequest`) and response model (`QueryResponse`).
|
| 29 |
+
- Define endpoint: `POST /query` (or `/predict`).
|
| 30 |
+
- Add lifecycle events (`lifespan` / `@app.on_event("startup")`) to load heavy ML/FAISS models once into memory on startup.
|
| 31 |
+
|
| 32 |
+
### Step 3: Local Testing via FastAPI Swagger UI
|
| 33 |
+
- Launch server locally:
|
| 34 |
+
```bash
|
| 35 |
+
uvicorn app:app --reload --host 127.0.0.1 --port 8000
|
| 36 |
+
```
|
| 37 |
+
- Navigate to `http://127.0.0.1:8000/docs` to test input payload, validation, error handling, and JSON output structure.
|
| 38 |
+
|
| 39 |
+
### Step 4: Developer Manual Verification
|
| 40 |
+
- Execute tests using `curl`, Postman, or Python `requests` script to verify:
|
| 41 |
+
- Valid queries return correct RAG answers and source documents.
|
| 42 |
+
- Invalid inputs return standard `422 Unprocessable Entity` or structured error messages.
|
| 43 |
+
|
| 44 |
+
### Step 5: Public Exposure via Cloudflare Tunnel
|
| 45 |
+
- Install Cloudflare CLI (`cloudflared`).
|
| 46 |
+
- Run ad-hoc public tunnel:
|
| 47 |
+
```bash
|
| 48 |
+
cloudflared tunnel --url http://127.0.0.1:8000
|
| 49 |
+
```
|
| 50 |
+
- Copy generated HTTPS URL (e.g., `https://your-tunnel-subdomain.trycloudflare.com`).
|
| 51 |
+
|
| 52 |
+
### Step 6: Public Endpoint Testing
|
| 53 |
+
- Validate public URL with live queries:
|
| 54 |
+
```bash
|
| 55 |
+
curl -X POST "https://your-tunnel-subdomain.trycloudflare.com/query" \
|
| 56 |
+
-H "Content-Type: application/json" \
|
| 57 |
+
-d "{\"question\": \"What are the symptoms of acute hypertension?\", \"top_k\": 5}"
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### Step 7: Developer Integration Specification
|
| 61 |
+
Provide client developers with exact details required for integration:
|
| 62 |
+
|
| 63 |
+
| Attribute | Value |
|
| 64 |
+
| :--- | :--- |
|
| 65 |
+
| **Base URL** | `https://<your-cloudflare-tunnel-url>` |
|
| 66 |
+
| **Endpoint** | `/query` |
|
| 67 |
+
| **HTTP Method** | `POST` |
|
| 68 |
+
| **Headers** | `Content-Type: application/json` |
|
| 69 |
+
|
| 70 |
+
#### Request Body (JSON)
|
| 71 |
+
```json
|
| 72 |
+
{
|
| 73 |
+
"question": "What are the common side effects of Lisinopril?",
|
| 74 |
+
"top_k": 6
|
| 75 |
+
}
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
#### Response Body (JSON)
|
| 79 |
+
```json
|
| 80 |
+
{
|
| 81 |
+
"status": "success",
|
| 82 |
+
"question": "What are the common side effects of Lisinopril?",
|
| 83 |
+
"answer": "Common side effects include dizziness, cough, headache...",
|
| 84 |
+
"sources": [
|
| 85 |
+
{
|
| 86 |
+
"id": 1024,
|
| 87 |
+
"text": "Lisinopril documentation excerpt...",
|
| 88 |
+
"score": 0.89
|
| 89 |
+
}
|
| 90 |
+
],
|
| 91 |
+
"execution_time_seconds": 0.42
|
| 92 |
+
}
|
| 93 |
+
```
|
README.md
CHANGED
|
@@ -1,149 +1,226 @@
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
| 5 |
tags:
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
- medical-qa
|
| 14 |
-
pipeline_tag: question-answering
|
| 15 |
-
license: apache-2.0
|
| 16 |
---
|
| 17 |
|
| 18 |
-
# Medical RAG
|
| 19 |
|
| 20 |
-
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
---
|
| 27 |
|
| 28 |
-
##
|
| 29 |
-
|
| 30 |
-
```
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
│
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
```
|
| 57 |
|
| 58 |
-
|
| 59 |
|
| 60 |
-
|
| 61 |
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
-
|
| 65 |
-
- **Query Expansion:** a small lay-term → medical-term dictionary is appended to the query before embedding (e.g. `underactive thyroid → hypothyroidism`, `high blood sugar → hyperglycemia`). The original query is never replaced, only extended.
|
| 66 |
-
- **Reranking:** `cross-encoder/ms-marco-MiniLM-L-6-v2` scores `(query, Answer)` — not `(query, Question)` — since many records share the same question with different answers; this identifies which *evidence* is actually useful.
|
| 67 |
-
- **Evidence deduplication:** near-identical answers are collapsed via `difflib.SequenceMatcher` (threshold 0.92).
|
| 68 |
|
| 69 |
-
##
|
| 70 |
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
```
|
| 79 |
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
|
|
|
| 84 |
|
| 85 |
-
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
|
| 88 |
|
| 89 |
-
|
| 90 |
|
| 91 |
-
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
|
| 100 |
-
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|---|---:|
|
| 104 |
-
| Knowledge Base Size | 16,384 records |
|
| 105 |
-
| Self-Retrieval Top-1 | 100% |
|
| 106 |
-
| Retrieval Recall@1 / @5 / @10 | 69.0% / 89.9% / 95.9% |
|
| 107 |
-
| Retrieval MRR | 1.000 |
|
| 108 |
-
| Retrieval + Reranker Recall@1 / @5 / @10 | 8.3% / 22.2% / 38.8% |
|
| 109 |
-
| Retrieval + Reranker MRR | 0.237 |
|
| 110 |
-
| Category Recall (retrieval / +reranker) | 97% / 98% |
|
| 111 |
-
| Confidence Gate Accuracy | 88% (n=50) |
|
| 112 |
-
| Example End-to-End Latency | 0.99 sec |
|
| 113 |
|
| 114 |
-
|
| 115 |
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
|
| 119 |
|
| 120 |
-
|
| 121 |
-
- Semantic similarity ≠ appropriate evidence (see broken-leg example).
|
| 122 |
-
- Reranker not fine-tuned on this medical domain yet.
|
| 123 |
-
- Manual gold set is still small — evaluation should be treated as preliminary.
|
| 124 |
-
- Not for diagnosis, emergencies, prescriptions, or personalized treatment.
|
| 125 |
|
| 126 |
-
##
|
|
|
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
|
|
|
| 130 |
```
|
| 131 |
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
| 135 |
```
|
| 136 |
|
| 137 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
-
|
|
|
|
|
|
|
| 140 |
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
- [Cross-Encoder](https://www.sbert.net/examples/applications/cross-encoder/README.html)
|
|
|
|
| 1 |
---
|
| 2 |
+
title: CortexRAG Medical RAG API
|
| 3 |
+
emoji: 🩺
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 8000
|
| 8 |
tags:
|
| 9 |
+
- rag
|
| 10 |
+
- medical-ai
|
| 11 |
+
- sentence-transformers
|
| 12 |
+
- faiss
|
| 13 |
+
- fastapi
|
| 14 |
+
- cross-encoder
|
| 15 |
+
- groq
|
|
|
|
|
|
|
|
|
|
| 16 |
---
|
| 17 |
|
| 18 |
+
# 🩺 CortexRAG - Advanced Medical RAG API
|
| 19 |
|
| 20 |
+
**CortexRAG** is a high-performance, domain-specific Retrieval-Augmented Generation (RAG) system engineered for medical and clinical question answering. It combines semantic vector search (FAISS + Sentence Transformers), cross-encoder re-ranking, and high-speed LLM inference (Groq / Llama) wrapped in a lightweight **FastAPI** REST interface.
|
| 21 |
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 🌟 Features
|
| 25 |
|
| 26 |
+
- **Semantic Embedding Engine**: `all-MiniLM-L6-v2` dense vector retrieval using FAISS indexing.
|
| 27 |
+
- **Precision Re-ranking**: Cross-encoder scoring (`cross-encoder/ms-marco-MiniLM-L-6-v2`) for optimal document relevance.
|
| 28 |
+
- **Medical Synonym Expansion**: Context-aware synonym mapping for expanded search recall.
|
| 29 |
+
- **Ultra-Fast REST API**: Built on FastAPI with asynchronous request handling and Pydantic validation.
|
| 30 |
+
- **Cloudflare Tunnel Ready**: Zero-trust public exposure without complex firewall configuration.
|
| 31 |
|
| 32 |
---
|
| 33 |
|
| 34 |
+
## 🏗 System Architecture
|
| 35 |
+
|
| 36 |
+
```
|
| 37 |
+
┌───────────────────────────┐
|
| 38 |
+
│ Client Request │
|
| 39 |
+
└─────────────┬─────────────┘
|
| 40 |
+
│ POST /query
|
| 41 |
+
▼
|
| 42 |
+
┌───────────────────────────┐
|
| 43 |
+
│ FastAPI Web Server │
|
| 44 |
+
└─────────────┬─────────────┘
|
| 45 |
+
│
|
| 46 |
+
┌─────────────────────┴─────────────────────┐
|
| 47 |
+
│ │
|
| 48 |
+
▼ ▼
|
| 49 |
+
┌───────────────────────────┐ ┌───────────────────────────┐
|
| 50 |
+
│ Query Vectorization │ │ Medical Synonym Expansion │
|
| 51 |
+
│ (all-MiniLM-L6-v2) │ └─────────────┬─────────────┘
|
| 52 |
+
└─────────────┬─────────────┘ │
|
| 53 |
+
│ │
|
| 54 |
+
└─────────────────────┬─────────────────────┘
|
| 55 |
+
│
|
| 56 |
+
▼
|
| 57 |
+
┌───────────────────────────┐
|
| 58 |
+
│ FAISS Vector Index │
|
| 59 |
+
└─────────────┬─────────────┘
|
| 60 |
+
│ Top-N Candidate Docs
|
| 61 |
+
▼
|
| 62 |
+
┌───────────────────────────┐
|
| 63 |
+
│ Cross-Encoder Reranker │
|
| 64 |
+
│ (ms-marco-MiniLM-L-6-v2) │
|
| 65 |
+
└─────────────┬─────────────┘
|
| 66 |
+
│ Top-K Ranked Context
|
| 67 |
+
▼
|
| 68 |
+
┌───────────────────────────┐
|
| 69 |
+
│ LLM Synthesis (Groq) │
|
| 70 |
+
└─────────────┬─────────────┘
|
| 71 |
+
│
|
| 72 |
+
▼
|
| 73 |
+
┌───────────────────────────┐
|
| 74 |
+
│ JSON API Response │
|
| 75 |
+
└───────────────────────────┘
|
| 76 |
```
|
| 77 |
|
| 78 |
+
---
|
| 79 |
|
| 80 |
+
## 📁 Repository Structure
|
| 81 |
|
| 82 |
+
```
|
| 83 |
+
CortexRAG/
|
| 84 |
+
├── API_DEPLOYMENT_PLAN.md # Step-by-step API & tunnel setup documentation
|
| 85 |
+
├── README.md # Hugging Face & GitHub Project Card
|
| 86 |
+
├── question_embeddings.npy # Pre-computed dense embeddings matrix
|
| 87 |
+
├── questions.index # Binary FAISS vector search index
|
| 88 |
+
├── notebooks/ # Experimental notebooks & cleaning scripts
|
| 89 |
+
│ ├── Medical_RAG_Sytem.ipynb
|
| 90 |
+
│ └── rag_data_cleaning.ipynb
|
| 91 |
+
└── rag_model/ # Core RAG engine configurations & resources
|
| 92 |
+
├── rag_config.json # Search, score & model parameters
|
| 93 |
+
├── requirements.txt # Python dependency specifications
|
| 94 |
+
└── models/ # Synonyms & model metadata
|
| 95 |
+
└── medical_synonyms.json
|
| 96 |
+
```
|
| 97 |
|
| 98 |
+
---
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
+
## ⚡ Quick Start & Installation
|
| 101 |
|
| 102 |
+
### 1. Prerequisites
|
| 103 |
+
- Python 3.9+
|
| 104 |
+
- Pip package manager
|
| 105 |
|
| 106 |
+
### 2. Environment Setup
|
| 107 |
+
```bash
|
| 108 |
+
# Clone repository
|
| 109 |
+
git clone https://huggingface.co/spaces/YOUR_USERNAME/CortexRAG
|
| 110 |
+
cd CortexRAG
|
| 111 |
+
|
| 112 |
+
# Create virtual environment
|
| 113 |
+
python -m venv venv
|
| 114 |
+
# Activate on Windows:
|
| 115 |
+
venv\Scripts\activate
|
| 116 |
+
# Activate on Linux/macOS:
|
| 117 |
+
source venv/bin/activate
|
| 118 |
+
|
| 119 |
+
# Install dependencies
|
| 120 |
+
pip install -r rag_model/requirements.txt fastapi uvicorn pydantic
|
| 121 |
```
|
| 122 |
|
| 123 |
+
### 3. Environment Variables
|
| 124 |
+
Set your Groq API Key (or other LLM provider keys):
|
| 125 |
+
```bash
|
| 126 |
+
# Windows PowerShell
|
| 127 |
+
$env:GROQ_API_KEY="your_groq_api_key_here"
|
| 128 |
|
| 129 |
+
# Linux/macOS
|
| 130 |
+
export GROQ_API_KEY="your_groq_api_key_here"
|
| 131 |
+
```
|
| 132 |
|
| 133 |
+
---
|
| 134 |
|
| 135 |
+
## 🚀 Running the Local API
|
| 136 |
|
| 137 |
+
Start the server using `uvicorn`:
|
| 138 |
|
| 139 |
+
```bash
|
| 140 |
+
uvicorn app:app --host 127.0.0.1 --port 8000 --reload
|
| 141 |
+
```
|
| 142 |
|
| 143 |
+
Interactive API Documentation (Swagger UI) is available at:
|
| 144 |
+
👉 **`http://127.0.0.1:8000/docs`**
|
| 145 |
|
| 146 |
+
---
|
| 147 |
|
| 148 |
+
## 🌐 Exposing Publicly via Cloudflare Tunnel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
+
To expose your local FastAPI server securely to the internet without port forwarding:
|
| 151 |
|
| 152 |
+
1. Download [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-local-tunnel/).
|
| 153 |
+
2. Run the tunnel pointing to your local port:
|
| 154 |
+
```bash
|
| 155 |
+
cloudflared tunnel --url http://127.0.0.1:8000
|
| 156 |
+
```
|
| 157 |
+
3. Use the generated URL (e.g. `https://xxx.trycloudflare.com`) as your public API endpoint.
|
| 158 |
|
| 159 |
+
---
|
| 160 |
|
| 161 |
+
## 🔌 API Reference & Integration Guide
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
+
### Endpoint
|
| 164 |
+
`POST /query`
|
| 165 |
|
| 166 |
+
### Request Headers
|
| 167 |
+
```http
|
| 168 |
+
Content-Type: application/json
|
| 169 |
```
|
| 170 |
|
| 171 |
+
### Request Payload Example
|
| 172 |
+
```json
|
| 173 |
+
{
|
| 174 |
+
"question": "What are the first-line treatments for type 2 diabetes?",
|
| 175 |
+
"top_k": 6
|
| 176 |
+
}
|
| 177 |
```
|
| 178 |
|
| 179 |
+
### Response Payload Example
|
| 180 |
+
```json
|
| 181 |
+
{
|
| 182 |
+
"status": "success",
|
| 183 |
+
"question": "What are the first-line treatments for type 2 diabetes?",
|
| 184 |
+
"answer": "First-line pharmacological management for type 2 diabetes typically includes Metformin alongside lifestyle modifications...",
|
| 185 |
+
"retrieved_context": [
|
| 186 |
+
{
|
| 187 |
+
"doc_id": 42,
|
| 188 |
+
"text": "Metformin remains the initial drug of choice for monotherapy...",
|
| 189 |
+
"rerank_score": 4.85
|
| 190 |
+
}
|
| 191 |
+
],
|
| 192 |
+
"execution_time_sec": 0.38
|
| 193 |
+
}
|
| 194 |
+
```
|
| 195 |
|
| 196 |
+
### Python Integration Example
|
| 197 |
+
```python
|
| 198 |
+
import requests
|
| 199 |
|
| 200 |
+
url = "https://your-cloudflare-url.trycloudflare.com/query"
|
| 201 |
+
payload = {
|
| 202 |
+
"question": "What are the common causes of chest pain?",
|
| 203 |
+
"top_k": 5
|
| 204 |
+
}
|
| 205 |
+
headers = {"Content-Type": "application/json"}
|
| 206 |
|
| 207 |
+
response = requests.post(url, json=payload, headers=headers)
|
| 208 |
+
print(response.json())
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
|
| 213 |
+
## 🛠 Configuration Parameters (`rag_config.json`)
|
| 214 |
+
|
| 215 |
+
| Parameter | Default | Description |
|
| 216 |
+
| :--- | :--- | :--- |
|
| 217 |
+
| `embedding_model` | `all-MiniLM-L6-v2` | SentenceTransformer embedding model |
|
| 218 |
+
| `reranker_model` | `cross-encoder/ms-marco-MiniLM-L-6-v2` | Precision reranking cross-encoder model |
|
| 219 |
+
| `retrieve_top_n` | `20` | Initial FAISS vector retrieval candidate count |
|
| 220 |
+
| `rerank_top_k` | `6` | Number of context snippets passed to LLM |
|
| 221 |
+
| `min_similarity_floor` | `0.4` | Cosine similarity threshold |
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
|
| 225 |
+
## 📜 License
|
| 226 |
+
This project is released under the **MIT License**.
|
|
|
__pycache__/app.cpython-310.pyc
ADDED
|
Binary file (12.1 kB). View file
|
|
|
app.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
import json
|
| 4 |
+
import difflib
|
| 5 |
+
from typing import List, Optional, Dict, Any
|
| 6 |
+
from contextlib import asynccontextmanager
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import faiss
|
| 11 |
+
from sentence_transformers import SentenceTransformer, CrossEncoder
|
| 12 |
+
from groq import Groq
|
| 13 |
+
|
| 14 |
+
from fastapi import FastAPI, HTTPException, status
|
| 15 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 16 |
+
from pydantic import BaseModel, Field
|
| 17 |
+
|
| 18 |
+
# ==========================================
|
| 19 |
+
# Paths & Default Configurations
|
| 20 |
+
# ==========================================
|
| 21 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 22 |
+
FAISS_INDEX_PATH = os.path.join(BASE_DIR, "questions.index")
|
| 23 |
+
DATASET_PATH = os.path.join(BASE_DIR, "notebooks", "AHD_english_cleaned.xlsx")
|
| 24 |
+
SYNONYMS_PATH = os.path.join(BASE_DIR, "rag_model", "models", "medical_synonyms.json")
|
| 25 |
+
CONFIG_PATH = os.path.join(BASE_DIR, "rag_model", "rag_config.json")
|
| 26 |
+
|
| 27 |
+
DEFAULT_GROQ_API_KEY = os.getenv("GROQ_API_KEY", "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")
|
| 28 |
+
LLM_MODEL = os.getenv("LLM_MODEL", "openai/gpt-oss-20b")
|
| 29 |
+
|
| 30 |
+
# Load config if available
|
| 31 |
+
if os.path.exists(CONFIG_PATH):
|
| 32 |
+
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
| 33 |
+
RAG_CONFIG = json.load(f)
|
| 34 |
+
else:
|
| 35 |
+
RAG_CONFIG = {
|
| 36 |
+
"embedding_model": "all-MiniLM-L6-v2",
|
| 37 |
+
"reranker_model": "cross-encoder/ms-marco-MiniLM-L-6-v2",
|
| 38 |
+
"retrieve_top_n": 20,
|
| 39 |
+
"rerank_top_k": 6,
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
# Dynamic Global State
|
| 43 |
+
rag_resources: Dict[str, Any] = {}
|
| 44 |
+
|
| 45 |
+
SYSTEM_PROMPT = """
|
| 46 |
+
You are a medical RAG assistant specialized ONLY in:
|
| 47 |
+
- Diabetes
|
| 48 |
+
- Endocrinology
|
| 49 |
+
- Thyroid disorders
|
| 50 |
+
- Hormonal disorders
|
| 51 |
+
- Endocrine glands and related disorders
|
| 52 |
+
- Nutrition, glucose management, insulin, and complications DIRECTLY related to diabetes/endocrinology
|
| 53 |
+
|
| 54 |
+
You operate in STRICT RAG MODE.
|
| 55 |
+
|
| 56 |
+
==================================================
|
| 57 |
+
CRITICAL DOMAIN RULE
|
| 58 |
+
==================================================
|
| 59 |
+
The USER QUESTION itself determines whether the question is in-domain.
|
| 60 |
+
A question is IN-DOMAIN only if its actual subject is diabetes, endocrinology,
|
| 61 |
+
thyroid, hormones, endocrine glands/disorders, or a problem explicitly stated
|
| 62 |
+
by the user to be related to diabetes/endocrinology.
|
| 63 |
+
|
| 64 |
+
If the user's actual question is about another body system or another medical
|
| 65 |
+
specialty, it is OUT-OF-DOMAIN.
|
| 66 |
+
|
| 67 |
+
==================================================
|
| 68 |
+
TYPE 1 — IN-DOMAIN MEDICAL QUESTION
|
| 69 |
+
==================================================
|
| 70 |
+
If and ONLY IF the USER QUESTION itself is in-domain:
|
| 71 |
+
- Answer using ONLY the RETRIEVED MEDICAL EVIDENCE.
|
| 72 |
+
- Do NOT use pretrained/background medical knowledge.
|
| 73 |
+
- Do NOT guess or invent missing details.
|
| 74 |
+
- Cite supporting [doc_id] for every medical claim.
|
| 75 |
+
|
| 76 |
+
==================================================
|
| 77 |
+
TYPE 2 — APP / IDENTITY QUESTIONS
|
| 78 |
+
==================================================
|
| 79 |
+
Only for direct questions about the assistant/application itself:
|
| 80 |
+
Answer briefly and naturally.
|
| 81 |
+
|
| 82 |
+
==================================================
|
| 83 |
+
TYPE 3 — EVERYTHING ELSE / OUT OF DOMAIN / INSUFFICIENT EVIDENCE
|
| 84 |
+
==================================================
|
| 85 |
+
For ALL TYPE 3 cases, reply with ONLY one short refusal:
|
| 86 |
+
If the user wrote in Arabic:
|
| 87 |
+
"معرفش، السؤال ده مش جزء من تخصصي (السكر والغدد الصماء)."
|
| 88 |
+
If the user wrote in English:
|
| 89 |
+
"I don't know — this is outside my specialty (diabetes and endocrinology)."
|
| 90 |
+
|
| 91 |
+
==================================================
|
| 92 |
+
FINAL SAFETY NOTE FOR TYPE 1 ONLY
|
| 93 |
+
==================================================
|
| 94 |
+
At the end of every TYPE 1 medical answer, write:
|
| 95 |
+
"This information is based on the available medical evidence and is for general informational purposes. It is not a diagnosis or a substitute for professional medical advice."
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
# ==========================================
|
| 99 |
+
# RAG Helper Logic
|
| 100 |
+
# ==========================================
|
| 101 |
+
def expand_query(query: str, synonyms: dict) -> str:
|
| 102 |
+
lower_q = query.lower()
|
| 103 |
+
extra_terms = [
|
| 104 |
+
med_term
|
| 105 |
+
for phrase, med_term in synonyms.items()
|
| 106 |
+
if phrase in lower_q and med_term not in lower_q
|
| 107 |
+
]
|
| 108 |
+
if not extra_terms:
|
| 109 |
+
return query
|
| 110 |
+
return f"{query} ({', '.join(dict.fromkeys(extra_terms))})"
|
| 111 |
+
|
| 112 |
+
def vector_search(query: str, top_n: int = 20):
|
| 113 |
+
embedder = rag_resources["embedder"]
|
| 114 |
+
faiss_index = rag_resources["faiss_index"]
|
| 115 |
+
metadata_store = rag_resources["metadata_store"]
|
| 116 |
+
synonyms = rag_resources["synonyms"]
|
| 117 |
+
|
| 118 |
+
query_for_embedding = expand_query(query, synonyms)
|
| 119 |
+
q_emb = embedder.encode(
|
| 120 |
+
[query_for_embedding],
|
| 121 |
+
convert_to_numpy=True,
|
| 122 |
+
normalize_embeddings=True
|
| 123 |
+
).astype("float32")
|
| 124 |
+
|
| 125 |
+
scores, idxs = faiss_index.search(q_emb, top_n)
|
| 126 |
+
retrieved = []
|
| 127 |
+
for score, idx in zip(scores[0], idxs[0]):
|
| 128 |
+
if idx == -1:
|
| 129 |
+
continue
|
| 130 |
+
payload = metadata_store[int(idx)]
|
| 131 |
+
retrieved.append({
|
| 132 |
+
"doc_id": payload["doc_id"],
|
| 133 |
+
"Question": payload["question"],
|
| 134 |
+
"Answer": payload["answer"],
|
| 135 |
+
"Category": payload["category"],
|
| 136 |
+
"similarity": float(score),
|
| 137 |
+
})
|
| 138 |
+
return pd.DataFrame(retrieved)
|
| 139 |
+
|
| 140 |
+
def rerank(query: str, candidates: pd.DataFrame, top_k: int = 6, alpha: float = 0.6) -> pd.DataFrame:
|
| 141 |
+
if candidates.empty:
|
| 142 |
+
return candidates
|
| 143 |
+
|
| 144 |
+
reranker = rag_resources["reranker"]
|
| 145 |
+
pairs = [
|
| 146 |
+
(query, f"Question: {row['Question']}\nAnswer: {row['Answer']}")
|
| 147 |
+
for _, row in candidates.iterrows()
|
| 148 |
+
]
|
| 149 |
+
|
| 150 |
+
rerank_scores = reranker.predict(pairs)
|
| 151 |
+
reranked = candidates.copy()
|
| 152 |
+
reranked["rerank_score"] = rerank_scores
|
| 153 |
+
|
| 154 |
+
def norm(s):
|
| 155 |
+
s = s.astype(float)
|
| 156 |
+
rng = s.max() - s.min()
|
| 157 |
+
return (s - s.min()) / rng if rng > 0 else s * 0
|
| 158 |
+
|
| 159 |
+
reranked["sim_norm"] = norm(reranked["similarity"])
|
| 160 |
+
reranked["rerank_norm"] = norm(reranked["rerank_score"])
|
| 161 |
+
reranked["final_score"] = alpha * reranked["rerank_norm"] + (1 - alpha) * reranked["sim_norm"]
|
| 162 |
+
|
| 163 |
+
return reranked.sort_values("final_score", ascending=False).head(top_k).reset_index(drop=True)
|
| 164 |
+
|
| 165 |
+
def is_near_duplicate(text_a: str, text_b: str, threshold: float = 0.92) -> bool:
|
| 166 |
+
return difflib.SequenceMatcher(None, text_a, text_b).ratio() > threshold
|
| 167 |
+
|
| 168 |
+
def build_evidence(reranked: pd.DataFrame, max_answer_chars: int = 700) -> List[dict]:
|
| 169 |
+
evidence = []
|
| 170 |
+
seen_answers = []
|
| 171 |
+
|
| 172 |
+
for _, row in reranked.iterrows():
|
| 173 |
+
answer = str(row["Answer"])
|
| 174 |
+
if any(is_near_duplicate(answer, seen) for seen in seen_answers):
|
| 175 |
+
continue
|
| 176 |
+
seen_answers.append(answer)
|
| 177 |
+
|
| 178 |
+
evidence.append({
|
| 179 |
+
"doc_id": int(row["doc_id"]),
|
| 180 |
+
"question": str(row["Question"]),
|
| 181 |
+
"answer": answer[:max_answer_chars] + ("..." if len(answer) > max_answer_chars else ""),
|
| 182 |
+
"category": str(row["Category"]),
|
| 183 |
+
"similarity": round(float(row["similarity"]), 3),
|
| 184 |
+
"rerank_score": round(float(row["rerank_score"]), 3),
|
| 185 |
+
})
|
| 186 |
+
return evidence
|
| 187 |
+
|
| 188 |
+
def generate_answer(query: str, user_data: str, evidence: List[dict]) -> str:
|
| 189 |
+
groq_client = rag_resources["groq_client"]
|
| 190 |
+
if not evidence:
|
| 191 |
+
return "I don't know — this is outside my specialty (diabetes and endocrinology)."
|
| 192 |
+
|
| 193 |
+
evidence_blocks = [
|
| 194 |
+
f"[{e['doc_id']}] (category: {e['category']})\nRelated question: {e['question']}\nAnswer: {e['answer']}"
|
| 195 |
+
for e in evidence
|
| 196 |
+
]
|
| 197 |
+
evidence_text = "\n\n".join(evidence_blocks)
|
| 198 |
+
|
| 199 |
+
user_message = f"""
|
| 200 |
+
USER QUESTION:
|
| 201 |
+
{query}
|
| 202 |
+
|
| 203 |
+
USER DATA:
|
| 204 |
+
{user_data}
|
| 205 |
+
|
| 206 |
+
RETRIEVED MEDICAL EVIDENCE:
|
| 207 |
+
{evidence_text}
|
| 208 |
+
|
| 209 |
+
TASK:
|
| 210 |
+
Answer the USER QUESTION using ONLY the RETRIEVED MEDICAL EVIDENCE.
|
| 211 |
+
Cite every important medical claim using the relevant [doc_id].
|
| 212 |
+
If the evidence is insufficient or irrelevant, give a clear refusal.
|
| 213 |
+
"""
|
| 214 |
+
|
| 215 |
+
try:
|
| 216 |
+
response = groq_client.chat.completions.create(
|
| 217 |
+
model=LLM_MODEL,
|
| 218 |
+
messages=[
|
| 219 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 220 |
+
{"role": "user", "content": user_message}
|
| 221 |
+
],
|
| 222 |
+
temperature=0.1,
|
| 223 |
+
max_tokens=800,
|
| 224 |
+
)
|
| 225 |
+
return response.choices[0].message.content
|
| 226 |
+
except Exception as e:
|
| 227 |
+
# Fallback or error handling for Groq API
|
| 228 |
+
return f"Error generating answer from LLM: {str(e)}"
|
| 229 |
+
|
| 230 |
+
# ==========================================
|
| 231 |
+
# FastAPI Lifecycle & App Initialization
|
| 232 |
+
# ==========================================
|
| 233 |
+
@asynccontextmanager
|
| 234 |
+
async def lifespan(app: FastAPI):
|
| 235 |
+
print("Loading RAG resources...")
|
| 236 |
+
|
| 237 |
+
# Load FAISS index
|
| 238 |
+
if not os.path.exists(FAISS_INDEX_PATH):
|
| 239 |
+
raise RuntimeError(f"FAISS index file not found at: {FAISS_INDEX_PATH}")
|
| 240 |
+
faiss_index = faiss.read_index(FAISS_INDEX_PATH)
|
| 241 |
+
|
| 242 |
+
# Load Dataset for Metadata
|
| 243 |
+
if not os.path.exists(DATASET_PATH):
|
| 244 |
+
raise RuntimeError(f"Dataset file not found at: {DATASET_PATH}")
|
| 245 |
+
df = pd.read_excel(DATASET_PATH, engine="openpyxl").reset_index(drop=True)
|
| 246 |
+
df["doc_id"] = df.index
|
| 247 |
+
metadata_store = [
|
| 248 |
+
{
|
| 249 |
+
"doc_id": row["doc_id"],
|
| 250 |
+
"question": row["Question"],
|
| 251 |
+
"answer": row["Answer"],
|
| 252 |
+
"category": row["Category"],
|
| 253 |
+
}
|
| 254 |
+
for _, row in df.iterrows()
|
| 255 |
+
]
|
| 256 |
+
|
| 257 |
+
# Load Synonyms
|
| 258 |
+
synonyms = {}
|
| 259 |
+
if os.path.exists(SYNONYMS_PATH):
|
| 260 |
+
with open(SYNONYMS_PATH, "r", encoding="utf-8") as f:
|
| 261 |
+
synonyms = json.load(f)
|
| 262 |
+
|
| 263 |
+
# Load Transformer Models
|
| 264 |
+
embedder = SentenceTransformer(RAG_CONFIG.get("embedding_model", "all-MiniLM-L6-v2"))
|
| 265 |
+
reranker = CrossEncoder(RAG_CONFIG.get("reranker_model", "cross-encoder/ms-marco-MiniLM-L-6-v2"))
|
| 266 |
+
|
| 267 |
+
# Load Groq Client
|
| 268 |
+
groq_client = Groq(api_key=DEFAULT_GROQ_API_KEY)
|
| 269 |
+
|
| 270 |
+
# Save to global state
|
| 271 |
+
rag_resources["faiss_index"] = faiss_index
|
| 272 |
+
rag_resources["metadata_store"] = metadata_store
|
| 273 |
+
rag_resources["synonyms"] = synonyms
|
| 274 |
+
rag_resources["embedder"] = embedder
|
| 275 |
+
rag_resources["reranker"] = reranker
|
| 276 |
+
rag_resources["groq_client"] = groq_client
|
| 277 |
+
|
| 278 |
+
print(f"RAG resources loaded successfully! Index size: {faiss_index.ntotal}")
|
| 279 |
+
yield
|
| 280 |
+
print("Shutting down RAG resources.")
|
| 281 |
+
rag_resources.clear()
|
| 282 |
+
|
| 283 |
+
app = FastAPI(
|
| 284 |
+
title="CortexRAG Medical API",
|
| 285 |
+
description="Production-ready FastAPI interface for CortexRAG Medical Question Answering Engine.",
|
| 286 |
+
version="1.0.0",
|
| 287 |
+
lifespan=lifespan
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
app.add_middleware(
|
| 291 |
+
CORSMiddleware,
|
| 292 |
+
allow_origins=["*"],
|
| 293 |
+
allow_credentials=True,
|
| 294 |
+
allow_methods=["*"],
|
| 295 |
+
allow_headers=["*"],
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
# ==========================================
|
| 299 |
+
# Request / Response Schemas
|
| 300 |
+
# ==========================================
|
| 301 |
+
class QueryRequest(BaseModel):
|
| 302 |
+
question: str = Field(..., example="What are the common symptoms of hypothyroidism?", min_length=2)
|
| 303 |
+
user_data: Optional[str] = Field(default="", example="Age: 45, Gender: Female")
|
| 304 |
+
top_k: Optional[int] = Field(default=6, ge=1, le=20)
|
| 305 |
+
|
| 306 |
+
class EvidenceItem(BaseModel):
|
| 307 |
+
doc_id: int
|
| 308 |
+
question: str
|
| 309 |
+
answer: str
|
| 310 |
+
category: str
|
| 311 |
+
similarity: float
|
| 312 |
+
rerank_score: float
|
| 313 |
+
|
| 314 |
+
class QueryResponse(BaseModel):
|
| 315 |
+
status: str
|
| 316 |
+
question: str
|
| 317 |
+
answer: str
|
| 318 |
+
evidence: List[EvidenceItem]
|
| 319 |
+
execution_time_sec: float
|
| 320 |
+
|
| 321 |
+
# ==========================================
|
| 322 |
+
# Endpoints
|
| 323 |
+
# ==========================================
|
| 324 |
+
@app.get("/", tags=["Health"])
|
| 325 |
+
def root():
|
| 326 |
+
return {
|
| 327 |
+
"status": "online",
|
| 328 |
+
"service": "CortexRAG Medical API",
|
| 329 |
+
"documentation": "/docs"
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
@app.get("/health", tags=["Health"])
|
| 333 |
+
def health_check():
|
| 334 |
+
return {
|
| 335 |
+
"status": "healthy",
|
| 336 |
+
"faiss_index_entries": rag_resources.get("faiss_index", None).ntotal if "faiss_index" in rag_resources else 0,
|
| 337 |
+
"model_loaded": "embedder" in rag_resources
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
@app.post("/predict", response_model=QueryResponse, tags=["RAG Inference"])
|
| 341 |
+
@app.post("/query", response_model=QueryResponse, tags=["RAG Inference"])
|
| 342 |
+
def predict(payload: QueryRequest):
|
| 343 |
+
start_time = time.time()
|
| 344 |
+
|
| 345 |
+
try:
|
| 346 |
+
# Step 1: Vector Search
|
| 347 |
+
candidates = vector_search(payload.question, top_n=RAG_CONFIG.get("retrieve_top_n", 20))
|
| 348 |
+
|
| 349 |
+
# Step 2: Cross Encoder Rerank
|
| 350 |
+
reranked_df = rerank(payload.question, candidates, top_k=payload.top_k)
|
| 351 |
+
|
| 352 |
+
# Step 3: Deduplicate Evidence
|
| 353 |
+
evidence = build_evidence(reranked_df)
|
| 354 |
+
|
| 355 |
+
# Step 4: Generate LLM Answer
|
| 356 |
+
answer = generate_answer(payload.question, payload.user_data or "", evidence)
|
| 357 |
+
|
| 358 |
+
elapsed = round(time.time() - start_time, 3)
|
| 359 |
+
|
| 360 |
+
return QueryResponse(
|
| 361 |
+
status="success",
|
| 362 |
+
question=payload.question,
|
| 363 |
+
answer=answer,
|
| 364 |
+
evidence=[EvidenceItem(**item) for item in evidence],
|
| 365 |
+
execution_time_sec=elapsed
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
except Exception as e:
|
| 369 |
+
raise HTTPException(
|
| 370 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 371 |
+
detail=f"An error occurred during inference: {str(e)}"
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
if __name__ == "__main__":
|
| 375 |
+
import uvicorn
|
| 376 |
+
uvicorn.run("app:app", host="127.0.0.1", port=8000, reload=True)
|
cloudflared.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c29eee2b121f5436a642eed69fd9767da7e7b8c510fa50aaa130337f931357b5
|
| 3 |
+
size 54893480
|
gold_set_for_manual_review.xlsx
CHANGED
|
Binary files a/gold_set_for_manual_review.xlsx and b/gold_set_for_manual_review.xlsx differ
|
|
|
notebooks/Medical_RAG_Sytem.ipynb
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
rag_model/data/metadata_store.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|