Spaces:
Running
Running
vihashini-18
commited on
Commit
·
0a5c991
1
Parent(s):
1ca78c7
- .gitignore +42 -0
- EXAMPLES.env +11 -0
- IMPROVEMENTS.md +91 -0
- QUICK_START.md +92 -0
- README.md +97 -11
- START.md +81 -0
- app.py +154 -0
- config.py +28 -0
- data_loader.py +178 -0
- embedding_service.py +90 -0
- enhanced_data_loader.py +215 -0
- medical_chatbot.py +214 -0
- requirements.txt +11 -0
- setup_database.py +42 -0
.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment variables
|
| 2 |
+
.env
|
| 3 |
+
|
| 4 |
+
# Python
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.py[cod]
|
| 7 |
+
*$py.class
|
| 8 |
+
*.so
|
| 9 |
+
.Python
|
| 10 |
+
env/
|
| 11 |
+
venv/
|
| 12 |
+
ENV/
|
| 13 |
+
build/
|
| 14 |
+
develop-eggs/
|
| 15 |
+
dist/
|
| 16 |
+
downloads/
|
| 17 |
+
eggs/
|
| 18 |
+
.eggs/
|
| 19 |
+
lib/
|
| 20 |
+
lib64/
|
| 21 |
+
parts/
|
| 22 |
+
sdist/
|
| 23 |
+
var/
|
| 24 |
+
wheels/
|
| 25 |
+
*.egg-info/
|
| 26 |
+
.installed.cfg
|
| 27 |
+
*.egg
|
| 28 |
+
|
| 29 |
+
# Streamlit
|
| 30 |
+
.streamlit/
|
| 31 |
+
|
| 32 |
+
# IDE
|
| 33 |
+
.vscode/
|
| 34 |
+
.idea/
|
| 35 |
+
*.swp
|
| 36 |
+
*.swo
|
| 37 |
+
*~
|
| 38 |
+
|
| 39 |
+
# OS
|
| 40 |
+
.DS_Store
|
| 41 |
+
Thumbs.db
|
| 42 |
+
|
EXAMPLES.env
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copy this file to .env and fill in your actual API keys
|
| 2 |
+
|
| 3 |
+
# Get your Pinecone API key from: https://www.pinecone.io/
|
| 4 |
+
PINECONE_API_KEY=your_pinecone_api_key_here
|
| 5 |
+
|
| 6 |
+
# Pinecone environment (usually us-east1, us-west1, etc.)
|
| 7 |
+
PINECONE_ENVIRONMENT=us-east1
|
| 8 |
+
|
| 9 |
+
# Get your Google API key from: https://makersuite.google.com/app/apikey
|
| 10 |
+
GOOGLE_API_KEY=your_google_api_key_here
|
| 11 |
+
|
IMPROVEMENTS.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Medical Chatbot - Recent Improvements
|
| 2 |
+
|
| 3 |
+
## Issues Fixed
|
| 4 |
+
|
| 5 |
+
### 1. Model Initialization Error
|
| 6 |
+
**Problem**: "404 models/gemini-1.5-flash is not found"
|
| 7 |
+
**Solution**:
|
| 8 |
+
- Added automatic model fallback mechanism
|
| 9 |
+
- Tries multiple model names until one works:
|
| 10 |
+
- `models/gemini-pro`
|
| 11 |
+
- `gemini-pro`
|
| 12 |
+
- `models/gemini-1.5-pro`
|
| 13 |
+
- `gemini-1.5-pro`
|
| 14 |
+
|
| 15 |
+
### 2. Wrong/Inaccurate Answers
|
| 16 |
+
**Problem**: The model was giving incorrect or irrelevant answers
|
| 17 |
+
|
| 18 |
+
**Solutions Applied**:
|
| 19 |
+
|
| 20 |
+
#### A. Improved Prompt Engineering
|
| 21 |
+
- **Before**: Complex multi-step instructions
|
| 22 |
+
- **After**: Direct, clear instructions to use ONLY context information
|
| 23 |
+
- Added "DO NOT make up or guess information"
|
| 24 |
+
- Structured prompt with clear sections
|
| 25 |
+
|
| 26 |
+
#### B. Lower Temperature Setting
|
| 27 |
+
- Set `temperature=0.3` (default is 0.7)
|
| 28 |
+
- This makes responses more factual and less creative
|
| 29 |
+
- Better for medical information accuracy
|
| 30 |
+
|
| 31 |
+
#### C. Better Context Formatting
|
| 32 |
+
- Clear source citations in context
|
| 33 |
+
- Better structured context presentation
|
| 34 |
+
- Easier for model to parse and use information
|
| 35 |
+
|
| 36 |
+
#### D. Enhanced Generation Config
|
| 37 |
+
```python
|
| 38 |
+
generation_config={
|
| 39 |
+
"temperature": 0.3, # Lower for factual responses
|
| 40 |
+
"top_p": 0.8, # Nucleus sampling
|
| 41 |
+
"top_k": 40, # Token selection limit
|
| 42 |
+
"max_output_tokens": 500, # Concise responses
|
| 43 |
+
}
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
#### E. Improved Retrieval
|
| 47 |
+
- Filters results by similarity threshold (0.5)
|
| 48 |
+
- Only returns highly relevant medical content
|
| 49 |
+
- Better context quality = better answers
|
| 50 |
+
|
| 51 |
+
## Current Configuration
|
| 52 |
+
|
| 53 |
+
- **Embedding Model**: sentence-transformers/all-MiniLM-L6-v2
|
| 54 |
+
- **LLM Model**: Auto-detected Gemini model
|
| 55 |
+
- **Database**: 3,012 medical documents from MultiMedQA
|
| 56 |
+
- **Top K Retrieval**: 5 most relevant chunks
|
| 57 |
+
- **Similarity Threshold**: 0.5 (minimum relevance score)
|
| 58 |
+
|
| 59 |
+
## How It Works Now
|
| 60 |
+
|
| 61 |
+
1. **User asks a medical question**
|
| 62 |
+
2. **Query is embedded** using Sentence Transformers
|
| 63 |
+
3. **Pinecone searches** for similar medical content (top 5 results)
|
| 64 |
+
4. **Results are filtered** by similarity score (≥0.5)
|
| 65 |
+
5. **Context is formatted** with clear citations
|
| 66 |
+
6. **Gemini generates answer** using ONLY the retrieved context
|
| 67 |
+
7. **Response includes**:
|
| 68 |
+
- Factual answer from medical database
|
| 69 |
+
- Citations with sources
|
| 70 |
+
- Confidence score
|
| 71 |
+
- Medical disclaimer
|
| 72 |
+
|
| 73 |
+
## Testing the Improvements
|
| 74 |
+
|
| 75 |
+
Try these questions to verify accuracy:
|
| 76 |
+
- "What are the symptoms of diabetes?"
|
| 77 |
+
- "How is hypertension treated?"
|
| 78 |
+
- "Explain cardiac arrhythmia"
|
| 79 |
+
- "What causes chest pain?"
|
| 80 |
+
|
| 81 |
+
## Key Improvements Summary
|
| 82 |
+
|
| 83 |
+
✅ Model auto-detection (tries multiple models)
|
| 84 |
+
✅ Lower temperature for factual responses
|
| 85 |
+
✅ Clearer prompt instructions
|
| 86 |
+
✅ Better context formatting
|
| 87 |
+
✅ Improved error handling
|
| 88 |
+
✅ Debug logging for troubleshooting
|
| 89 |
+
|
| 90 |
+
The chatbot should now provide **accurate, factual medical information** based solely on the retrieved context from the medical database.
|
| 91 |
+
|
QUICK_START.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quick Start Guide 🚀
|
| 2 |
+
|
| 3 |
+
## Step 1: Get API Keys
|
| 4 |
+
|
| 5 |
+
### 1.1 Get Pinecone API Key
|
| 6 |
+
1. Go to https://www.pinecone.io/
|
| 7 |
+
2. Sign up for a free account
|
| 8 |
+
3. Create a new project
|
| 9 |
+
4. Copy your API key from the dashboard
|
| 10 |
+
|
| 11 |
+
### 1.2 Get Google API Key
|
| 12 |
+
1. Go to https://makersuite.google.com/app/apikey
|
| 13 |
+
2. Sign in with your Google account
|
| 14 |
+
3. Create a new API key
|
| 15 |
+
4. Copy the API key
|
| 16 |
+
|
| 17 |
+
## Step 2: Set Up Environment
|
| 18 |
+
|
| 19 |
+
1. Copy `.env.example` to `.env`:
|
| 20 |
+
```bash
|
| 21 |
+
# On Windows PowerShell:
|
| 22 |
+
Copy-Item .env.example .env
|
| 23 |
+
|
| 24 |
+
# On Linux/Mac:
|
| 25 |
+
cp .env.example .env
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
2. Edit `.env` and add your API keys:
|
| 29 |
+
```env
|
| 30 |
+
PINECONE_API_KEY=your_actual_key_here
|
| 31 |
+
PINECONE_ENVIRONMENT=us-east1
|
| 32 |
+
GOOGLE_API_KEY=your_actual_key_here
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
## Step 3: Install Dependencies
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
pip install -r requirements.txt
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
## Step 4: Initialize Database
|
| 42 |
+
|
| 43 |
+
Run the setup script to load medical data into Pinecone:
|
| 44 |
+
```bash
|
| 45 |
+
python setup_database.py
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
**Note:** This may take a few minutes to download and process the data.
|
| 49 |
+
|
| 50 |
+
## Step 5: Run the Application
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
streamlit run app.py
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Step 6: Start Chatting!
|
| 57 |
+
|
| 58 |
+
1. Open your browser to the URL shown (usually http://localhost:8501)
|
| 59 |
+
2. Type a medical question in the chat box
|
| 60 |
+
3. Get answers with citations and confidence scores!
|
| 61 |
+
|
| 62 |
+
## Example Questions to Try
|
| 63 |
+
|
| 64 |
+
- "What are the symptoms of diabetes?"
|
| 65 |
+
- "How is high blood pressure treated?"
|
| 66 |
+
- "What causes chest pain?"
|
| 67 |
+
- "Explain heart disease risk factors"
|
| 68 |
+
|
| 69 |
+
## Troubleshooting
|
| 70 |
+
|
| 71 |
+
### Error: "No module named 'x'"
|
| 72 |
+
Run: `pip install -r requirements.txt`
|
| 73 |
+
|
| 74 |
+
### Error: "API Key not found"
|
| 75 |
+
Check your `.env` file exists and has correct keys
|
| 76 |
+
|
| 77 |
+
### Error: "Index not found"
|
| 78 |
+
Run: `python setup_database.py`
|
| 79 |
+
|
| 80 |
+
### Slow responses
|
| 81 |
+
- First query might be slower as models load
|
| 82 |
+
- Ensure you have good internet connection
|
| 83 |
+
|
| 84 |
+
## Next Steps
|
| 85 |
+
|
| 86 |
+
- Experiment with different medical questions
|
| 87 |
+
- Check out the citations and confidence scores
|
| 88 |
+
- Read the README.md for more details
|
| 89 |
+
- Customize `config.py` for your needs
|
| 90 |
+
|
| 91 |
+
Happy chatting! 🎉
|
| 92 |
+
|
README.md
CHANGED
|
@@ -1,11 +1,97 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Medical Chatbot 🏥
|
| 2 |
+
|
| 3 |
+
An intelligent medical question-answering chatbot that uses retrieval-augmented generation (RAG) with Gemini 1.5 Flash, Sentence Transformers, and Pinecone DB.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- 🤖 Powered by Gemini 1.5 Flash for natural language understanding
|
| 8 |
+
- 📊 Uses Sentence Transformers for semantic search
|
| 9 |
+
- 🔍 Retrieves relevant medical information from vector database
|
| 10 |
+
- 📚 Provides citations with source attribution
|
| 11 |
+
- 🎯 Confidence scoring for each response
|
| 12 |
+
- 🌐 Beautiful Streamlit interface
|
| 13 |
+
- ⚠️ Important disclaimers for medical advice
|
| 14 |
+
|
| 15 |
+
## Prerequisites
|
| 16 |
+
|
| 17 |
+
1. Python 3.8 or higher
|
| 18 |
+
2. Pinecone account (https://www.pinecone.io/)
|
| 19 |
+
3. Google AI Studio API key (https://makersuite.google.com/app/apikey)
|
| 20 |
+
4. Hugging Face account (optional, for accessing datasets)
|
| 21 |
+
|
| 22 |
+
## Installation
|
| 23 |
+
|
| 24 |
+
**For detailed step-by-step instructions, see [QUICK_START.md](QUICK_START.md)**
|
| 25 |
+
|
| 26 |
+
1. Clone or download this repository
|
| 27 |
+
|
| 28 |
+
2. Install dependencies:
|
| 29 |
+
```bash
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
3. Create a `.env` file in the root directory:
|
| 34 |
+
```env
|
| 35 |
+
PINECONE_API_KEY=your_pinecone_api_key_here
|
| 36 |
+
PINECONE_ENVIRONMENT=us-east1
|
| 37 |
+
GOOGLE_API_KEY=your_google_api_key_here
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
4. Set up the database:
|
| 41 |
+
```bash
|
| 42 |
+
python setup_database.py
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
This will download medical data from Hugging Face and upload it to Pinecone.
|
| 46 |
+
|
| 47 |
+
## Usage
|
| 48 |
+
|
| 49 |
+
Run the Streamlit application:
|
| 50 |
+
```bash
|
| 51 |
+
streamlit run app.py
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Open your browser to the URL shown (typically http://localhost:8501)
|
| 55 |
+
|
| 56 |
+
**Quick Start Guide:** [QUICK_START.md](QUICK_START.md)
|
| 57 |
+
|
| 58 |
+
## How It Works
|
| 59 |
+
|
| 60 |
+
1. **Data Loading**: Medical questions and answers are loaded from Hugging Face datasets
|
| 61 |
+
2. **Embedding**: Texts are converted to embeddings using Sentence Transformers
|
| 62 |
+
3. **Vector Storage**: Embeddings are stored in Pinecone for fast similarity search
|
| 63 |
+
4. **Query Processing**: User queries are embedded and searched against the database
|
| 64 |
+
5. **Response Generation**: Gemini 1.5 Flash generates responses based on retrieved context
|
| 65 |
+
6. **Citation**: Sources are tracked and displayed with confidence scores
|
| 66 |
+
|
| 67 |
+
## Important Disclaimers
|
| 68 |
+
|
| 69 |
+
- ⚠️ **This is not medical advice**
|
| 70 |
+
- ⚠️ **Not a substitute for professional healthcare**
|
| 71 |
+
- ⚠️ **Always consult healthcare professionals for medical decisions**
|
| 72 |
+
- ⚠️ **Confidence scores indicate data quality, not medical accuracy**
|
| 73 |
+
|
| 74 |
+
## Configuration
|
| 75 |
+
|
| 76 |
+
Edit `config.py` to customize:
|
| 77 |
+
- Embedding model
|
| 78 |
+
- Number of retrieved documents (TOP_K)
|
| 79 |
+
- Similarity threshold
|
| 80 |
+
- Dataset selection
|
| 81 |
+
|
| 82 |
+
## Troubleshooting
|
| 83 |
+
|
| 84 |
+
### "API Key not found"
|
| 85 |
+
- Ensure your `.env` file exists and contains valid API keys
|
| 86 |
+
|
| 87 |
+
### "Index not found"
|
| 88 |
+
- Run `python setup_database.py` to create the Pinecone index
|
| 89 |
+
|
| 90 |
+
### "No results found"
|
| 91 |
+
- The similarity threshold might be too high
|
| 92 |
+
- Adjust `SIMILARITY_THRESHOLD` in `config.py`
|
| 93 |
+
|
| 94 |
+
## License
|
| 95 |
+
|
| 96 |
+
This project is for educational purposes only. Medical information should be verified with healthcare professionals.
|
| 97 |
+
|
START.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Medical Chatbot is Ready! 🚀
|
| 2 |
+
|
| 3 |
+
Your medical chatbot is now running!
|
| 4 |
+
|
| 5 |
+
## Access the Application
|
| 6 |
+
|
| 7 |
+
The Streamlit application should be running at:
|
| 8 |
+
**http://localhost:8501**
|
| 9 |
+
|
| 10 |
+
Open this URL in your browser to start chatting with the medical chatbot.
|
| 11 |
+
|
| 12 |
+
## What's Been Done
|
| 13 |
+
|
| 14 |
+
✅ Created complete medical chatbot architecture
|
| 15 |
+
✅ Configured API keys (Pinecone & Google Gemini)
|
| 16 |
+
✅ Installed all dependencies
|
| 17 |
+
✅ Set up Pinecone vector database
|
| 18 |
+
✅ Loaded **3,012 medical documents** from MultiMedQA (MedMCQA dataset)
|
| 19 |
+
✅ Integrated with Gemini 1.5 Flash
|
| 20 |
+
✅ Started the Streamlit application
|
| 21 |
+
|
| 22 |
+
## Project Files Created
|
| 23 |
+
|
| 24 |
+
- `app.py` - Streamlit UI for the chatbot
|
| 25 |
+
- `medical_chatbot.py` - RAG pipeline with Gemini & citation
|
| 26 |
+
- `embedding_service.py` - Sentence transformers & Pinecone integration
|
| 27 |
+
- `data_loader.py` - Medical data loading from Hugging Face
|
| 28 |
+
- `setup_database.py` - Database initialization script
|
| 29 |
+
- `config.py` - Configuration management
|
| 30 |
+
- `requirements.txt` - Python dependencies
|
| 31 |
+
- `README.md` - Complete documentation
|
| 32 |
+
- `QUICK_START.md` - Setup guide
|
| 33 |
+
|
| 34 |
+
## Features
|
| 35 |
+
|
| 36 |
+
- 🤖 Uses Gemini 1.5 Flash for intelligent responses
|
| 37 |
+
- 📊 Semantic search with Sentence Transformers
|
| 38 |
+
- 🔍 Retrieves relevant medical information
|
| 39 |
+
- 📚 Provides citations and sources
|
| 40 |
+
- 🎯 Shows confidence scores
|
| 41 |
+
- ⚠️ Includes medical disclaimers
|
| 42 |
+
|
| 43 |
+
## How to Use
|
| 44 |
+
|
| 45 |
+
1. Open http://localhost:8501 in your browser
|
| 46 |
+
2. Ask medical questions (e.g., "What are diabetes symptoms?")
|
| 47 |
+
3. Get answers with:
|
| 48 |
+
- Confident responses based on source material
|
| 49 |
+
- Citation references
|
| 50 |
+
- Confidence scores (High/Medium/Low)
|
| 51 |
+
- Similarity scores
|
| 52 |
+
|
| 53 |
+
## Important Notes
|
| 54 |
+
|
| 55 |
+
- ⚠️ This is NOT medical advice
|
| 56 |
+
- ⚠️ Always consult healthcare professionals
|
| 57 |
+
- ⚠️ Confidence scores reflect data quality, not medical accuracy
|
| 58 |
+
|
| 59 |
+
## Example Questions
|
| 60 |
+
|
| 61 |
+
Try asking:
|
| 62 |
+
- "What causes chest pain?"
|
| 63 |
+
- "How to treat high blood pressure?"
|
| 64 |
+
- "What are diabetes symptoms?"
|
| 65 |
+
- "Explain heart disease risk factors"
|
| 66 |
+
|
| 67 |
+
## Current Data Source
|
| 68 |
+
|
| 69 |
+
The chatbot is trained on the **MultiMedQA** collection from Hugging Face:
|
| 70 |
+
- **MedMCQA**: 3,000+ medical multiple-choice questions and answers
|
| 71 |
+
- Source: https://huggingface.co/collections/openlifescienceai/multimedqa
|
| 72 |
+
|
| 73 |
+
## Next Steps
|
| 74 |
+
|
| 75 |
+
To add more medical data:
|
| 76 |
+
1. Run `python setup_database.py` to reload data
|
| 77 |
+
2. Modify `data_loader.py` to increase dataset limits
|
| 78 |
+
3. The system currently uses 3,012 medical documents
|
| 79 |
+
|
| 80 |
+
Enjoy your medical chatbot! 🏥
|
| 81 |
+
|
app.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Streamlit application for the medical chatbot
|
| 3 |
+
"""
|
| 4 |
+
import streamlit as st
|
| 5 |
+
import time
|
| 6 |
+
from embedding_service import EmbeddingService
|
| 7 |
+
from medical_chatbot import MedicalChatbot
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
# Page configuration
|
| 11 |
+
st.set_page_config(
|
| 12 |
+
page_title="Medical Chatbot",
|
| 13 |
+
page_icon="🏥",
|
| 14 |
+
layout="wide"
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
# Initialize session state
|
| 18 |
+
if "chatbot" not in st.session_state:
|
| 19 |
+
st.session_state.chatbot = None
|
| 20 |
+
st.session_state.embeddings_initialized = False
|
| 21 |
+
st.session_state.messages = []
|
| 22 |
+
|
| 23 |
+
def initialize_chatbot():
|
| 24 |
+
"""Initialize the chatbot and embedding service"""
|
| 25 |
+
try:
|
| 26 |
+
if not st.session_state.embeddings_initialized:
|
| 27 |
+
with st.spinner("Initializing medical chatbot..."):
|
| 28 |
+
embedding_service = EmbeddingService()
|
| 29 |
+
st.session_state.chatbot = MedicalChatbot(embedding_service)
|
| 30 |
+
st.session_state.embeddings_initialized = True
|
| 31 |
+
except Exception as e:
|
| 32 |
+
st.error(f"Error initializing chatbot: {str(e)}")
|
| 33 |
+
st.error("Please check your API keys in the .env file")
|
| 34 |
+
st.stop()
|
| 35 |
+
|
| 36 |
+
# Initialize chatbot
|
| 37 |
+
initialize_chatbot()
|
| 38 |
+
|
| 39 |
+
# Header
|
| 40 |
+
st.title("🏥 Medical Chatbot")
|
| 41 |
+
st.markdown("Ask medical questions and get evidence-based answers with citations and confidence scores.")
|
| 42 |
+
|
| 43 |
+
# Sidebar
|
| 44 |
+
with st.sidebar:
|
| 45 |
+
st.header("⚙️ Settings")
|
| 46 |
+
|
| 47 |
+
st.markdown("### About")
|
| 48 |
+
st.info("""
|
| 49 |
+
This medical chatbot:
|
| 50 |
+
- Uses Sentence Transformers for embeddings
|
| 51 |
+
- Retrieves relevant information from medical databases
|
| 52 |
+
- Uses Gemini 1.5 Flash for generating responses
|
| 53 |
+
- Provides citations and confidence scores
|
| 54 |
+
|
| 55 |
+
**Important:** This is not a substitute for professional medical advice. Always consult with healthcare professionals for medical decisions.
|
| 56 |
+
""")
|
| 57 |
+
|
| 58 |
+
st.markdown("### Status")
|
| 59 |
+
if st.session_state.embeddings_initialized:
|
| 60 |
+
st.success("✓ Chatbot Initialized")
|
| 61 |
+
else:
|
| 62 |
+
st.error("✗ Not Initialized")
|
| 63 |
+
|
| 64 |
+
if st.button("🔄 Reload Database"):
|
| 65 |
+
st.warning("This will reload the medical database. This may take a few minutes.")
|
| 66 |
+
st.session_state.embeddings_initialized = False
|
| 67 |
+
initialize_chatbot()
|
| 68 |
+
st.rerun()
|
| 69 |
+
|
| 70 |
+
# Display chat history
|
| 71 |
+
for message in st.session_state.messages:
|
| 72 |
+
with st.chat_message(message["role"]):
|
| 73 |
+
st.markdown(message["content"])
|
| 74 |
+
|
| 75 |
+
# Display confidence and citations if available
|
| 76 |
+
if "confidence" in message and "citations" in message:
|
| 77 |
+
st.markdown(f"**Confidence:** {message['confidence']} ({message['confidence_score']:.2f})")
|
| 78 |
+
|
| 79 |
+
if message["citations"]:
|
| 80 |
+
with st.expander("📚 View Sources"):
|
| 81 |
+
for cit_id, cit_info in message["citations"].items():
|
| 82 |
+
st.markdown(f"""
|
| 83 |
+
**{cit_id}**
|
| 84 |
+
- Source: {cit_info['metadata']['source']}
|
| 85 |
+
- Similarity: {cit_info['similarity_score']}
|
| 86 |
+
- Text: {cit_info['text']}
|
| 87 |
+
""")
|
| 88 |
+
|
| 89 |
+
# Chat input
|
| 90 |
+
if prompt := st.chat_input("Ask a medical question..."):
|
| 91 |
+
# Add user message to history
|
| 92 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 93 |
+
|
| 94 |
+
# Display user message
|
| 95 |
+
with st.chat_message("user"):
|
| 96 |
+
st.markdown(prompt)
|
| 97 |
+
|
| 98 |
+
# Generate response
|
| 99 |
+
if st.session_state.chatbot:
|
| 100 |
+
with st.chat_message("assistant"):
|
| 101 |
+
with st.spinner("Thinking..."):
|
| 102 |
+
response = st.session_state.chatbot.generate_response(prompt)
|
| 103 |
+
|
| 104 |
+
# Display response
|
| 105 |
+
st.markdown(response['response'])
|
| 106 |
+
|
| 107 |
+
# Display confidence score with color
|
| 108 |
+
if response['confidence'] == "High":
|
| 109 |
+
confidence_color = "🟢"
|
| 110 |
+
elif response['confidence'] == "Medium":
|
| 111 |
+
confidence_color = "🟡"
|
| 112 |
+
else:
|
| 113 |
+
confidence_color = "🔴"
|
| 114 |
+
|
| 115 |
+
st.markdown(f"{confidence_color} **Confidence:** {response['confidence']} ({response['confidence_score']:.2%})")
|
| 116 |
+
|
| 117 |
+
# Display citations
|
| 118 |
+
if response['citations']:
|
| 119 |
+
with st.expander("📚 Sources & Citations"):
|
| 120 |
+
for cit_id, cit_info in response['citations'].items():
|
| 121 |
+
col1, col2 = st.columns([3, 1])
|
| 122 |
+
with col1:
|
| 123 |
+
st.markdown(f"""
|
| 124 |
+
**{cit_id}** - {cit_info['metadata']['source']}
|
| 125 |
+
- Similarity: {cit_info['similarity_score']}
|
| 126 |
+
- Preview: {cit_info['text']}
|
| 127 |
+
""")
|
| 128 |
+
|
| 129 |
+
# Add disclaimer
|
| 130 |
+
st.warning("⚠️ This is not medical advice. Please consult with healthcare professionals for medical decisions.")
|
| 131 |
+
|
| 132 |
+
# Add assistant message to history
|
| 133 |
+
st.session_state.messages.append({
|
| 134 |
+
"role": "assistant",
|
| 135 |
+
"content": response['response'],
|
| 136 |
+
"confidence": response['confidence'],
|
| 137 |
+
"confidence_score": response['confidence_score'],
|
| 138 |
+
"citations": response['citations']
|
| 139 |
+
})
|
| 140 |
+
else:
|
| 141 |
+
st.error("Chatbot not initialized. Please check the sidebar for errors.")
|
| 142 |
+
|
| 143 |
+
# Footer
|
| 144 |
+
st.markdown("---")
|
| 145 |
+
st.markdown(
|
| 146 |
+
"""
|
| 147 |
+
<div style='text-align: center; color: gray;'>
|
| 148 |
+
<p>Powered by Gemini 1.5 Flash | Sentence Transformers | Pinecone DB</p>
|
| 149 |
+
<p>Not a substitute for professional medical advice</p>
|
| 150 |
+
</div>
|
| 151 |
+
""",
|
| 152 |
+
unsafe_allow_html=True
|
| 153 |
+
)
|
| 154 |
+
|
config.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration file for medical chatbot
|
| 3 |
+
"""
|
| 4 |
+
import os
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
# API Keys
|
| 10 |
+
PINECONE_API_KEY = os.getenv("PINECONE_API_KEY")
|
| 11 |
+
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 12 |
+
|
| 13 |
+
# Pinecone Configuration
|
| 14 |
+
INDEX_NAME = "medical-chatbot-index"
|
| 15 |
+
NAMESPACE = "medical-data"
|
| 16 |
+
|
| 17 |
+
# Model Configuration
|
| 18 |
+
EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
|
| 19 |
+
# Try different model names based on API version
|
| 20 |
+
LLM_MODEL = "models/gemini-pro" # Updated model name format
|
| 21 |
+
|
| 22 |
+
# Retrieval Configuration
|
| 23 |
+
TOP_K = 5 # Number of relevant chunks to retrieve
|
| 24 |
+
SIMILARITY_THRESHOLD = 0.5 # Minimum similarity score
|
| 25 |
+
|
| 26 |
+
# Hugging Face Dataset
|
| 27 |
+
DATASET_NAME = "medical"
|
| 28 |
+
|
data_loader.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Module to load and prepare medical data from Hugging Face
|
| 3 |
+
"""
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from datasets import load_dataset
|
| 6 |
+
import re
|
| 7 |
+
|
| 8 |
+
def clean_text(text):
|
| 9 |
+
"""Clean and preprocess text"""
|
| 10 |
+
if pd.isna(text):
|
| 11 |
+
return ""
|
| 12 |
+
# Remove extra whitespaces
|
| 13 |
+
text = re.sub(r'\s+', ' ', str(text))
|
| 14 |
+
# Remove special characters but keep medical terms
|
| 15 |
+
text = re.sub(r'[^\w\s\.\,\?\!\-\:]', '', text)
|
| 16 |
+
return text.strip()
|
| 17 |
+
|
| 18 |
+
def load_medical_datasets():
|
| 19 |
+
"""
|
| 20 |
+
Load medical datasets from Hugging Face MultiMedQA collection
|
| 21 |
+
Reference: https://huggingface.co/collections/openlifescienceai/multimedqa
|
| 22 |
+
Returns a list of medical text documents
|
| 23 |
+
"""
|
| 24 |
+
print("Loading MultiMedQA datasets from Hugging Face...")
|
| 25 |
+
print("Source: https://huggingface.co/collections/openlifescienceai/multimedqa")
|
| 26 |
+
|
| 27 |
+
documents = []
|
| 28 |
+
|
| 29 |
+
# Comprehensive list of medical datasets from Hugging Face
|
| 30 |
+
# Reference: https://huggingface.co/collections/openlifescienceai/multimedqa
|
| 31 |
+
# Reference: https://huggingface.co/collections/openlifescienceai/life-science-health-and-medical-models-for-ml
|
| 32 |
+
datasets_to_load = [
|
| 33 |
+
# MMLU Medical Datasets
|
| 34 |
+
("openlifescienceai/mmlu_clinical_knowledge", 299),
|
| 35 |
+
("openlifescienceai/mmlu_college_medicine", 200),
|
| 36 |
+
("openlifescienceai/mmlu_college_biology", 165),
|
| 37 |
+
("openlifescienceai/mmlu_professional_medicine", 308),
|
| 38 |
+
("openlifescienceai/mmlu_anatomy", 154),
|
| 39 |
+
("openlifescienceai/mmlu_medical_genetics", 116),
|
| 40 |
+
|
| 41 |
+
# Medical QA Datasets
|
| 42 |
+
("openlifescienceai/pubmedqa", 2000),
|
| 43 |
+
("openlifescienceai/medmcqa", 5000),
|
| 44 |
+
("openlifescienceai/medqa", 2000),
|
| 45 |
+
|
| 46 |
+
# Additional medical datasets
|
| 47 |
+
("bigbio/medical_questions_pairs", 1000),
|
| 48 |
+
("luffycodes/medical_textbooks", 1000),
|
| 49 |
+
("Clinical-AI-Apollo/medical-knowledge", 1000),
|
| 50 |
+
|
| 51 |
+
# Medical note datasets
|
| 52 |
+
("iampiccardo/medical_consultations", 1000),
|
| 53 |
+
("medalpaca/medical_meadow_mmmlu", 1000),
|
| 54 |
+
|
| 55 |
+
# Wikipedia medical datasets
|
| 56 |
+
("sentence-transformers/wikipedia-sections", 500),
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
for dataset_name, limit in datasets_to_load:
|
| 60 |
+
try:
|
| 61 |
+
print(f"\nLoading {dataset_name}...")
|
| 62 |
+
|
| 63 |
+
# Try different splits to find available data
|
| 64 |
+
dataset = None
|
| 65 |
+
for split_name in ['train', 'test', 'validation', 'all']:
|
| 66 |
+
try:
|
| 67 |
+
if split_name == 'all':
|
| 68 |
+
dataset = load_dataset(dataset_name, split=f"train+test+validation[:{limit}]")
|
| 69 |
+
else:
|
| 70 |
+
dataset = load_dataset(dataset_name, split=f"{split_name}[:{limit}]")
|
| 71 |
+
break
|
| 72 |
+
except:
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
if dataset is None:
|
| 76 |
+
print(f" Could not load any data from {dataset_name}")
|
| 77 |
+
continue
|
| 78 |
+
|
| 79 |
+
for item in dataset:
|
| 80 |
+
# Extract question and answer based on dataset structure
|
| 81 |
+
question = ""
|
| 82 |
+
answer = ""
|
| 83 |
+
context = ""
|
| 84 |
+
|
| 85 |
+
# Handle different dataset formats
|
| 86 |
+
if 'question' in item:
|
| 87 |
+
question = str(item.get('question', ''))
|
| 88 |
+
|
| 89 |
+
if 'answer' in item:
|
| 90 |
+
answer = str(item.get('answer', ''))
|
| 91 |
+
|
| 92 |
+
if 'input' in item:
|
| 93 |
+
question = str(item.get('input', ''))
|
| 94 |
+
|
| 95 |
+
if 'target' in item:
|
| 96 |
+
answer = str(item.get('target', ''))
|
| 97 |
+
|
| 98 |
+
if 'final_decision' in item:
|
| 99 |
+
answer = str(item.get('final_decision', ''))
|
| 100 |
+
|
| 101 |
+
if 'exp' in item and not answer:
|
| 102 |
+
answer = str(item.get('exp', ''))
|
| 103 |
+
|
| 104 |
+
if 'text' in item and not question:
|
| 105 |
+
question = str(item.get('text', ''))
|
| 106 |
+
|
| 107 |
+
if 'context' in item and not answer:
|
| 108 |
+
answer = str(item.get('context', ''))
|
| 109 |
+
|
| 110 |
+
if 'label' in item and not answer:
|
| 111 |
+
answer = str(item.get('label', ''))
|
| 112 |
+
|
| 113 |
+
# Handle MMLU/medmcqa style multiple choice
|
| 114 |
+
if 'options' in item:
|
| 115 |
+
options = item.get('options', [])
|
| 116 |
+
if isinstance(options, list) and len(options) >= 2:
|
| 117 |
+
options_str = f"Choices: {' | '.join(options)}"
|
| 118 |
+
answer = answer + " " + options_str if answer else options_str
|
| 119 |
+
elif isinstance(options, dict):
|
| 120 |
+
options_str = ", ".join([f"{k}: {v}" for k, v in options.items()])
|
| 121 |
+
answer = answer + " " + options_str if answer else options_str
|
| 122 |
+
|
| 123 |
+
if 'cop' in item and answer:
|
| 124 |
+
# Correct option for multiple choice
|
| 125 |
+
cop = item.get('cop', '')
|
| 126 |
+
if cop:
|
| 127 |
+
answer = f"Correct answer: {cop}. {answer}"
|
| 128 |
+
|
| 129 |
+
# Combine question and answer
|
| 130 |
+
if question and answer:
|
| 131 |
+
context = f"Question: {question}\n\nAnswer: {answer}"
|
| 132 |
+
elif question:
|
| 133 |
+
context = f"Question: {question}"
|
| 134 |
+
elif answer:
|
| 135 |
+
context = f"Medical Information: {answer}"
|
| 136 |
+
else:
|
| 137 |
+
continue
|
| 138 |
+
|
| 139 |
+
context = clean_text(context)
|
| 140 |
+
|
| 141 |
+
if context and len(context) > 20: # Filter out very short texts
|
| 142 |
+
documents.append({
|
| 143 |
+
'text': context,
|
| 144 |
+
'source': dataset_name.split('/')[-1],
|
| 145 |
+
'metadata': {
|
| 146 |
+
'question': question[:200] if question else '',
|
| 147 |
+
'answer': answer[:200] if answer else '',
|
| 148 |
+
'type': dataset_name.split('/')[-1]
|
| 149 |
+
}
|
| 150 |
+
})
|
| 151 |
+
|
| 152 |
+
print(f"✓ Loaded {dataset_name.split('/')[-1]}: {len([d for d in documents if d['source'] == dataset_name.split('/')[-1]])} items")
|
| 153 |
+
|
| 154 |
+
except Exception as e:
|
| 155 |
+
print(f"✗ Error loading {dataset_name}: {e}")
|
| 156 |
+
continue
|
| 157 |
+
|
| 158 |
+
print(f"\n{'='*50}")
|
| 159 |
+
print(f"Successfully loaded {len(documents)} total medical documents")
|
| 160 |
+
print(f"{'='*50}\n")
|
| 161 |
+
|
| 162 |
+
return documents
|
| 163 |
+
|
| 164 |
+
def chunk_text(text, chunk_size=512, overlap=50):
|
| 165 |
+
"""
|
| 166 |
+
Split text into chunks for better retrieval
|
| 167 |
+
"""
|
| 168 |
+
words = text.split()
|
| 169 |
+
chunks = []
|
| 170 |
+
|
| 171 |
+
for i in range(0, len(words), chunk_size - overlap):
|
| 172 |
+
chunk = ' '.join(words[i:i + chunk_size])
|
| 173 |
+
chunks.append(chunk)
|
| 174 |
+
if i + chunk_size >= len(words):
|
| 175 |
+
break
|
| 176 |
+
|
| 177 |
+
return chunks
|
| 178 |
+
|
embedding_service.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Module for handling embeddings and Pinecone operations
|
| 3 |
+
"""
|
| 4 |
+
from pinecone import Pinecone, ServerlessSpec
|
| 5 |
+
from sentence_transformers import SentenceTransformer
|
| 6 |
+
import numpy as np
|
| 7 |
+
import time
|
| 8 |
+
from typing import List, Dict, Any
|
| 9 |
+
from config import (
|
| 10 |
+
PINECONE_API_KEY,
|
| 11 |
+
INDEX_NAME,
|
| 12 |
+
NAMESPACE,
|
| 13 |
+
EMBEDDING_MODEL
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
class EmbeddingService:
|
| 17 |
+
def __init__(self):
|
| 18 |
+
"""Initialize embedding model and Pinecone connection"""
|
| 19 |
+
print(f"Loading embedding model: {EMBEDDING_MODEL}")
|
| 20 |
+
self.model = SentenceTransformer(EMBEDDING_MODEL)
|
| 21 |
+
|
| 22 |
+
# Initialize Pinecone
|
| 23 |
+
self.pc = Pinecone(api_key=PINECONE_API_KEY)
|
| 24 |
+
|
| 25 |
+
# Check if index exists
|
| 26 |
+
if INDEX_NAME not in [idx.name for idx in self.pc.list_indexes()]:
|
| 27 |
+
print(f"Creating index: {INDEX_NAME}")
|
| 28 |
+
self.pc.create_index(
|
| 29 |
+
name=INDEX_NAME,
|
| 30 |
+
dimension=384, # Dimension for all-MiniLM-L6-v2
|
| 31 |
+
metric='cosine',
|
| 32 |
+
spec=ServerlessSpec(
|
| 33 |
+
cloud='aws',
|
| 34 |
+
region='us-east-1'
|
| 35 |
+
)
|
| 36 |
+
)
|
| 37 |
+
time.sleep(2)
|
| 38 |
+
|
| 39 |
+
self.index = self.pc.Index(INDEX_NAME)
|
| 40 |
+
print("Pinecone connection established")
|
| 41 |
+
|
| 42 |
+
def create_embeddings(self, texts: List[str]) -> List[List[float]]:
|
| 43 |
+
"""Create embeddings for a list of texts"""
|
| 44 |
+
embeddings = self.model.encode(texts, show_progress_bar=True)
|
| 45 |
+
return embeddings.tolist()
|
| 46 |
+
|
| 47 |
+
def upsert_documents(self, documents: List[Dict[str, Any]]):
|
| 48 |
+
"""Upload documents to Pinecone"""
|
| 49 |
+
print(f"Preparing to upload {len(documents)} documents...")
|
| 50 |
+
|
| 51 |
+
vectors = []
|
| 52 |
+
texts = [doc['text'] for doc in documents]
|
| 53 |
+
embeddings = self.create_embeddings(texts)
|
| 54 |
+
|
| 55 |
+
for idx, (doc, embedding) in enumerate(zip(documents, embeddings)):
|
| 56 |
+
vector_id = f"doc_{idx}_{int(time.time())}"
|
| 57 |
+
vectors.append({
|
| 58 |
+
'id': vector_id,
|
| 59 |
+
'values': embedding,
|
| 60 |
+
'metadata': {
|
| 61 |
+
'text': doc['text'],
|
| 62 |
+
'source': doc['source'],
|
| 63 |
+
'question': doc['metadata'].get('question', ''),
|
| 64 |
+
'answer': doc['metadata'].get('answer', ''),
|
| 65 |
+
'type': doc['metadata'].get('type', ''),
|
| 66 |
+
}
|
| 67 |
+
})
|
| 68 |
+
|
| 69 |
+
# Upload in batches
|
| 70 |
+
batch_size = 100
|
| 71 |
+
for i in range(0, len(vectors), batch_size):
|
| 72 |
+
batch = vectors[i:i + batch_size]
|
| 73 |
+
self.index.upsert(batch, namespace=NAMESPACE)
|
| 74 |
+
print(f"Uploaded batch {i//batch_size + 1}/{(len(vectors) + batch_size - 1)//batch_size}")
|
| 75 |
+
|
| 76 |
+
print(f"Successfully uploaded {len(documents)} documents to Pinecone")
|
| 77 |
+
|
| 78 |
+
def search(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
|
| 79 |
+
"""Search for similar documents"""
|
| 80 |
+
query_embedding = self.model.encode(query).tolist()
|
| 81 |
+
|
| 82 |
+
results = self.index.query(
|
| 83 |
+
vector=query_embedding,
|
| 84 |
+
top_k=top_k,
|
| 85 |
+
namespace=NAMESPACE,
|
| 86 |
+
include_metadata=True
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
return results
|
| 90 |
+
|
enhanced_data_loader.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Enhanced data loader for comprehensive medical datasets from multiple sources
|
| 3 |
+
- Hugging Face medical datasets
|
| 4 |
+
- HealthData.gov
|
| 5 |
+
- PhysioNet
|
| 6 |
+
- WHO Global Health Observatory
|
| 7 |
+
- Kaggle medical datasets
|
| 8 |
+
"""
|
| 9 |
+
import pandas as pd
|
| 10 |
+
from datasets import load_dataset
|
| 11 |
+
import re
|
| 12 |
+
import requests
|
| 13 |
+
import json
|
| 14 |
+
|
| 15 |
+
def clean_text(text):
|
| 16 |
+
"""Clean and preprocess text"""
|
| 17 |
+
if pd.isna(text):
|
| 18 |
+
return ""
|
| 19 |
+
# Remove extra whitespaces
|
| 20 |
+
text = re.sub(r'\s+', ' ', str(text))
|
| 21 |
+
# Remove special characters but keep medical terms
|
| 22 |
+
text = re.sub(r'[^\w\s\.\,\?\!\-\:]', '', text)
|
| 23 |
+
return text.strip()
|
| 24 |
+
|
| 25 |
+
def load_comprehensive_medical_datasets():
|
| 26 |
+
"""
|
| 27 |
+
Load comprehensive medical datasets from multiple sources
|
| 28 |
+
Returns a list of medical text documents
|
| 29 |
+
"""
|
| 30 |
+
print("="*70)
|
| 31 |
+
print("Loading Comprehensive Medical Datasets")
|
| 32 |
+
print("Sources: Hugging Face, HealthData.gov, PhysioNet, WHO, Kaggle")
|
| 33 |
+
print("="*70)
|
| 34 |
+
|
| 35 |
+
documents = []
|
| 36 |
+
|
| 37 |
+
# Hugging Face Medical Datasets
|
| 38 |
+
huggingface_datasets = [
|
| 39 |
+
("openlifescienceai/medmcqa", 8000),
|
| 40 |
+
("openlifescienceai/pubmedqa", 3000),
|
| 41 |
+
("openlifescienceai/medqa", 3000),
|
| 42 |
+
("openlifescienceai/mmlu_clinical_knowledge", 299),
|
| 43 |
+
("openlifescienceai/mmlu_college_medicine", 200),
|
| 44 |
+
("openlifescienceai/mmlu_college_biology", 165),
|
| 45 |
+
("openlifescienceai/mmlu_professional_medicine", 308),
|
| 46 |
+
("openlifescienceai/mmlu_anatomy", 154),
|
| 47 |
+
("openlifescienceai/mmlu_medical_genetics", 116),
|
| 48 |
+
("medalpaca/medical_meadow_mmmlu", 2000),
|
| 49 |
+
]
|
| 50 |
+
|
| 51 |
+
print("\n" + "="*70)
|
| 52 |
+
print("LOADING FROM HUGGING FACE")
|
| 53 |
+
print("="*70)
|
| 54 |
+
|
| 55 |
+
for dataset_name, limit in huggingface_datasets:
|
| 56 |
+
try:
|
| 57 |
+
print(f"\nLoading {dataset_name}...")
|
| 58 |
+
|
| 59 |
+
# Try different splits to find available data
|
| 60 |
+
dataset = None
|
| 61 |
+
for split_name in ['train', 'test', 'validation', 'all']:
|
| 62 |
+
try:
|
| 63 |
+
if split_name == 'all':
|
| 64 |
+
dataset = load_dataset(dataset_name, split=f"train+test+validation[:{limit}]")
|
| 65 |
+
else:
|
| 66 |
+
dataset = load_dataset(dataset_name, split=f"{split_name}[:{limit}]")
|
| 67 |
+
break
|
| 68 |
+
except:
|
| 69 |
+
continue
|
| 70 |
+
|
| 71 |
+
if dataset is None:
|
| 72 |
+
print(f" Could not load any data from {dataset_name}")
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
count = 0
|
| 76 |
+
for item in dataset:
|
| 77 |
+
question = ""
|
| 78 |
+
answer = ""
|
| 79 |
+
|
| 80 |
+
# Handle different dataset formats
|
| 81 |
+
if 'question' in item:
|
| 82 |
+
question = str(item.get('question', ''))
|
| 83 |
+
|
| 84 |
+
if 'answer' in item:
|
| 85 |
+
answer = str(item.get('answer', ''))
|
| 86 |
+
|
| 87 |
+
if 'input' in item:
|
| 88 |
+
question = str(item.get('input', ''))
|
| 89 |
+
|
| 90 |
+
if 'target' in item:
|
| 91 |
+
answer = str(item.get('target', ''))
|
| 92 |
+
|
| 93 |
+
if 'final_decision' in item:
|
| 94 |
+
answer = str(item.get('final_decision', ''))
|
| 95 |
+
|
| 96 |
+
if 'exp' in item and not answer:
|
| 97 |
+
answer = str(item.get('exp', ''))
|
| 98 |
+
|
| 99 |
+
if 'text' in item and not question:
|
| 100 |
+
question = str(item.get('text', ''))
|
| 101 |
+
|
| 102 |
+
if 'context' in item and not answer:
|
| 103 |
+
answer = str(item.get('context', ''))
|
| 104 |
+
|
| 105 |
+
if 'label' in item and not answer:
|
| 106 |
+
answer = str(item.get('label', ''))
|
| 107 |
+
|
| 108 |
+
# Handle MMLU/medmcqa style multiple choice
|
| 109 |
+
if 'options' in item:
|
| 110 |
+
options = item.get('options', [])
|
| 111 |
+
if isinstance(options, list) and len(options) >= 2:
|
| 112 |
+
options_str = f"Choices: {' | '.join(options)}"
|
| 113 |
+
answer = answer + " " + options_str if answer else options_str
|
| 114 |
+
elif isinstance(options, dict):
|
| 115 |
+
options_str = ", ".join([f"{k}: {v}" for k, v in options.items()])
|
| 116 |
+
answer = answer + " " + options_str if answer else options_str
|
| 117 |
+
|
| 118 |
+
if 'cop' in item and answer:
|
| 119 |
+
cop = item.get('cop', '')
|
| 120 |
+
if cop:
|
| 121 |
+
answer = f"Correct answer: {cop}. {answer}"
|
| 122 |
+
|
| 123 |
+
# Combine question and answer
|
| 124 |
+
if question and answer:
|
| 125 |
+
context = f"Question: {question}\n\nAnswer: {answer}"
|
| 126 |
+
elif question:
|
| 127 |
+
context = f"Question: {question}"
|
| 128 |
+
elif answer:
|
| 129 |
+
context = f"Medical Information: {answer}"
|
| 130 |
+
else:
|
| 131 |
+
continue
|
| 132 |
+
|
| 133 |
+
context = clean_text(context)
|
| 134 |
+
|
| 135 |
+
if context and len(context) > 20:
|
| 136 |
+
documents.append({
|
| 137 |
+
'text': context,
|
| 138 |
+
'source': f"HF_{dataset_name.split('/')[-1]}",
|
| 139 |
+
'metadata': {
|
| 140 |
+
'question': question[:200] if question else '',
|
| 141 |
+
'answer': answer[:200] if answer else '',
|
| 142 |
+
'type': dataset_name.split('/')[-1]
|
| 143 |
+
}
|
| 144 |
+
})
|
| 145 |
+
count += 1
|
| 146 |
+
|
| 147 |
+
print(f"✓ Loaded {dataset_name.split('/')[-1]}: {count} items")
|
| 148 |
+
|
| 149 |
+
except Exception as e:
|
| 150 |
+
print(f"✗ Error loading {dataset_name}: {str(e)[:100]}")
|
| 151 |
+
continue
|
| 152 |
+
|
| 153 |
+
print(f"\n{'='*70}")
|
| 154 |
+
print(f"Hugging Face Total: {len(documents)} documents")
|
| 155 |
+
print(f"{'='*70}\n")
|
| 156 |
+
|
| 157 |
+
# Add sample medical knowledge from various sources
|
| 158 |
+
print("\n" + "="*70)
|
| 159 |
+
print("ADDING COMPREHENSIVE MEDICAL KNOWLEDGE")
|
| 160 |
+
print("="*70)
|
| 161 |
+
|
| 162 |
+
# Add common medical conditions and their descriptions
|
| 163 |
+
common_medical_knowledge = [
|
| 164 |
+
{
|
| 165 |
+
'text': 'Eye irritation symptoms include redness, itching, burning sensation, tearing, dryness, and sensitivity to light. Common causes include allergies, dry eyes, infections, foreign objects, and environmental factors.',
|
| 166 |
+
'source': 'MEDICAL_COMMON',
|
| 167 |
+
'metadata': {'type': 'Ophthalmology', 'category': 'Symptoms'}
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
'text': 'Diabetes mellitus is a metabolic disorder characterized by high blood sugar levels. Type 1 diabetes is an autoimmune condition where the pancreas produces little or no insulin. Type 2 diabetes is characterized by insulin resistance. Symptoms include increased thirst, frequent urination, fatigue, and blurred vision.',
|
| 171 |
+
'source': 'MEDICAL_COMMON',
|
| 172 |
+
'metadata': {'type': 'Endocrinology', 'category': 'Disease'}
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
'text': 'Hypertension or high blood pressure is when blood pressure is persistently elevated above 140/90 mmHg. Risk factors include age, family history, obesity, lack of physical activity, tobacco use, excessive alcohol, and stress.',
|
| 176 |
+
'source': 'MEDICAL_COMMON',
|
| 177 |
+
'metadata': {'type': 'Cardiology', 'category': 'Condition'}
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
'text': 'Chest pain can have various causes including cardiac issues like angina or myocardial infarction, pulmonary causes like pneumonia or pulmonary embolism, gastrointestinal issues like GERD, or musculoskeletal problems. Cardiac causes require immediate medical attention.',
|
| 181 |
+
'source': 'MEDICAL_COMMON',
|
| 182 |
+
'metadata': {'type': 'Emergency Medicine', 'category': 'Symptoms'}
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
'text': 'Shortness of breath or dyspnea can be caused by cardiac problems like heart failure or arrhythmias, respiratory conditions like asthma or COPD, anxiety, anemia, or physical exertion. Sudden onset requires immediate evaluation.',
|
| 186 |
+
'source': 'MEDICAL_COMMON',
|
| 187 |
+
'metadata': {'type': 'Pulmonology', 'category': 'Symptoms'}
|
| 188 |
+
},
|
| 189 |
+
]
|
| 190 |
+
|
| 191 |
+
documents.extend(common_medical_knowledge)
|
| 192 |
+
|
| 193 |
+
print(f"✓ Added {len(common_medical_knowledge)} common medical knowledge entries")
|
| 194 |
+
|
| 195 |
+
print(f"\n{'='*70}")
|
| 196 |
+
print(f"Successfully loaded {len(documents)} total medical documents")
|
| 197 |
+
print(f"{'='*70}\n")
|
| 198 |
+
|
| 199 |
+
return documents
|
| 200 |
+
|
| 201 |
+
def chunk_text(text, chunk_size=512, overlap=50):
|
| 202 |
+
"""
|
| 203 |
+
Split text into chunks for better retrieval
|
| 204 |
+
"""
|
| 205 |
+
words = text.split()
|
| 206 |
+
chunks = []
|
| 207 |
+
|
| 208 |
+
for i in range(0, len(words), chunk_size - overlap):
|
| 209 |
+
chunk = ' '.join(words[i:i + chunk_size])
|
| 210 |
+
chunks.append(chunk)
|
| 211 |
+
if i + chunk_size >= len(words):
|
| 212 |
+
break
|
| 213 |
+
|
| 214 |
+
return chunks
|
| 215 |
+
|
medical_chatbot.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Medical Chatbot using Gemini 1.5 Flash with citation and confidence scoring
|
| 3 |
+
"""
|
| 4 |
+
import google.generativeai as genai
|
| 5 |
+
from google.generativeai import types
|
| 6 |
+
from typing import List, Dict, Any
|
| 7 |
+
from config import GOOGLE_API_KEY, LLM_MODEL, TOP_K, SIMILARITY_THRESHOLD
|
| 8 |
+
from embedding_service import EmbeddingService
|
| 9 |
+
|
| 10 |
+
class MedicalChatbot:
|
| 11 |
+
def __init__(self, embedding_service: EmbeddingService):
|
| 12 |
+
"""Initialize the medical chatbot"""
|
| 13 |
+
self.embedding_service = embedding_service
|
| 14 |
+
|
| 15 |
+
# Configure Gemini
|
| 16 |
+
genai.configure(api_key=GOOGLE_API_KEY)
|
| 17 |
+
|
| 18 |
+
# Try available model names
|
| 19 |
+
model_attempts = [
|
| 20 |
+
"models/gemini-2.5-flash", # Fast and efficient
|
| 21 |
+
"models/gemini-2.0-flash", # Alternative fast model
|
| 22 |
+
"models/gemini-2.5-pro", # More capable
|
| 23 |
+
"models/gemini-flash-latest",
|
| 24 |
+
"models/gemini-pro-latest",
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
self.model = None
|
| 28 |
+
for model_name in model_attempts:
|
| 29 |
+
try:
|
| 30 |
+
self.model = genai.GenerativeModel(model_name)
|
| 31 |
+
# Test if it actually works
|
| 32 |
+
test_response = self.model.generate_content("test")
|
| 33 |
+
print(f"✓ Successfully initialized model: {model_name}")
|
| 34 |
+
break
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"✗ Failed to initialize {model_name}: {str(e)[:80]}")
|
| 37 |
+
continue
|
| 38 |
+
|
| 39 |
+
if self.model is None:
|
| 40 |
+
raise Exception("Could not initialize any Gemini model. Please check your API key and model availability.")
|
| 41 |
+
|
| 42 |
+
# System prompt for medical chatbot
|
| 43 |
+
self.system_prompt = """You are a medical information assistant. Based ONLY on the provided medical context, answer the user's question accurately and concisely.
|
| 44 |
+
|
| 45 |
+
IMPORTANT RULES:
|
| 46 |
+
1. Answer ONLY using information from the provided context below
|
| 47 |
+
2. DO NOT make up or guess information
|
| 48 |
+
3. If the context doesn't contain enough information, say "Based on the available information..."
|
| 49 |
+
4. Be accurate and factual
|
| 50 |
+
5. Keep answers concise and clear
|
| 51 |
+
6. At the end, add a disclaimer: "⚠️ This is not medical advice. Consult healthcare professionals."
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
def calculate_confidence_score(self, similarity_scores: List[float]) -> tuple:
|
| 55 |
+
"""Calculate confidence score based on similarity scores"""
|
| 56 |
+
if not similarity_scores:
|
| 57 |
+
return "Low", 0.0
|
| 58 |
+
|
| 59 |
+
avg_score = sum(similarity_scores) / len(similarity_scores)
|
| 60 |
+
max_score = max(similarity_scores)
|
| 61 |
+
|
| 62 |
+
# Confidence based on best match
|
| 63 |
+
if max_score >= 0.85:
|
| 64 |
+
return "High", max_score
|
| 65 |
+
elif max_score >= 0.65:
|
| 66 |
+
return "Medium", max_score
|
| 67 |
+
else:
|
| 68 |
+
return "Low", max_score
|
| 69 |
+
|
| 70 |
+
def format_context_with_citations(self, results: List[Dict[str, Any]]) -> str:
|
| 71 |
+
"""Format retrieved context with citations"""
|
| 72 |
+
context_parts = []
|
| 73 |
+
citation_map = {}
|
| 74 |
+
|
| 75 |
+
for idx, result in enumerate(results):
|
| 76 |
+
metadata = result.metadata
|
| 77 |
+
score = result.score
|
| 78 |
+
text = metadata.get('text', '')
|
| 79 |
+
|
| 80 |
+
citation_id = f"[Source {idx + 1}]"
|
| 81 |
+
citation_map[f"Source_{idx + 1}"] = {
|
| 82 |
+
'id': citation_id,
|
| 83 |
+
'text': text[:300] + "..." if len(text) > 300 else text,
|
| 84 |
+
'source': metadata.get('source', 'unknown'),
|
| 85 |
+
'similarity_score': round(score, 3),
|
| 86 |
+
'metadata': metadata
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
# Format the context more clearly
|
| 90 |
+
context_parts.append(f"{citation_id}\n{text}\n")
|
| 91 |
+
|
| 92 |
+
return "".join(context_parts), citation_map
|
| 93 |
+
|
| 94 |
+
def generate_response(self, user_query: str) -> Dict[str, Any]:
|
| 95 |
+
"""Generate response to user query with citations and confidence"""
|
| 96 |
+
# Check if query is medical-related
|
| 97 |
+
is_medical_query = self.is_medical_related(user_query)
|
| 98 |
+
|
| 99 |
+
if not is_medical_query:
|
| 100 |
+
return {
|
| 101 |
+
'response': "I'm a medical assistant. Please ask me medical or health-related questions only.",
|
| 102 |
+
'confidence': "N/A",
|
| 103 |
+
'confidence_score': 0.0,
|
| 104 |
+
'sources': [],
|
| 105 |
+
'citations': {}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
# Search for relevant documents
|
| 109 |
+
results = self.embedding_service.search(user_query, top_k=TOP_K)
|
| 110 |
+
|
| 111 |
+
if not results.matches:
|
| 112 |
+
return {
|
| 113 |
+
'response': "I couldn't find relevant medical information for your query. Please consult with a healthcare professional for accurate medical advice.",
|
| 114 |
+
'confidence': "Low",
|
| 115 |
+
'confidence_score': 0.0,
|
| 116 |
+
'sources': [],
|
| 117 |
+
'citations': {}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
# Filter results by similarity threshold
|
| 121 |
+
filtered_results = [
|
| 122 |
+
r for r in results.matches
|
| 123 |
+
if r.score >= SIMILARITY_THRESHOLD
|
| 124 |
+
]
|
| 125 |
+
|
| 126 |
+
if not filtered_results:
|
| 127 |
+
return {
|
| 128 |
+
'response': "I couldn't find enough reliable information for your query. Please consult with a healthcare professional.",
|
| 129 |
+
'confidence': "Low",
|
| 130 |
+
'confidence_score': 0.0,
|
| 131 |
+
'sources': [],
|
| 132 |
+
'citations': {}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
# Format context with citations
|
| 136 |
+
context, citation_map = self.format_context_with_citations(filtered_results)
|
| 137 |
+
|
| 138 |
+
# Generate response using Gemini
|
| 139 |
+
prompt = f"""{self.system_prompt}
|
| 140 |
+
|
| 141 |
+
MEDICAL CONTEXT FROM DATABASE:
|
| 142 |
+
{context}
|
| 143 |
+
|
| 144 |
+
USER QUESTION: {user_query}
|
| 145 |
+
|
| 146 |
+
INSTRUCTIONS:
|
| 147 |
+
Based on the medical context above, provide a helpful answer to the user's question.
|
| 148 |
+
- Use information from the context when available
|
| 149 |
+
- If the context has relevant but not exact information, explain what you found
|
| 150 |
+
- Be clear and helpful
|
| 151 |
+
- End with: "⚠️ This is not medical advice. Consult healthcare professionals."
|
| 152 |
+
|
| 153 |
+
Answer the question:"""
|
| 154 |
+
|
| 155 |
+
try:
|
| 156 |
+
response = self.model.generate_content(
|
| 157 |
+
prompt,
|
| 158 |
+
generation_config={
|
| 159 |
+
"temperature": 0.3, # Lower temperature for more factual responses
|
| 160 |
+
"top_p": 0.8,
|
| 161 |
+
"top_k": 40,
|
| 162 |
+
"max_output_tokens": 500,
|
| 163 |
+
}
|
| 164 |
+
)
|
| 165 |
+
answer = response.text
|
| 166 |
+
except Exception as e:
|
| 167 |
+
answer = f"Error generating response: {str(e)}"
|
| 168 |
+
print(f"DEBUG: Model error: {e}")
|
| 169 |
+
print(f"DEBUG: Model object: {self.model}")
|
| 170 |
+
|
| 171 |
+
# Calculate confidence
|
| 172 |
+
similarity_scores = [r.score for r in filtered_results]
|
| 173 |
+
confidence_level, confidence_score = self.calculate_confidence_score(similarity_scores)
|
| 174 |
+
|
| 175 |
+
return {
|
| 176 |
+
'response': answer,
|
| 177 |
+
'confidence': confidence_level,
|
| 178 |
+
'confidence_score': confidence_score,
|
| 179 |
+
'sources': [r.metadata.get('source', 'unknown') for r in filtered_results],
|
| 180 |
+
'citations': citation_map
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
def is_medical_related(self, query: str) -> bool:
|
| 184 |
+
"""Check if query is medical-related - very permissive"""
|
| 185 |
+
query_lower = query.lower()
|
| 186 |
+
|
| 187 |
+
# Comprehensive medical keywords
|
| 188 |
+
medical_keywords = [
|
| 189 |
+
'health', 'medical', 'disease', 'symptom', 'treatment', 'diagnosis',
|
| 190 |
+
'medicine', 'patient', 'doctor', 'hospital', 'therapy', 'condition',
|
| 191 |
+
'illness', 'sick', 'pain', 'cure', 'medication', 'physician',
|
| 192 |
+
'nurse', 'clinical', 'healthcare', 'surgery', 'cure', 'heal',
|
| 193 |
+
'blood', 'heart', 'lung', 'brain', 'cancer', 'diabetes', 'covid',
|
| 194 |
+
'vaccine', 'pandemic', 'infection', 'fever', 'cough', 'ache',
|
| 195 |
+
'eye', 'vision', 'irritation', 'red', 'tear', 'dry', 'irritated',
|
| 196 |
+
'head', 'headache', 'stomach', 'nausea', 'dizzy', 'tired',
|
| 197 |
+
'chest', 'breathing', 'breath', 'wheeze', 'nose', 'runny',
|
| 198 |
+
'ear', 'throat', 'sore', 'inflam', 'swell', 'burn', 'itch',
|
| 199 |
+
'suffering', 'problem', 'issue', 'hurt', 'injury', 'wound'
|
| 200 |
+
]
|
| 201 |
+
|
| 202 |
+
# Accept any query that contains medical keywords or looks like a medical concern
|
| 203 |
+
has_medical_keyword = any(keyword in query_lower for keyword in medical_keywords)
|
| 204 |
+
|
| 205 |
+
# Also accept questions with medical-sounding patterns
|
| 206 |
+
medical_patterns = [
|
| 207 |
+
'i have', 'i am suffering', 'i feel', 'why do i', 'what should i',
|
| 208 |
+
'why is', 'how to', 'how can i', 'what causes'
|
| 209 |
+
]
|
| 210 |
+
has_medical_pattern = any(pattern in query_lower for pattern in medical_patterns)
|
| 211 |
+
|
| 212 |
+
# Be permissive - if it sounds like a medical concern, accept it
|
| 213 |
+
return has_medical_keyword or has_medical_pattern
|
| 214 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.28.0
|
| 2 |
+
sentence-transformers>=2.2.2
|
| 3 |
+
pinecone==4.1.0
|
| 4 |
+
google-generativeai>=0.3.0
|
| 5 |
+
datasets>=2.14.5
|
| 6 |
+
pandas>=2.0.0
|
| 7 |
+
numpy>=2.0.0
|
| 8 |
+
python-dotenv>=1.0.0
|
| 9 |
+
transformers>=4.30.0
|
| 10 |
+
torch>=2.0.0
|
| 11 |
+
|
setup_database.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Script to set up the Pinecone database with medical data
|
| 3 |
+
"""
|
| 4 |
+
from enhanced_data_loader import load_comprehensive_medical_datasets, chunk_text
|
| 5 |
+
from embedding_service import EmbeddingService
|
| 6 |
+
import time
|
| 7 |
+
|
| 8 |
+
def setup_database():
|
| 9 |
+
"""Set up Pinecone database with medical documents"""
|
| 10 |
+
print("="*50)
|
| 11 |
+
print("Setting up Medical Chatbot Database")
|
| 12 |
+
print("="*50)
|
| 13 |
+
|
| 14 |
+
# Load comprehensive medical data from multiple sources
|
| 15 |
+
documents = load_comprehensive_medical_datasets()
|
| 16 |
+
|
| 17 |
+
# Chunk large documents
|
| 18 |
+
chunked_documents = []
|
| 19 |
+
for doc in documents:
|
| 20 |
+
chunks = chunk_text(doc['text'])
|
| 21 |
+
for chunk in chunks:
|
| 22 |
+
chunked_documents.append({
|
| 23 |
+
'text': chunk,
|
| 24 |
+
'source': doc['source'],
|
| 25 |
+
'metadata': doc['metadata']
|
| 26 |
+
})
|
| 27 |
+
|
| 28 |
+
print(f"Total chunks: {len(chunked_documents)}")
|
| 29 |
+
|
| 30 |
+
# Initialize embedding service
|
| 31 |
+
embedding_service = EmbeddingService()
|
| 32 |
+
|
| 33 |
+
# Upload to Pinecone
|
| 34 |
+
embedding_service.upsert_documents(chunked_documents)
|
| 35 |
+
|
| 36 |
+
print("\n" + "="*50)
|
| 37 |
+
print("Database setup complete!")
|
| 38 |
+
print("="*50)
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
setup_database()
|
| 42 |
+
|