Skip to main content

Project: Serve a scikit-learn Model

This capstone extends Ticket 39's scikit-learn training workflow into two local delivery paths. You will serve the same inert, versioned artifact through a JSONL batch command and a loopback-only HTTP API, then exercise failures as first-class contract behavior.

Prerequisites​

Complete Fallbacks, Timeouts, and Degraded Modes and the preceding Study Sprint lessons. Use the repository's pinned content environment; no registry, cloud account, hosted API, or network download is needed at runtime.

Learning goals​

  • train and serve one immutable model version locally.
  • prove that batch and online paths share preprocessing semantics.
  • observe stable valid, malformed, missing, out-of-domain, and version-mismatch behavior.

Contract recap​

The training config publishes study-sprint-completion@1 and inference schema version 1. It carries feature order, learned numeric means/scales, categorical levels, coefficients, intercept, threshold, and input constraints in inert JSON. The loader rejects unsupported format, missing fields, inconsistent dimensions, non-finite numbers, and incompatible contract state before serving.

The implementation reconstructs only this narrow linear-logit format. It never loads pickle, joblib, cloudpickle, or arbitrary code. That constrained format is a teaching choice, not a general scikit-learn serializer.

Worked example​

Train from the repository root:

python examples/ml-engineering/train.py \
--config examples/ml-engineering/training-config.json \
--output-dir build/study-sprint \
--code-version ticket-40-local

Run batch inference over the committed request fixture:

python examples/ml-engineering/predict.py batch \
--model build/study-sprint/model.json \
--input fixtures/ml-engineering/inference-requests.jsonl \
--output build/study-sprint/inference-results.jsonl

Start the local API:

python examples/ml-engineering/predict.py serve \
--model build/study-sprint/model.json \
--host 127.0.0.1 \
--port 8000

In a second terminal, request the same instances:

curl --fail-with-body \
--header 'Content-Type: application/json' \
--data-binary @fixtures/ml-engineering/inference-request.json \
http://127.0.0.1:8000/v1/predict

Both paths return probabilities 0.015377 and 0.996779 in the same order. The committed expected values are independent literals in the integration verifier, not values recalculated with the implementation under test.

Run the public-seam verifier​

npm run test:ml-engineering-serving

The verifier trains the public artifact, invokes the batch CLI in subprocesses, starts an actual ephemeral loopback server, sends HTTP requests, and checks:

  • valid response and health shapes;
  • malformed JSON;
  • missing required feature;
  • numeric value outside the declared domain;
  • unsupported request schema and mismatched model version;
  • no partial batch publication after failure;
  • rejection of non-loopback binding;
  • identical observable predictions through batch and API.

It tests public seams without importing project-owned predictor functions or mocking them. The example uses Python's teaching server only on loopback; it is not production-ready.

Interpretation and limitations​

The 40-row synthetic fixture demonstrates contracts, packaging, validation, and parity. It provides no evidence that this classifier should guide real learners. A six-decimal match in the declared environment is a regression oracle, not a portability guarantee across arbitrary runtimes or hardware. Production work still needs representative validation, privacy and harm review, supported serving infrastructure, authentication/authorization, capacity evidence, monitoring, incident response, and ownership.

Common mistakes​

  • Editing expected outputs until a broken implementation passes.
  • Testing an imported helper instead of the real CLI and HTTP boundary.
  • Changing only a model filename while keeping the old requested version.
  • Interpreting contract validation as proof of feature truth or model fitness.
  • Exposing the loopback teaching server to other machines.

Practice​

Add one invalid request locally without changing the verifier: an unknown category, boolean numeric value, extra field, empty instance list, or oversized batch. Predict the stable error code and detail path, run it through both delivery modes, and explain whether the caller should retry.

Next steps​

Continue with the next ML Engineering & MLOps batch on monitoring and operations, where service health, data quality, drift, delayed labels, incidents, and retraining policy become explicit.

Primary sources​

Checked 2026-10-15: scikit-learn model persistence and serving guidance, Python http.server security boundary, RFC 9110 HTTP semantics, OpenAPI Specification, and Google Rules of ML on training–serving skew.