zunayed02's picture
Complete Medical Predictor Chatbot deployment
47c83f0
|
Raw
History Blame Contribute Delete
19.8 kB

A newer version of the Gradio SDK is available: 6.22.0

Upgrade

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

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

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

mkdir -p app/services app/utils models
touch app/__init__.py app/services/__init__.py app/utils/__init__.py

1.4 Verify Installation

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

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

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:

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:

python create_model.py

Option B: Use Pre-trained Model

If you have an existing model file:

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

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

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:

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

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

# 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

---
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

python test_extractor.py
# Check if JSON is returned with features

Test 2: Gradio UI (Local)

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"