Skip to content

From a notebook to a prediction service

Know which data, code, parameters and environment produced your result, compare experiments, then turn the useful Python capability into MCP or REST.

Apizr 0.4.6 supports this workflow without instrumentation. For sessions, decorators, Jupyter and editor history, start with your first SDK experiment.

The fraud detection example is an ordinary notebook: pandas, a small scikit-learn forest, three metrics and joblib model serialization. It has no Apizr import, decorator or tracking call. Keep the notebook unchanged throughout this guide. Only the active CSV changes between the two runs.

Prepare your environment

Use Python 3.11–3.15 on macOS or Linux. Download and unzip the fraud detection example, then open a terminal in its fraud-detection/ directory.

Create an environment and install Apizr with the notebook's dependencies:

python3 -m venv ../science
. ../science/bin/activate
python -m pip install 'outerspace-apizr[notebook,http,mcp]==0.4.6' -r requirements.txt
cp data/transactions-a.csv data/transactions.csv

Keep this terminal open for the following steps. If you already have a scientific environment, install Apizr there instead.

1. Inspect without running

apizr experiment inspect fraud_detection.ipynb

The report shows Code, Data, Parameters, Randomness, Environment, Metrics, Outputs and Serving. Look for data/transactions.csv, n_estimators = 24, max_depth = 5, random_state = 42, the three metric calls and predict. Metric values and the saved model do not exist yet. Inspection does not execute the notebook; a seed records an intended control, not a determinism guarantee.

Excerpt from the reference notebook's inspection:

Parameters — CAPTURED
  n_estimators = 24 (cell 1, line 7)
  max_depth = 5 (cell 1, line 8)
  Static candidates, not runtime parameters.

2. Record Run A

Run executes trusted code with host filesystem, network and subprocess access. It is not a security sandbox. Review unfamiliar notebooks before executing them.

apizr experiment run fraud_detection.ipynb \
  --output model=artifacts/model.joblib --format json > run-a.json
RUN_A=$(python -c 'import json; print(json.load(open("run-a.json"))["run_digest"])')

The worker captures the three metrics automatically. The explicit output name model makes the later artifact selection readable. artifacts/trained.txt marks that training executed; it is not selected for serving.

3. Change the data, then record Run B

cp data/transactions-b.csv data/transactions.csv
apizr experiment run fraud_detection.ipynb \
  --output model=artifacts/model.joblib --format json > run-b.json
RUN_B=$(python -c 'import json; print(json.load(open("run-b.json"))["run_digest"])')

Both committed CSVs are synthetic transactions. B uses a different fraud-label rule over the same features. Neither cohort describes real people or accounts. The notebook, hyperparameters and seed remain unchanged.

4. Find both runs

apizr experiment list

5. Review Run B

apizr experiment show "$RUN_B"

Read the recorded input and model hashes, worker Python/package versions, parameters and observed metrics. History retains A even though B has replaced the active CSV and model. Only selected runtime facts are collected.

6. Compare A and B

apizr experiment diff "$RUN_A" "$RUN_B"

The recorded data identity and observed metrics changed. This comparison does not establish that one caused the other. The source, parameters, seed and environment specification should agree; unknown observations remain unknown.

Prepare to share the prediction

The first six steps let you review and compare the notebook. Continue below when you want another application to call its prediction function.

Download the dependency wheels once. The generated service will use these same versions in a separate environment:

export TARGET="$PWD/../apizr-runtime"
export SCIENCE_WHEELHOUSE="$PWD/../science-wheels"
export BUNDLES="$PWD/../prediction-bundles"
mkdir -p "$TARGET/base" "$TARGET/extras"
python -m pip download --only-binary=:all: \
  'outerspace-apizr[notebook,http,mcp]==0.4.6' -d "$TARGET/base"
python -m pip download --only-binary=:all: \
  -r requirements.txt -d "$SCIENCE_WHEELHOUSE"

7. Select the prediction from Run B

Explicitly choose the Run, capability, model artifact and serving dependencies. predict needs joblib and scikit-learn; pandas is used only during training.

Apizr checks the downloaded packages to find the imports needed by predict. Use --allow-conditional only after reviewing these dependencies: Apizr can identify their files, but cannot establish whether third-party code is safe.

Authorize reading this local example with a narrowly scoped operator file:

