Generate an AI Bill of Materials

Train a model in MLflow and generate a CycloneDX 1.7 AI Bill of Materials.

Introduction

30 Second Summary

Every product you buy has an ingredient list. AI systems are increasingly expected to have the same. When regulators, clients, or your own governance team ask "what went into this model?", a machine-readable answer beats a shrug.

In this project, you will train a wine quality classifier, track its full lineage in MLflow, and generate a standards-compliant AI Bill of Materials that documents every ingredient in your ML system.

What You'll Build

You run a single pipeline that trains a model, tracks it end-to-end, and outputs a CycloneDX 1.7 JSON file you can hand to any governance gate or compliance reviewer.

By the end of this project, you'll have:

  • A model training pipeline where you run one script and see parameters, metrics, and artifacts appear in MLflow's tracking UI.
  • A registered model with version control and governance tags, browsable in MLflow's Model Registry.
  • A CycloneDX 1.7 AIBOM (JSON) that captures your model identity, dependencies, dataset references, and model card metadata in one machine-readable file you can present to your team.
  • Secret Mission: Audit your AIBOM for completeness gaps and add dataset provenance with SHA-256 hashes.

Are there any prerequisites?

You should be comfortable with basic Python (writing functions, running scripts from the terminal). Familiarity with ML concepts like training and testing a model is helpful, but you do not need prior experience with MLflow or BOMs.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters for your team.

Set Up the MLOps Environment

Every tool in this project plays a specific role in the pipeline you are about to build. MLflow tracks your model's training details, scikit-learn provides the classifier, and ai-bom-generator turns the tracked evidence into a standards-compliant Bill of Materials.

Without all three installed and verified, nothing else in the pipeline works. In this step you will create an isolated project directory, install pinned versions of each tool, and confirm they respond correctly.

In this step, get ready to:
  • Create a project directory and a Python virtual environment.
  • Install MLflow, scikit-learn, and ai-bom-generator via pip.
  • Verify each tool is accessible by checking versions in the terminal.
Create the project directory and virtual environment
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir mlflow-aibom
cd mlflow-aibom
  • Confirm the folder exists by running:
pwd

You should see a path ending in mlflow-aibom (e.g. /Users/yourname/Desktop/mlflow-aibom).

Before creating the virtual environment, confirm your Python version meets the project's requirement of 3.12 or higher (but below 3.15).

  • Check your Python version by running:
python3 --version

✔️ I see version 3.12 or higher

Your Python version is compatible. Continue to the next instruction below.

ⓧ I see a version lower than 3.12

The ai-bom-generator package requires Python >=3.12,<3.15. You need to install a compatible version before continuing.

  • Visit the official Python downloads page at https://www.python.org/downloads/ and install Python 3.12 or 3.13.
  • After installing, close and reopen your terminal, then re-run python3 --version to confirm the new version.

macOS shadows newer installs

macOS may still resolve python3 to an older system version even after you install a newer one. Try running python3.12 --version or python3.13 --version to use the newly installed binary directly.

ⓧ Command not found

Python is not installed or not on your PATH.

  • Visit https://www.python.org/downloads/ and download Python 3.12 or 3.13 for your operating system.
  • Run the installer, making sure to check the option to add Python to your PATH.
  • Close and reopen your terminal, then re-run python3 --version to confirm.

A virtual environment keeps this project's packages isolated from your system Python. Create and activate one now using the tab that matches your operating system.

macOS

  • Create and activate the virtual environment by running these commands:
python3 -m venv .venv
source .venv/bin/activate
  • Confirm activation by running:
which python

You should see a path containing .venv/bin/python. Your terminal prompt may also show (.venv) at the beginning.

Windows

  • Create and activate the virtual environment by running these commands:
python -m venv .venv
.venv\Scripts\activate
  • Confirm activation by running:
where python

You should see a path containing .venv\Scripts\python.exe. Your terminal prompt may also show (.venv) at the beginning.

Not seeing the .venv path?

Make sure you ran the activation command (source .venv/bin/activate on macOS or .venv\Scripts\activate on Windows) in the same terminal session.

On Windows, if you see an execution policy error, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned first, then try the activation command again.

help me activate my Python virtual environment

Install the project dependencies

You will pin exact versions so that every tool behaves predictably throughout the project. The three packages go into a requirements.txt file that pip reads.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type the name of your code editor (e.g. VS Code or Cursor) and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Select the mlflow-aibom folder on your Desktop, then click Open.
  • Create a new file called requirements.txt by right-clicking in the Explorer sidebar and selecting New File.
  • Paste the following contents into requirements.txt:
