Spaces:
Runtime error
Runtime error
| # 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"** | |