├── .gitignore ├── artifacts ├── metrics.json └── model.pkl ├── requirements.txt ├── Dockerfile ├── app.py ├── README.md ├── .github └── workflows │ └── ci.yml ├── train.py └── run_model.py /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | -------------------------------------------------------------------------------- /artifacts/metrics.json: -------------------------------------------------------------------------------- 1 | {"accuracy": 1.0} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==2.3.2 2 | scikit-learn==1.3.2 3 | joblib==1.4.2 4 | pandas==2.2.2 5 | -------------------------------------------------------------------------------- /artifacts/model.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iam-veeramalla/hello-world-mlops/HEAD/artifacts/model.pkl -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | COPY requirements.txt . 5 | 6 | RUN python -m pip install --upgrade pip 7 | RUN python -m pip install -r requirements.txt 8 | 9 | COPY . . 10 | 11 | EXPOSE 5001 12 | 13 | CMD ["python", "app.py"] 14 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | import joblib 3 | import os 4 | from pathlib import Path 5 | 6 | app = Flask(__name__) 7 | MODEL_PATH = Path("artifacts/model.pkl") 8 | 9 | if not MODEL_PATH.exists(): 10 | # convenience: train if model missing 11 | import train as _train 12 | _train.main() 13 | 14 | model = joblib.load(MODEL_PATH) 15 | 16 | @app.route("/health", methods=["GET"]) 17 | def health(): 18 | return jsonify({"status": "ok"}) 19 | 20 | @app.route("/predict", methods=["POST"]) 21 | def predict(): 22 | data = request.get_json() 23 | if not data or "features" not in data: 24 | return jsonify({"error": "send JSON with key 'features'"}), 400 25 | features = data["features"] 26 | try: 27 | pred = model.predict([features]) 28 | return jsonify({"prediction": int(pred[0])}) 29 | except Exception as e: 30 | return jsonify({"error": str(e)}), 500 31 | 32 | if __name__ == "__main__": 33 | app.run(host="0.0.0.0", port=5001) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello-World MLOps 2 | 3 | This repository demonstrates a tiny reproducible MLOps flow: 4 | 1. Train a small model (`train.py`) — writes `artifacts/model.pkl` and `artifacts/metrics.json` 5 | 2. Run predictions from the command line with `run_model.py --input "[5.1,3.5,1.4,0.2]"` 6 | 3. Start a minimal Flask app with `python src/app.py` that serves `/predict` 7 | 4. Build a Docker image with `docker build -t hello-mlops .` 8 | 5. CI trains the model and uploads artifacts 9 | 10 | ## Quick start (local) 11 | 1. Create and activate a venv (example using python 3.13 or 3.11): 12 | python -m venv .venv 13 | source .venv/bin/activate 14 | 15 | 2. Install dependencies: 16 | pip install --upgrade pip setuptools wheel 17 | pip install -r requirements.txt 18 | 19 | 3. Train the model: 20 | python train.py 21 | 22 | 4. Run a single prediction from CLI: 23 | python run_model.py --input "[5.1, 3.5, 1.4, 0.2]" 24 | 25 | 5. Start the API: 26 | python src/app.py 27 | Then test: 28 | curl -X POST "http://127.0.0.1:5000/predict" -H "Content-Type: application/json" -d '{"features":[5.1,3.5,1.4,0.2]}' 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI - Train and Save the model 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | matrix-pip: 11 | name: Train and Save the model 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python: [3.11, 3.12] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Setup python 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: ${{ matrix.python }} 25 | 26 | - name: Upgrade pip 27 | run: python -m pip install --upgrade pip setuptools wheel 28 | 29 | - name: Install project dependencies 30 | run: python -m pip install -r requirements.txt 31 | 32 | - name: Train the model 33 | run: | 34 | python train.py 35 | ls -la artifacts 36 | 37 | - name: Upload artifacts 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: ml-artifacts-${{ matrix.python }}-${{ github.run_id }} 41 | path: artifacts 42 | 43 | 44 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple training script: 3 | - loads iris dataset from sklearn 4 | - trains a LogisticRegression 5 | - saves model to model.pkl 6 | """ 7 | 8 | from sklearn.datasets import load_iris 9 | from sklearn.linear_model import LogisticRegression 10 | from sklearn.model_selection import train_test_split 11 | import joblib 12 | import os 13 | import json 14 | 15 | def main(): 16 | iris = load_iris() 17 | X, y = iris.data, iris.target 18 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 19 | 20 | model = LogisticRegression(max_iter=200) 21 | model.fit(X_train, y_train) 22 | 23 | # Save model 24 | os.makedirs("artifacts", exist_ok=True) 25 | model_path = os.path.join("artifacts", "model.pkl") 26 | joblib.dump(model, model_path) 27 | 28 | # Save a tiny metrics file 29 | acc = model.score(X_test, y_test) 30 | metrics = {"accuracy": float(acc)} 31 | with open(os.path.join("artifacts", "metrics.json"), "w") as f: 32 | json.dump(metrics, f) 33 | 34 | print(f"Saved model to {model_path}") 35 | print(f"Test accuracy: {acc:.4f}") 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /run_model.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Usage: 4 | python run_model.py --input "[5.1, 3.5, 1.4, 0.2]" 5 | """ 6 | 7 | import argparse 8 | import json 9 | from pathlib import Path 10 | import numpy as np 11 | import joblib 12 | 13 | MODEL_PATH = Path("artifacts/model.pkl") 14 | 15 | def load_model(): 16 | if not MODEL_PATH.exists(): 17 | raise FileNotFoundError(f"Model file not found: {MODEL_PATH}") 18 | return joblib.load(MODEL_PATH) # <-- correct binary loading 19 | 20 | def main(): 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("--input", required=True, 23 | help="Feature list as JSON string. Example: \"[5.1,3.5,1.4,0.2]\"") 24 | args = parser.parse_args() 25 | 26 | # Parse input 27 | try: 28 | features = json.loads(args.input) 29 | except json.JSONDecodeError: 30 | raise ValueError("Invalid input. Use JSON list, e.g. --input \"[5.1,3.5,1.4,0.2]\"") 31 | 32 | X = np.array(features).reshape(1, -1) 33 | 34 | model = load_model() 35 | pred = model.predict(X) 36 | 37 | print(json.dumps({"prediction": pred.tolist()})) 38 | 39 | if __name__ == "__main__": 40 | main() 41 | 42 | --------------------------------------------------------------------------------