mlflow==3.15.1
scikit-learn==1.9.0
ai-bom-generator==0.6.1

What does each package do?

  • mlflow==3.15.1 provides experiment tracking, model registration, and a browser-based UI to inspect training runs.
  • scikit-learn==1.9.0 supplies the RandomForestClassifier and the wine dataset you will train on.
  • ai-bom-generator==0.6.1 reads your project structure and generates a CycloneDX 1.7 AIBOM from it.
  • Save requirements.txt with Cmd+S (macOS) or Ctrl+S (Windows).
  • Open the integrated terminal in your editor by pressing Ctrl+` (backtick key, same shortcut on both platforms).
  • Install all three packages by running:
pip install -r requirements.txt

Pip downloads and installs the packages and their dependencies. You should see a stream of progress bars followed by a final line confirming the install succeeded.

Seeing a permissions error or package conflict?

Make sure your virtual environment is active. Your terminal prompt should start with (.venv). If not, re-run the activation command from the previous substep.

If you see a resolver conflict mentioning Python version requirements, confirm your Python is 3.12 or higher (but below 3.15) by running python --version in the same terminal.

help me fix my pip install error for MLflow and ai-bom-generator

✔️ Awesome, I've got everything!

Great. Double check you have saved requirements.txt and that pip reported a successful install.

ⓧ I'd like to double check the full code

Your requirements.txt should contain exactly these three lines:

mlflow==3.15.1
scikit-learn==1.9.0
ai-bom-generator==0.6.1
Verify each tool is accessible

Before you run the version commands, what do you expect to see if the install worked? Each tool should print its version number.

  • Check that MLflow is accessible by running:
mlflow --version

You should see 3.15.1 printed in the output.

  • Check that the AIBOM generator is accessible by running:
ai-bom --version

You should see 0.6.1 printed in the output.

What about scikit-learn?

scikit-learn does not install a standalone CLI command. You can confirm it imported successfully by running:

python -c "import sklearn; print(sklearn.__version__)"

This prints 1.9.0.

Command not found for mlflow or ai-bom?

This usually means the virtual environment is not active in your current terminal session. Re-run the activation command (source .venv/bin/activate on macOS or .venv\Scripts\activate on Windows), then try the version command again.

If activation is already confirmed, check that pip install finished without errors by scrolling up in your terminal output.

help me troubleshoot why mlflow or ai-bom commands are not found

Your environment is ready with all three tools locked to known versions. Next up, you will write a training script that puts MLflow to work, tracking every detail of a wine quality classifier.

Train and Track a Model with MLflow

Your environment is ready with MLflow, scikit-learn, and ai-bom-generator installed. Now you need the thing an AIBOM actually describes: a trained model with full lineage.

For an AIBOM to capture what went into your AI system, you need a model trained under a tracking system that records parameters, metrics, and artifacts. That is what this step produces.

In this step, get ready to:
  • Write a training script that trains a RandomForestClassifier on the wine dataset with MLflow tracking.
  • Run the script and verify the tracked run in the MLflow UI.
Write the training script

MLflow's autologging captures model parameters and artifacts automatically. You will also log test metrics and governance tags manually to give the AIBOM richer lineage data.

  • Create a new file called train.py in your project directory.
  • Paste the following imports and MLflow configuration at the top of the file:
import mlflow
from sklearn.datasets import load_wine
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score

# Set experiment name
mlflow.set_experiment("wine-classifier-aibom")

# Enable autologging for scikit-learn
mlflow.sklearn.autolog(log_datasets=True, log_input_examples=True)

What does this code do?

These lines set up the tracking infrastructure before any training happens.

  • mlflow.set_experiment("wine-classifier-aibom") creates (or selects) a named experiment so all runs group together in the UI.
  • mlflow.sklearn.autolog(log_datasets=True, log_input_examples=True) tells MLflow to automatically capture every parameter, the fitted model artifact, and dataset info whenever you call .fit().

The next chunk completes the script with data loading, training, and manual metric logging.

  • Below the configuration block in train.py, paste the data loading and model training code:
# Load and prepare data
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42
)

# Train model with MLflow tracking
with mlflow.start_run(run_name="wine-rf-v1") as run:
    model = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42)
    model.fit(X_train, y_train)

    # Manual metric logging for test performance
    y_pred = model.predict(X_test)
    test_accuracy = accuracy_score(y_test, y_pred)
    test_f1 = f1_score(y_test, y_pred, average="weighted")
    mlflow.log_metric("test_accuracy", test_accuracy)
    mlflow.log_metric("test_f1_weighted", test_f1)

    # Tag the run for governance
    mlflow.set_tag("model_task", "multi-class-classification")
    mlflow.set_tag("dataset_name", "sklearn-wine")

    print(f"Run ID: {run.info.run_id}")
    print(f"Test Accuracy: {test_accuracy:.4f}")
    print(f"Test F1 (weighted): {test_f1:.4f}")

What does this code do?

This block loads the UCI Wine dataset (178 samples, 13 features, 3 classes), splits it 80/20, and trains the classifier inside an MLflow run.

  • mlflow.start_run(run_name="wine-rf-v1") opens a tracked run. Everything inside this block gets recorded.
  • Autologging captures n_estimators and max_depth automatically. The manual log_metric calls add test-set accuracy and F1 that autolog cannot capture.
  • mlflow.set_tag attaches governance metadata (task type, dataset name) that the AIBOM generator can reference later.
  • Save train.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved the file before moving on.

ⓧ I'd like to double check the full code

Your train.py should contain these two sections in order:

  • Chunk 1: Imports (mlflow, sklearn modules) followed by set_experiment and autolog calls.
  • Chunk 2: load_wine() and train_test_split, then the entire with mlflow.start_run(...) block including model training, metric logging, tag setting, and print statements.

Scroll up and confirm each chunk is present in the correct order.

Run the training script and launch the MLflow UI

The script is complete. Time to run it and see what MLflow captured.

  • Before you run this, what do you think the script will print? Will training succeed or fail?
  • Run the training script by executing this command:
python train.py

You should see output with your Run ID, a test accuracy around 0.97, and a test F1 score around 0.97.

Seeing a ModuleNotFoundError?

  • Make sure your virtual environment is activated. You should see your environment name in parentheses at the start of your terminal prompt.
  • Confirm MLflow is installed by running mlflow --version. If it prints nothing, re-run pip install mlflow==3.15.1 scikit-learn==1.9.0.

Still stuck? Help me debug a ModuleNotFoundError when running train.py

MLflow stored all the run data locally in a new mlruns/ directory. Now launch the MLflow UI to explore it visually.

  • Start the MLflow tracking UI by running:
mlflow ui

What does this command do?

This starts a local web server that serves the MLflow tracking UI. The command takes over your terminal until you stop it.

You will see a message confirming the server is running on http://127.0.0.1:5000 with instructions to press CTRL+C to quit. Keep this terminal running while you inspect the UI.

  • Open your browser and navigate to http://127.0.0.1:5000.
  • Click the experiment named wine-classifier-aibom in the left sidebar.
  • Click the run named wine-rf-v1 in the runs table.

You should see the run detail page with parameters (n_estimators=100, max_depth=5), metrics (test_accuracy, test_f1_weighted), and a model artifact listed under Artifacts.

Why do both autolog and manual metrics appear?

Autologging captures training-time metrics (like training_score) and all hyperparameters automatically. The manual log_metric calls you added record test-set performance that autolog cannot know about.

Together, they give the AIBOM a complete picture of how the model was trained and how it performs on unseen data.

Don't see wine-classifier-aibom in the UI?

  • Make sure you ran mlflow ui from the same directory where you ran python train.py. MLflow looks for the mlruns/ folder in the current working directory.
  • If your browser shows a blank experiments list, try a hard refresh (Cmd+Shift+R on macOS or Ctrl+Shift+R on Windows).

Need more help? Help me troubleshoot a missing experiment in the MLflow UI

Your model is trained and fully tracked. Next up, you will register it in MLflow's Model Registry and attach governance metadata that the AIBOM generator needs.

Register the Model and Add Governance Metadata

Your model is trained and tracked, but it is just sitting inside a run. Anyone on your team looking at the MLflow UI sees an experiment with a run ID, not a named, versioned model they can trust.

Registering the model in MLflow's Model Registry gives it a formal identity: a name, a version number, an alias your team can reference, and governance tags that record its approval status. Without this, the AIBOM you generate later has no formal model identity to point to.

In this step, get ready to:
  • Write a registration script that promotes the trained model to the Model Registry.
  • Apply governance metadata (alias, validation status, intended use).
  • Confirm the model appears in the MLflow UI Models tab.
Write the registration script

The registration script needs to find the latest training run, extract the model artifact URI, and promote it to the registry. You will build this in two chunks: first finding the run, then registering and tagging the model.

  • Create a new file called register_model.py in your project directory.
  • Add the following code to set up the client and find the latest run:
import mlflow
from mlflow import MlflowClient

client = MlflowClient()

# Find the latest run in our experiment
experiment = client.get_experiment_by_name("wine-classifier-aibom")
runs = client.search_runs(
    experiment_ids=[experiment.experiment_id],
    order_by=["start_time DESC"],
    max_results=1,
)

if not runs:
    print("No runs found. Run train.py first.")
    raise SystemExit(1)

latest_run = runs[0]
run_id = latest_run.info.run_id
print(f"Registering model from run: {run_id}")

What does this code do?

MlflowClient gives you direct access to the MLflow tracking server and registry APIs.

The script looks up your experiment by name, searches for the most recent run (sorted by start time), and extracts its run ID. If no runs exist, it exits early with a helpful message.

  • Below the existing code in register_model.py, add the registration and governance logic:
# Register the model
model_uri = f"runs:/{run_id}/model"
model_version = mlflow.register_model(model_uri=model_uri, name="wine-quality-classifier")

print(f"Registered model: {model_version.name}, version: {model_version.version}")

# Set alias for this version
client.set_registered_model_alias(
    name="wine-quality-classifier",
    alias="champion",
    version=model_version.version,
)

# Add governance tags
client.set_model_version_tag(
    name="wine-quality-classifier",
    version=model_version.version,
    key="validation_status",
    value="approved",
)
client.set_model_version_tag(
    name="wine-quality-classifier",
    version=model_version.version,
    key="intended_use",
    value="wine-quality-prediction-demo",
)

print(f"Alias 'champion' set for version {model_version.version}")
print("Governance tags applied: validation_status=approved, intended_use=wine-quality-prediction-demo")

What does this code do?

register_model() promotes the model artifact from the run into the Model Registry with a human-readable name and version number.

The alias champion gives your team a stable reference to the best model version. As you train new versions, you move the alias to whichever version passes validation.

Governance tags like validation_status and intended_use record your team's approval decisions directly on the model version. These become part of the AIBOM later.

  • Save register_model.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved the file before running it.

ⓧ I'd like to double check the full code

Here is the complete register_model.py for comparison:

import mlflow
from mlflow import MlflowClient

client = MlflowClient()

# Find the latest run in our experiment
experiment = client.get_experiment_by_name("wine-classifier-aibom")
runs = client.search_runs(
    experiment_ids=[experiment.experiment_id],
    order_by=["start_time DESC"],
    max_results=1,
)

if not runs:
    print("No runs found. Run train.py first.")
    raise SystemExit(1)

latest_run = runs[0]
run_id = latest_run.info.run_id
print(f"Registering model from run: {run_id}")

And the second half:

# Register the model
model_uri = f"runs:/{run_id}/model"
model_version = mlflow.register_model(model_uri=model_uri, name="wine-quality-classifier")

print(f"Registered model: {model_version.name}, version: {model_version.version}")

# Set alias for this version
client.set_registered_model_alias(
    name="wine-quality-classifier",
    alias="champion",
    version=model_version.version,
)

# Add governance tags
client.set_model_version_tag(
    name="wine-quality-classifier",
    version=model_version.version,
    key="validation_status",
    value="approved",
)
client.set_model_version_tag(
    name="wine-quality-classifier",
    version=model_version.version,
    key="intended_use",
    value="wine-quality-prediction-demo",
)

print(f"Alias 'champion' set for version {model_version.version}")
print("Governance tags applied: validation_status=approved, intended_use=wine-quality-prediction-demo")
Run the script and confirm registration in the UI

Before you run this, think about what you expect to see. The script searches for a run, registers the model, then applies metadata.

  • What do you think the script will print if registration succeeds?
  • Run the registration script:
python register_model.py

You should see output showing the run ID, the registered model name with version 1, the alias confirmation, and the governance tags applied.

Seeing 'No runs found'?

This means the script could not find any runs in the wine-classifier-aibom experiment. Make sure you ran python train.py successfully in the previous step.

help me debug why register_model.py cannot find my training run

  • Now confirm it in the UI. Switch back to the MLflow UI in your browser at http://127.0.0.1:5000.
  • Click the Models tab in the top navigation.
  • You should see wine-quality-classifier listed with version 1.
  • Click into the model and verify you see the champion alias and the governance tags (validation_status, intended_use).

Why aliases instead of stage names?

MLflow 3.x replaced the old staging workflow (Staging, Production, Archived) with aliases. Aliases are flexible labels your team defines. You can have champion, challenger, or any label that matches your governance process.

Your model now has a formal identity in the registry, complete with version control and governance metadata. Next up, you will structure the project so the AIBOM generator can read everything it needs to produce a standards-compliant Bill of Materials.

Structure the Project for AIBOM Generation

You have a trained, registered model with full MLflow lineage. But the ai-bom-generator cannot produce a complete AIBOM from code alone.

The generator reads structured metadata files that declare what your model is, what it does, and what went into building it. Without a MODEL_CARD.md and an aibom.toml configuration, the generator has no formal evidence to work with. In this step, you create both files and run the generator to produce your first standards-compliant CycloneDX 1.7 AIBOM.

In this step, get ready to:
  • Create a MODEL_CARD.md documenting your model's purpose, data, and limitations.
  • Create an aibom.toml configuration declaring the model identity, dependencies, and datasets.
  • Run the generator and produce a valid CycloneDX 1.7 JSON AIBOM.
Create the model card (part 1)

A model card is structured documentation that describes your model's identity, intended use, training data, and limitations. The ai-bom-generator reads this file and embeds its content into the AIBOM's model card metadata section.

  • Create a new file called MODEL_CARD.md in your project directory.
  • Paste the following content for the model details, intended use, and training data sections:
# Model Card: Wine Quality Classifier

## Model Details

- **Model Name:** wine-quality-classifier
- **Version:** 1.0.0
- **Type:** RandomForestClassifier (scikit-learn)
- **Task:** Multi-class classification (3 wine cultivar classes)
- **License:** Apache-2.0
- **Author:** ML Team

## Intended Use

- **Primary Use:** Classify wine samples into one of three cultivar classes based on chemical analysis features.
- **Intended Users:** Data science teams evaluating MLOps pipelines and AIBOM generation workflows.
- **Out-of-Scope Uses:** Production decision-making for wine quality grading, medical or safety-critical applications.

## Training Data

- **Dataset:** UCI Wine dataset (via scikit-learn `load_wine()`)
- **Source:** https://archive.ics.uci.edu/dataset/109/wine
- **Size:** 178 samples, 13 features, 3 classes
- **Split:** 80% train / 20% test (random_state=42)
- **Preprocessing:** None (raw features used directly)

What does this content do?

Each section maps to a specific part of the CycloneDX model card schema. Model Details gives the AIBOM its component identity (name, version, license). Intended Use declares scope boundaries that governance gates check. Training Data records provenance so auditors can trace where the model learned from.

  • Save MODEL_CARD.md.
Create the model card (part 2)
  • Below the Training Data section in MODEL_CARD.md, add the evaluation metrics, limitations, and ethical considerations:
## Evaluation Metrics

- **Test Accuracy:** ~0.97
- **Test F1 (weighted):** ~0.97
- **Evaluation Method:** Hold-out test set (20% of data)

## Limitations

- Small dataset (178 samples) limits generalization claims
- No hyperparameter tuning performed beyond defaults
- Model has not been tested on out-of-distribution wine data
- Not suitable for production deployment without further validation

## Ethical Considerations

- No sensitive personal information in the dataset
- Low risk application (wine cultivar classification)
- No known fairness concerns for this dataset

What does this content do?

Evaluation Metrics lets the AIBOM report quantitative performance. Limitations surfaces known weaknesses, which the EU AI Act and internal governance policies require for transparency. Ethical Considerations confirms that bias and privacy risks were assessed.

  • Save MODEL_CARD.md.
  • Confirm the file exists by running:
ls MODEL_CARD.md

You should see MODEL_CARD.md listed in the output.

Don't see the file?

Make sure you created the file inside your project directory (the same folder containing train.py and mlruns/). Check that your terminal is in the correct directory with pwd.

Help me find where my MODEL_CARD.md ended up

✔️ Awesome, I've got everything!

Great. Double check you saved the file with all six sections (Model Details through Ethical Considerations).

ⓧ I'd like to double check the full code

Your MODEL_CARD.md should contain all six sections in order: Model Details, Intended Use, Training Data, Evaluation Metrics, Limitations, and Ethical Considerations. Scroll up and confirm both chunks are present, starting with the # Model Card: Wine Quality Classifier heading and ending with the three Ethical Considerations bullet points.

Create the AIBOM configuration file

The aibom.toml file tells the generator where to find your model identity, artifacts, dependencies, and datasets. Without it, the CLI has no instructions for what to include in the BOM.

  • Create a new file called aibom.toml in your project directory and paste the following configuration:
schema_version = "1"

[output]
format = "cyclonedx-json-1.7"

[model]
name = "wine-quality-classifier"
version = "1.0.0"
model_card = "MODEL_CARD.md"
license_declared = "Apache-2.0"

[artifacts]
include = ["mlruns/**/*.pkl", "mlruns/**/*.skops"]

[[dependencies]]
path = "requirements.txt"
type = "pip"

[[datasets]]
name = "uci-wine"
license_declared = "CC-BY-4.0"

What does each section do?

  • [model] declares the model's identity, version, license, and points to your model card file.
  • [artifacts] tells the generator which files are model artifacts. The glob patterns cover both .pkl and .skops serialization formats that MLflow may produce.
  • [[dependencies]] points to requirements.txt so the generator parses your pinned packages into BOM library components.
  • [[datasets]] registers the UCI Wine dataset with its license for supply chain traceability.
  • Save aibom.toml.
  • Confirm both metadata files are in place by running:
ls MODEL_CARD.md aibom.toml requirements.txt

You should see all three files listed. Your requirements.txt already exists from Step 1 with your pinned dependencies. The generator parses it to build the dependency component list.

Missing requirements.txt?

If requirements.txt is missing, the generator cannot discover your dependencies. Confirm it exists with the three pinned packages (mlflow==3.15.1, scikit-learn==1.9.0, ai-bom-generator==0.6.1).

Help me fix my missing requirements.txt

✔️ Awesome, I've got everything!

Great. Double check you saved aibom.toml with all the sections intact.

ⓧ I'd like to double check the full code

schema_version = "1"

[output]
format = "cyclonedx-json-1.7"

[model]
name = "wine-quality-classifier"
version = "1.0.0"
model_card = "MODEL_CARD.md"
license_declared = "Apache-2.0"

[artifacts]
include = ["mlruns/**/*.pkl", "mlruns/**/*.skops"]

[[dependencies]]
path = "requirements.txt"
type = "pip"

[[datasets]]
name = "uci-wine"
license_declared = "CC-BY-4.0"
Run the generator and inspect the output

All the evidence files are in place. Now you can run the ai-bom-generator to produce a machine-readable CycloneDX 1.7 AIBOM from your project.

  • Create the output directory by running this command:
mkdir -p ../output

Why does output go outside the project?

The ai-bom-generator requires that generated output paths resolve outside the target model project directory. This prevents the generated BOM from being accidentally included as evidence in future runs.

Before you run the generator, what do you think the output will contain? Will it have your model's name, your dependencies, and the dataset reference?

  • Generate the AIBOM by running this command:
ai-bom generate . --format cyclonedx-json-1.7 --output ../output/bom.cdx.json --warning-report ../output/warnings.json --summary ../output/summary.json

The generator reads your aibom.toml, parses MODEL_CARD.md, scans artifacts, and writes three output files.

  • Check the generation summary by running:
cat ../output/summary.json

You should see "status": "success" in the output. This confirms the generator processed your project without errors.

  • Verify the BOM file exists and is valid JSON by running:
python -c "import json; bom = json.load(open('../output/bom.cdx.json')); print(f'Format: {bom[\"bomFormat\"]}'); print(f'Spec: {bom[\"specVersion\"]}'); print(f'Components: {len(bom.get(\"components\", []))}')"

You should see output showing Format: CycloneDX, a spec version, and a component count greater than zero. This proves your AIBOM is a valid CycloneDX document with real content.

Seeing an error when running ai-bom generate?

Make sure your virtual environment is activated. The ai-bom command is only available inside the environment where you installed it.

If the error mentions the output path, confirm you created the ../output directory. The tool requires the output directory to exist and to resolve outside your project folder.

Help me debug my ai-bom generate error

You now have a standards-compliant CycloneDX 1.7 AIBOM sitting in your output directory. Next up, you'll write a script to inspect and interpret this AIBOM so you can walk your team through exactly what it captured.

Inspect and Interpret the Generated AIBOM

You have a bom.cdx.json file sitting in your output directory. That is a machine-readable artifact, but generating a JSON file is not the same as understanding what it says.

Your team demo needs you to walk through the AIBOM structure confidently. In this step, you will write a script that extracts and presents the key fields so you can confirm the BOM matches what MLflow tracked and identify any governance gaps.

In this step, get ready to:
  • Write an inspection script that parses the CycloneDX 1.7 JSON and prints a human-readable summary.
  • Run the script and walk through the output, confirming it matches what MLflow tracked.
Write the inspection script header and metadata extraction

The CycloneDX BOM has a predictable structure. Every BOM starts with top-level metadata fields like bomFormat, specVersion, and serialNumber. Your script will extract these first.

  • Create a new file called inspect_bom.py in your wine-classifier project directory (the same folder that contains train.py).
  • Add the following code to set up the imports, default path, and BOM metadata extraction:
import json
import sys

BOM_PATH = "../output/bom.cdx.json"


def inspect_bom(path):
    with open(path, "r") as f:
        bom = json.load(f)

    print("=" * 60)
    print("AIBOM INSPECTION REPORT")
    print("=" * 60)

    # BOM metadata
    print(f"\nFormat: {bom.get('bomFormat', 'N/A')}")
    print(f"Spec Version: {bom.get('specVersion', 'N/A')}")
    print(f"Serial Number: {bom.get('serialNumber', 'N/A')}")

What does this code do?

  • BOM_PATH points to the generated BOM file in the output/ directory one level up (since the generator outputs outside the project directory).
  • The function opens the JSON, then pulls three top-level CycloneDX fields that identify what standard the BOM follows and its unique serial number.
Add component filtering and model details

CycloneDX stores everything as components. Your AIBOM has two kinds: ML model components and library dependencies. The script needs to separate them and display the model's governance-critical fields.

  • Still in inspect_bom.py, add the following code directly below the last print statement (still inside the inspect_bom function):
    # Components summary
    components = bom.get("components", [])
    print(f"\nTotal Components: {len(components)}")

    ml_models = [c for c in components if c.get("type") == "machine-learning-model"]
    libraries = [c for c in components if c.get("type") == "library"]

    print(f"  ML Model Components: {len(ml_models)}")
    print(f"  Library Components: {len(libraries)}")

    # Model details
    for model in ml_models:
        print(f"\n--- ML Model ---")
        print(f"  Name: {model.get('name', 'N/A')}")
        print(f"  Version: {model.get('version', 'N/A')}")
        licenses = model.get("licenses", [])
        if licenses:
            for lic in licenses:
                license_info = lic.get("license", {})
                print(f"  License: {license_info.get('id', license_info.get('name', 'N/A'))}")
        hashes = model.get("hashes", [])
        if hashes:
            for h in hashes:
                print(f"  Hash ({h.get('alg', '?')}): {h.get('content', 'N/A')[:16]}...")
        else:
            print("  Hashes: NONE (consider adding for governance)")

What does this code do?

  • It filters the components list by type. CycloneDX uses machine-learning-model for ML components and library for dependency packages.
  • For each ML model it prints name, version, license, and hash status. A governance gate would check all of these fields.
  • The hash check is important. If no hashes are present, the model artifact cannot be verified for integrity. The script flags this gap explicitly.
Add dependency listing and the main entry point
  • Add the final section below the model details code (still inside the inspect_bom function), then add the __main__ block at the bottom of the file:
    # Dependency libraries
    if libraries:
        print(f"\n--- Dependencies ({len(libraries)} packages) ---")
        for lib in libraries[:5]:
            print(f"  {lib.get('name', '?')} @ {lib.get('version', '?')}")
        if len(libraries) > 5:
            print(f"  ... and {len(libraries) - 5} more")

    print("\n" + "=" * 60)
    print("AIBOM generation complete. Ready for team review.")
    print("=" * 60)


if __name__ == "__main__":
    path = sys.argv[1] if len(sys.argv) > 1 else BOM_PATH
    inspect_bom(path)

What does this code do?

  • The dependency section prints the first five library components with their pinned versions. This confirms your requirements.txt was parsed correctly by the generator.
  • The __main__ block lets you optionally pass a custom path as a command-line argument. By default it uses the BOM_PATH constant pointing at your generated file.
  • Save inspect_bom.py.

✔️ Awesome, I've got everything!

Great. Double check you have saved the file before running it.

ⓧ I'd like to double check the full code

Your inspect_bom.py was built across three chunks. Scroll up and confirm each one is present in this order:

  • Chunk 1: imports, BOM_PATH constant, function definition start through the three metadata print lines.
  • Chunk 2: components summary, ml_models and libraries filtering, model details loop with license and hash checks.
  • Chunk 3: dependency libraries listing, closing banner, and the if __name__ == "__main__" block.

Seeing an indentation error?

All code from the components summary through the dependency listing must be indented one level inside the inspect_bom function. Only the if __name__ block sits at the top level.

help me fix an indentation error in my inspect_bom.py script

Run the inspection script and review the output

Before you run this, think about what you expect the report to show. What model name, version, and license did you declare in aibom.toml? Will the model have hashes, or not?

  • Run the inspection script by running this command:
python inspect_bom.py

You should see a structured report that starts with the CycloneDX header, then lists your model component and dependencies.

What should I see?

The output confirms your AIBOM captured what MLflow tracked:

  • Format: CycloneDX and Spec Version: 1.7 confirm the BOM follows the CycloneDX 1.7 standard.
  • The ML Model section shows Name: wine-quality-classifier, Version: 1.0.0, and License: Apache-2.0. These match your aibom.toml declarations.
  • Hashes: NONE (consider adding for governance) flags a real governance gap. Without hashes, nobody can verify the model artifact has not been tampered with.
  • The Dependencies section lists the packages from your requirements.txt with their pinned versions.

Why does the hash gap matter?

A governance gate checking your AIBOM would flag that missing hash. It means anyone could swap the model file without detection. The Secret Mission covers how to fix this by computing SHA-256 hashes and adding them to your configuration.

Seeing FileNotFoundError?

This means the script cannot find bom.cdx.json at the expected path. Make sure you are running the script from inside your project directory (the folder containing train.py), not from the output/ directory. The path ../output/bom.cdx.json resolves relative to where you run the command.

help me fix a FileNotFoundError when running inspect_bom.py

You now have a complete, runnable pipeline from model training to AIBOM inspection. The report confirms your CycloneDX 1.7 BOM captured model identity, license, dependencies, and dataset references. It also surfaced a real gap: no artifact hashes for integrity verification.

Next up in the Secret Mission, you will close that gap by computing SHA-256 hashes and adding full dataset provenance to make your AIBOM governance-gate ready.

Secret mission

Audit for Completeness and Add Dataset Provenance

Your AIBOM passed generation, but would it survive a real governance gate? In this challenge, you add SHA-256 artifact hashes and detailed dataset provenance to produce a zero-warning BOM that proves exactly what went into your model.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources on disk for future demos, or delete them to reclaim space.

Resources you used:

  • Project directory containing train.py, register_model.py, inspect_bom.py, compute_hash.py, MODEL_CARD.md, aibom.toml, and requirements.txt.
  • mlruns/ directory (MLflow tracking data, experiment runs, and registered model artifacts).
  • output/ directory (bom.cdx.json, summary.json, warnings.json).
  • Python virtual environment inside the project directory.

Keep everything running

No action needed. Choose this if you plan to reuse your MLflow experiment data for future demos or extend the pipeline.

  • Your mlruns/ directory and generated BOMs remain on disk.
  • You can re-run mlflow ui at any time to browse your tracked experiments.
  • You can re-run ai-bom generate after updating your model or configuration.

Pause - I'll come back to this later

Shut down any running processes to free up terminal sessions and memory. Your files stay on disk for when you return.

  • If the MLflow UI is still running in a terminal, press Ctrl+C to stop it.
  • Deactivate your virtual environment by running:
deactivate
  • Your project files, mlruns/ data, and generated BOMs remain on disk for next time.

Delete - I don't want to use this again

Remove all project files, the virtual environment, MLflow tracking data, and generated BOM output to start fresh.

  • If the MLflow UI is still running, press Ctrl+C to stop it.
  • Deactivate your virtual environment by running:
deactivate
  • Navigate to your Desktop and delete the entire project folder (which contains the model project directory, mlruns/, output/, and the virtual environment).

On macOS or Linux, run:

cd ~/Desktop
rm -rf aibom-mlflow-pipeline

On Windows, run:

cd ~\Desktop
Remove-Item -Recurse -Force aibom-mlflow-pipeline
  • Confirm the folder is gone by running ls (macOS/Linux) or dir (Windows) on your Desktop. The project folder should no longer appear.

Nice Work!

Nice Work!

Nice work! You just built a complete MLOps-to-AIBOM pipeline that trains, tracks, registers, and documents an ML model from end to end.

You've learned how to:

  • Train a RandomForestClassifier on the wine dataset, track parameters, metrics, and artifacts in MLflow, and register the model with governance tags and version control.
  • Author a MODEL_CARD.md and aibom.toml to declare model identity, dependencies, and dataset references for structured project metadata.
  • Generate a standards-compliant CycloneDX 1.7 AIBOM (bom.cdx.json) capturing the full ML supply chain and interpret it with a custom inspection script.
  • Secret Mission: Added SHA-256 artifact hashes and dataset provenance for governance-gate-ready completeness.

Ready to quiz yourself?