zunayed02's picture
Complete Medical Predictor Chatbot deployment
47c83f0
|
Raw
History Blame Contribute Delete
19.8 kB
# Medical Predictor Chatbot - Developer Guides
## πŸ“š All Documentation Files
### For Project Overview & Planning
- **PROJECT_ROADMAP.md** ← Start here for complete timeline
- **ANALYSIS.md** ← Current status & what's missing
- **GUIDES.md** ← This file (detailed guides for each phase)
---
## 🎯 Phase-by-Phase Implementation Guide
---
## PHASE 1: Environment & Infrastructure βœ… (85% Complete)
### What You've Done
- βœ… Created basic project structure
- βœ… Set up Gradio + Groq dependencies
- βœ… Created basic Groq API integration
- βœ… Implemented state management
### What You Need to Do (15% Remaining)
#### 1.1 Create `.env` File
```bash
cd /media/zunayed/HDD_code/chatbot\ with\ llm/medical-predictor-chatbot
echo "GROQ_API_KEY=gsk_your_api_key_here" > .env
```
Get your API key:
1. Visit https://console.groq.com/keys
2. Sign up (free)
3. Generate API key
4. Paste into `.env`
#### 1.2 Create `.gitignore`
```bash
cat > .gitignore << 'EOF'
.env
*.pkl
__pycache__/
venv/
.DS_Store
*.pyc
.pytest_cache/
.idea/
*.egg-info/
dist/
build/
app_state.json
EOF
```
#### 1.3 Create Folder Structure
```bash
mkdir -p app/services app/utils models
touch app/__init__.py app/services/__init__.py app/utils/__init__.py
```
#### 1.4 Verify Installation
```bash
pip install -r requirements.txt
python test_extractor.py # Should print JSON with features
```
**βœ… Phase 1 Complete When:**
- `.env` exists with valid GROQ_API_KEY
- `.gitignore` created
- Folder structure matches
- `test_extractor.py` runs without errors
---
## PHASE 2: Configuration & Schemas πŸ”² (0% Complete)
### What to Create
#### 2.1 Create `app/config.py`
```python
import os
from pathlib import Path
from dotenv import load_dotenv
# Load environment
load_dotenv()
# Paths
BASE_DIR = Path(__file__).parent.parent
MODEL_DIR = BASE_DIR / "models"
MODEL_PATH = MODEL_DIR / "GradientBoosting_model.pkl"
# Model configuration
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
GROQ_MODEL = "llama-3.1-8b-instant"
GROQ_TEMPERATURE = 0
# Medical features (all 16)
DEFAULT_MODEL_FEATURES = [
"LengthOfStay", "Smoking", "Family History", "HbA1c", "Glucose",
"Age", "Diet Score", "Alcohol", "Physical Activity", "Blood Pressure",
"BMI", "Cholesterol", "Sleep Hours", "Stress Level",
"Triglycerides", "Oxygen Saturation"
]
# Feature validation ranges (min, max, expected type)
FEATURE_RANGES = {
"Age": (0, 150, float),
"Glucose": (70, 400, float),
"HbA1c": (3, 15, float),
"BMI": (10, 60, float),
"Cholesterol": (100, 400, float),
"Triglycerides": (20, 500, float),
"Blood Pressure": (60, 200, float), # Systolic only simplified
"Physical Activity": (0, 24, float), # hours per week
"Sleep Hours": (0, 24, float),
"Stress Level": (1, 10, float),
"Diet Score": (1, 10, float),
"Smoking": (0, 1, int), # 0=No, 1=Yes
"Alcohol": (0, 1, int), # 0=No, 1=Yes
"Family History": (0, 1, int), # 0=No, 1=Yes
"LengthOfStay": (0, 365, int), # days
"Oxygen Saturation": (80, 100, float), # percentage
}
# API configuration
GROQ_TIMEOUT = 30
MAX_RETRIES = 3
# App configuration
DEBUG_MODE = True
CONVERSATION_MAX_TURNS = 20
```
#### 2.2 Create `app/schemas.py`
```python
from typing import Optional
from pydantic import BaseModel, Field, validator
class MedicalFeatures(BaseModel):
"""All 16 required medical features"""
Age: Optional[float] = None
Glucose: Optional[float] = None
HbA1c: Optional[float] = None
BMI: Optional[float] = None
Cholesterol: Optional[float] = None
Triglycerides: Optional[float] = None
BloodPressure: Optional[float] = None
PhysicalActivity: Optional[float] = None
SleepHours: Optional[float] = None
StressLevel: Optional[float] = None
DietScore: Optional[float] = None
Smoking: Optional[int] = None
Alcohol: Optional[int] = None
FamilyHistory: Optional[int] = None
LengthOfStay: Optional[int] = None
OxygenSaturation: Optional[float] = None
@validator("Age")
def validate_age(cls, v):
if v is not None and not (0 <= v <= 150):
raise ValueError("Age must be between 0 and 150")
return v
# Add similar validators for other fields...
class Config:
use_enum_values = True
arbitrary_types_allowed = True
class PredictionRequest(BaseModel):
"""Request for prediction"""
features: MedicalFeatures
class PredictionResponse(BaseModel):
"""Prediction response"""
prediction: int # 0 or 1 (or disease class)
probability: float # 0.0 to 1.0
risk_level: str # "Low", "Medium", "High"
explanation: str
class ExtractionResponse(BaseModel):
"""LLM extraction response"""
extracted_features: MedicalFeatures
confidence: float
```
**βœ… Phase 2 Complete When:**
- Both files exist and have no import errors
- Run: `python -c "from app.config import *; from app.schemas import *"`
- No errors appear
**Time Estimate**: 15-20 minutes
---
## PHASE 3: ML Model Preparation πŸ”² (0% Complete)
### Option A: Create a Synthetic Model (for testing)
Create `create_model.py`:
```python
import joblib
import numpy as np
from sklearn.ensemble import GradientBoostingClassifier
from pathlib import Path
# Create synthetic data
np.random.seed(42)
X = np.random.randn(100, 16) # 16 features
y = np.random.randint(0, 2, 100) # Binary classification
# Train model
model = GradientBoostingClassifier(n_estimators=50, random_state=42)
model.fit(X, y)
# Save
Path("models").mkdir(exist_ok=True)
joblib.dump(model, "models/GradientBoosting_model.pkl")
print("βœ… Model saved to models/GradientBoosting_model.pkl")
print(f"Model expects {model.n_features_in_} features")
```
Run it:
```bash
python create_model.py
```
### Option B: Use Pre-trained Model
If you have an existing model file:
```bash
cp /path/to/your/GradientBoosting_model.pkl models/
```
**βœ… Phase 3 Complete When:**
- `models/GradientBoosting_model.pkl` exists
- Test with: `python -c "import joblib; m = joblib.load('models/GradientBoosting_model.pkl'); print(f'Model loaded! Features: {m.n_features_in_}')"`
**Time Estimate**: 20-40 minutes
---
## PHASE 4: Service Layer Implementation πŸ”² (50% Complete)
### 4.1 Implement `app/services/feature_builder.py`
```python
from typing import List, Dict, Any
from app.config import FEATURE_RANGES, DEFAULT_MODEL_FEATURES
from app.schemas import MedicalFeatures
def validate_feature(name: str, value: Any) -> Any:
"""Validate a single feature"""
if value is None:
return None
if name not in FEATURE_RANGES:
return None
min_val, max_val, expected_type = FEATURE_RANGES[name]
# Convert to expected type
try:
converted = expected_type(value)
except (ValueError, TypeError):
return None
# Check range
if not (min_val <= converted <= max_val):
return None
return converted
def prepare_features_dict(state: Dict[str, Any]) -> MedicalFeatures:
"""Convert state dict to MedicalFeatures model"""
validated = {}
for feature in DEFAULT_MODEL_FEATURES:
value = state.get(feature)
validated[feature] = validate_feature(feature, value)
return MedicalFeatures(**validated)
def prepare_feature_vector(state: Dict[str, Any]) -> List[float]:
"""
Convert state dict to feature vector for ML model.
Returns array in correct feature order.
Handles missing values with sensible defaults.
"""
# Define feature order (MUST match training data order)
feature_order = DEFAULT_MODEL_FEATURES
vector = []
for feature_name in feature_order:
value = state.get(feature_name)
if value is not None:
vector.append(float(value))
else:
# Use feature mean or 0 for missing values
vector.append(0.0) # Simple approach - can be improved
return vector
def is_ready_for_prediction(state: Dict[str, Any]) -> bool:
"""Check if enough features collected for prediction"""
missing = [k for k, v in state.items() if v is None]
# At least 14 out of 16 features
return len(missing) <= 2
```
### 4.2 Implement `app/services/predictor.py`
```python
import joblib
import logging
from pathlib import Path
from app.config import MODEL_PATH
from app.schemas import PredictionResponse
logger = logging.getLogger(__name__)
class Predictor:
"""Load and use ML model for predictions"""
def __init__(self, model_path: str = None):
if model_path is None:
model_path = MODEL_PATH
self.model_path = Path(model_path)
self.model = None
self.load_model()
def load_model(self):
"""Load model from disk"""
if not self.model_path.exists():
raise FileNotFoundError(f"Model not found at {self.model_path}")
try:
self.model = joblib.load(self.model_path)
logger.info(f"βœ… Model loaded from {self.model_path}")
except Exception as e:
logger.error(f"❌ Failed to load model: {e}")
raise
def predict(self, feature_vector: list) -> PredictionResponse:
"""
Make prediction on feature vector
Args:
feature_vector: List of 16 floats in correct order
Returns:
PredictionResponse with prediction, probability, risk level
"""
if self.model is None:
raise RuntimeError("Model not loaded")
try:
# Make prediction
prediction = self.model.predict([feature_vector])[0]
# Get probability
proba = self.model.predict_proba([feature_vector])[0]
probability = float(max(proba))
# Determine risk level
if probability >= 0.8:
risk_level = "High"
elif probability >= 0.5:
risk_level = "Medium"
else:
risk_level = "Low"
return PredictionResponse(
prediction=int(prediction),
probability=probability,
risk_level=risk_level,
explanation=f"Model predicts class {prediction} with {probability*100:.1f}% confidence"
)
except Exception as e:
logger.error(f"Prediction failed: {e}")
raise
# Global predictor instance
predictor = None
def get_predictor():
"""Get or create predictor instance"""
global predictor
if predictor is None:
predictor = Predictor()
return predictor
```
**βœ… Phase 4 Complete When:**
- Both services import without errors
- Test with: `python -c "from app.services.feature_builder import *; from app.services.predictor import *"`
**Time Estimate**: 30-40 minutes
---
## PHASE 5: Main Application Integration πŸ”² (20% Complete)
### Update `app/main.py`
Replace the current `main.py` with:
```python
import gradio as gr
import logging
from app.services.llm_extractor import extract_features_from_text
from app.services.feature_builder import prepare_feature_vector, is_ready_for_prediction
from app.services.predictor import get_predictor
from app.memory import initialize_state, update_state, get_missing_features
from app.utils.helpers import generate_question
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Global state
state = initialize_state()
conversation_history = []
def chat_fn(message, history):
"""Main chat function for Gradio"""
global state, conversation_history
try:
# Step 1: Extract features from user input
logger.info(f"User: {message}")
extracted = extract_features_from_text(message)
logger.info(f"Extracted: {extracted}")
# Step 2: Update memory with extracted features
state = update_state(state, extracted)
# Step 3: Check for missing features
missing = get_missing_features(state)
# Step 4: If all features collected, make prediction
if not missing or is_ready_for_prediction(state):
logger.info("All features collected! Making prediction...")
# Prepare features for model
feature_vector = prepare_feature_vector(state)
# Get prediction
predictor = get_predictor()
result = predictor.predict(feature_vector)
response = f"""
βœ… All information collected!
**Prediction Results:**
- **Prediction**: Class {result.prediction}
- **Confidence**: {result.probability*100:.1f}%
- **Risk Level**: {result.risk_level}
- **Details**: {result.explanation}
---
*Note: This is a demonstration. Always consult with a healthcare professional for medical advice.*
"""
return response
else:
# Ask for next missing feature
next_question = generate_question(missing[0])
remaining = len(missing)
response = f"Got it! {next_question}\n\n_(Missing {remaining} more features)_"
return response
except Exception as e:
logger.error(f"Error in chat: {e}")
return f"❌ Error: {str(e)}. Please try again."
# Create Gradio interface
demo = gr.ChatInterface(
fn=chat_fn,
title="πŸ₯ Medical Predictor Chatbot",
description="Chat about your medical information. I'll ask follow-up questions and predict your health risk.",
examples=[
"I'm 45 years old and my glucose is 150",
"I smoke and my stress level is 8",
"My BMI is 28 and I exercise 5 hours per week"
]
)
if __name__ == "__main__":
demo.launch()
```
**βœ… Phase 5 Complete When:**
- `python app/main.py` runs without errors
- Gradio UI launches at http://localhost:7860
- Chat works (asks questions and eventually predicts)
**Time Estimate**: 25-35 minutes
---
## PHASE 6: Error Handling & Validation πŸ”² (0% Complete)
### Add Error Handling to `app/services/llm_extractor.py`
```python
def extract_features_from_text(user_input: str) -> dict:
"""
Uses Groq LLM to extract structured medical features from text.
Returns dictionary with all required keys.
"""
if not user_input or not user_input.strip():
return {feature: None for feature in DEFAULT_MODEL_FEATURES}
prompt = f"""
You are a medical information extraction system.
Extract the following features from the user input.
Return STRICT JSON only. No explanation.
Features:
{DEFAULT_MODEL_FEATURES}
Rules:
- If value is missing, use null
- Convert values to numbers where possible
- Smoking, Alcohol, Family History β†’ 0 or 1
- Blood Pressure β†’ numeric (e.g., 120)
- Output must be valid JSON
User Input:
\"\"\"{user_input}\"\"\"
"""
try:
response = client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[
{"role": "system", "content": "You are a strict JSON generator."},
{"role": "user", "content": prompt}
],
temperature=0,
timeout=30
)
content = response.choices[0].message.content
try:
data = json.loads(content)
except json.JSONDecodeError:
# Try extracting JSON from markdown code blocks
if "```" in content:
content = content.split("```")[1]
if content.startswith("json"):
content = content[4:]
data = json.loads(content)
else:
logger.warning("Could not parse LLM response as JSON")
data = {feature: None for feature in DEFAULT_MODEL_FEATURES}
return data
except Exception as e:
logger.error(f"LLM extraction error: {e}")
return {feature: None for feature in DEFAULT_MODEL_FEATURES}
```
**βœ… Phase 6 Complete When:**
- No unhandled exceptions when running app
- Graceful error messages shown to user
**Time Estimate**: 15-20 minutes
---
## PHASE 7: Hugging Face Spaces Deployment πŸ”² (0% Complete)
### 7.1 Create HF Space
1. Go to https://huggingface.co/spaces/new
2. Fill in:
- **Space name**: medical-predictor-chatbot
- **License**: MIT
- **SDK**: Gradio
- **Visibility**: Public
### 7.2 Push Code to HF
```bash
# Navigate to your project
cd /media/zunayed/HDD_code/chatbot\ with\ llm/medical-predictor-chatbot
# Add HF remote (replace YOUR_USERNAME)
git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/medical-predictor-chatbot
# Push to HF
git push hf main
```
### 7.3 Add Secrets in HF Space
1. Go to your Space page
2. Click "Settings" β†’ "Repository Secrets"
3. Add secret:
- Key: `GROQ_API_KEY`
- Value: Your Groq API key from `console.groq.com/keys`
### 7.4 Update README.md
```markdown
---
title: Medical Predictor Chatbot
emoji: πŸ₯
colorFrom: blue
colorTo: green
sdk: gradio
app_file: app/main.py
pinned: false
---
# Medical Predictor Chatbot
A conversational AI chatbot that collects medical information through natural conversation and predicts health risks using machine learning.
## Features
- πŸ’¬ Conversational interface using Groq Llama 3 LLM
- πŸ₯ Extracts 16 medical features from free-form text
- πŸ€– Makes predictions using scikit-learn GradientBoostingClassifier
- πŸ“Š Shows confidence levels and risk assessment
## How It Works
1. Chat naturally about your health
2. The AI extracts medical information from your responses
3. Asks follow-up questions for missing information
4. Makes a health risk prediction once all data is collected
## Example Usage
- "I'm 45 years old and my glucose is 150"
- "I smoke and my stress level is 8"
- "My BMI is 28 and I exercise 5 hours per week"
## Disclaimer
⚠️ This is a demonstration tool. Always consult with healthcare professionals for medical advice.
```
**βœ… Phase 7 Complete When:**
- Code deployed to HF Spaces
- App loads and works
- GROQ_API_KEY secret is set
**Time Estimate**: 15-30 minutes
---
## PHASE 8: Testing & Finalization πŸ”² (0% Complete)
### Test Scenarios
#### Test 1: Feature Extraction
```bash
python test_extractor.py
# Check if JSON is returned with features
```
#### Test 2: Gradio UI (Local)
```bash
python app/main.py
# Open http://localhost:7860
# Test conversation flow
```
#### Test 3: Full Prediction Flow
Chat sequence:
1. "I'm 45 years old"
2. "My glucose is 150"
3. "I smoke"
4. (Continue answering questions for other features)
5. (Expect: Prediction with risk level)
#### Test 4: Error Handling
- Send empty message β†’ Should handle gracefully
- Send gibberish β†’ Should extract what it can
- Invalid values β†’ Should validate and reject
**βœ… Phase 8 Complete When:**
- All tests pass
- No errors in logs
- App works on HF Spaces
- README is complete
**Time Estimate**: 30-60 minutes
---
## πŸŽ‰ Success Criteria Checklist
- [ ] `.env` file created with GROQ_API_KEY
- [ ] `app/config.py` completed
- [ ] `app/schemas.py` completed
- [ ] `models/GradientBoosting_model.pkl` exists
- [ ] `app/services/feature_builder.py` completed
- [ ] `app/services/predictor.py` completed
- [ ] `app/main.py` updated with prediction
- [ ] Error handling added
- [ ] Logging configured
- [ ] Local testing successful
- [ ] HF Space created
- [ ] Code pushed to HF
- [ ] GROQ_API_KEY secret added to HF
- [ ] HF deployment successful
- [ ] README updated with documentation
---
## πŸš€ Start Here!
**Next step**: Follow **PHASE 2** in this guide to create `config.py` and `schemas.py`
When ready, type: **"I'm ready for Phase 2"**