python - <<'PY'
import json
from pathlib import Path
Path("operator.json").write_text(json.dumps({
    "schema": "apizr.operator-policy/v1",
    "grants": [{"adapter": "repository", "operation": "analyze",
                "target": {"kind": "local", "root": str(Path.cwd())},
                "permissions": ["source.analyze"]}]
}))
PY
CAPABILITY=python:fraud_detection:predict

8. Generate REST

apizr experiment expose "$RUN_B" --root . --operator-policy operator.json \
  --capability "$CAPABILITY" --interface rest --artifact model \
  --dependency scikit-learn --dependency joblib \
  --dependency-wheelhouse "$SCIENCE_WHEELHOUSE" --allow-conditional \
  --output-dir "$BUNDLES/rest"

Prepare a separate serving environment from the generated requirements and retained wheels. Its application pins come from Run B, without a new resolution against a package index:

python -m venv ../serving
export SERVING_PYTHON="$PWD/../serving/bin/python"
"$SERVING_PYTHON" -m pip install --no-index \
  --find-links "$TARGET/extras" --find-links "$TARGET/base" \
  --find-links "$SCIENCE_WHEELHOUSE" \
  -r "$BUNDLES/rest/requirements.txt" -r "$BUNDLES/rest/application-requirements.txt"

9. Start REST and make a real call

export REST_PORT=${REST_PORT:-8765}
"$SERVING_PYTHON" -m uvicorn --app-dir "$BUNDLES/rest" app:app \
  --host 127.0.0.1 --port "$REST_PORT" > "$BUNDLES/rest.log" 2>&1 &
REST_PID=$!
echo "$REST_PID" > "$BUNDLES/rest.pid"
python - <<'PY'
import json, os, time
from urllib.request import Request, urlopen
base = "http://127.0.0.1:" + os.environ["REST_PORT"]
deadline = time.monotonic() + 20
while True:
    try:
        with urlopen(base + "/health", timeout=1) as response:
            assert json.load(response) == {"status": "ok"}
        break
    except OSError:
        if time.monotonic() >= deadline:
            raise
        time.sleep(0.05)
payload = {"transaction": {"amount": 845.0, "transaction_velocity": 11,
                           "foreign_transaction": True, "account_age_days": 45}}
with urlopen(Request(base + "/capabilities/fraud_detection.predict",
                     data=json.dumps(payload).encode(),
                     headers={"Content-Type": "application/json"}), timeout=10) as response:
    print(json.dumps(json.load(response)))
PY

The response contains predictions, with a classification and probability. Only predict is public. Training and the private feature helper are not routes.

10. Generate MCP from the same Run

apizr experiment expose "$RUN_B" --root . --operator-policy operator.json \
  --capability "$CAPABILITY" --interface mcp --artifact model \
  --dependency scikit-learn --dependency joblib \
  --dependency-wheelhouse "$SCIENCE_WHEELHOUSE" --allow-conditional \
  --output-dir "$BUNDLES/mcp"
"$SERVING_PYTHON" -m pip install --no-index \
  --find-links "$TARGET/extras" --find-links "$TARGET/base" \
  --find-links "$SCIENCE_WHEELHOUSE" \
  -r "$BUNDLES/mcp/requirements.txt" -r "$BUNDLES/mcp/application-requirements.txt"

11. Call it with the official MCP client

python - <<'PY'
import json, os
import anyio
from mcp import Client, StdioServerParameters
async def main():
    async with Client(StdioServerParameters(
        command=os.environ["SERVING_PYTHON"],
        args=[os.path.join(os.environ["BUNDLES"], "mcp/server.py")],
    ), read_timeout_seconds=20) as client:
        tools = await client.list_tools()
        assert [tool.name for tool in tools.tools] == ["fraud_detection.predict"]
        result = await client.call_tool("fraud_detection.predict", {
            "transaction": {"amount": 845.0, "transaction_velocity": 11,
                            "foreign_transaction": True, "account_age_days": 45}})
        assert not result.is_error
        print(json.dumps(result.structured_content))
anyio.run(main)
PY

The MCP client initializes a real stdio session and receives the same structured business result as REST. This generated prediction service is separate from the optional Apizr analysis MCP plugin.

Stop the REST process when finished:

kill "$REST_PID"

A successful Run is evidence, not production approval. A saved model is an ordinary artifact, not a registry entry. Exposure makes an explicitly chosen interface; organizational promotion and deployment remain separate decisions. Continue with experiment evidence, comparison or serving integrity.