Version 1 structure, adapters and results
Normative source: docs/format-adapters.md. This page renders that file unchanged; the guides explain it, this text defines it.
Normative sections of the format contract.
structure.json
Composites add only the checked pipeline boundary sibling defined in pipelines; native indices address transformed features.
Structure is never embedded in the manifest. It is a JSON object with required
family and feature_importances fields, plus exactly the family fields below.
feature_importances is null or an ordered numeric array of length n_features.
Numbers use the canonical encoding, including non-finite tags. Arrays and tree
lists preserve native order; no pruning, rounding, flattening of output/class
dimensions or display-oriented simplification is permitted. Sklearn structures
add required state_sha256 and prediction_state fields; XGBoost adds required
native_model. See prediction state for their exact rules.
family |
Additional required fields |
|---|---|
decision_tree |
tree: one tree object. |
tree_ensemble |
estimator_shape: original shape of the ensemble’s estimator collection; trees: row-major flattened list of tree objects. Product of shape equals list length. A one-dimensional collection uses [N]. |
linear |
coef, intercept: native numeric arrays/scalars converted without reshaping. |
xgboost |
trees: ordered list of parsed JSON tree dumps, each from get_dump(dump_format="json", with_stats=True). Retain all native fields, including node IDs, split/leaf values, missing-child IDs, gain/cover and children. Do not embed dump strings. |
catboost |
native_model: complete numeric symmetric-tree arrays, feature borders/NaN routing, scale/bias and repacked splits defined in CatBoost. |
feature_importances |
No additional fields; the importance array must be non-null. |
A scikit-learn tree object has exactly node_count (positive integer),
max_depth (nonnegative integer, verified from reachability),
n_features (positive integer), n_outputs (positive integer), n_classes
(native per-output class-count array), and these native tree_ arrays:
children_left, children_right, feature, threshold, value, impurity,
n_node_samples, weighted_n_node_samples, missing_go_to_left. Except value, each array’s length
is node_count; value retains its native [node, output, class/value] shape.
Preserve leaf and undefined-feature sentinel values, and sklearn’s native value
normalization for the installed version. Do not reinterpret value as counts.
Trees use each constituent tree’s own dimensions, not an ensemble-wide guess.
Select family in this precedence: a single tree_, a tree ensemble, linear
coefficients/intercept, then feature importances only. An ensemble exposing
unsupported non-tree constituents must not masquerade as a supported tree
ensemble. Unsupported or oversized mandatory structure fails export rather than
being silently omitted. ONNX graph visualizations and additional structure
families require a deliberate future contract change.
All-scikit-learn Pipeline returns null structure in version 1. Its final estimator
operates on transformed features; version 1 has no input transformation mapping,
so no final-step tree or coefficient member is emitted. Claims, names and
predictions still describe the complete pipeline; model.state_sha256 also
binds every fitted step and transform. Standalone DecisionTree,
RandomForest, ExtraTrees, GradientBoosting, LogisticRegression, LinearRegression
and Ridge retain their native structures as specified above. HistGradientBoosting
is currently rejected: its TreePredictor storage is not on mlbundle’s trust
allowlist. No approximate trees are emitted for it.
Adapter and result contracts
The internal Adapter protocol in mlbundle.adapters owns recognition,
export preparation, serialization, loading, claim extraction, structure
extraction and a Predictor. prepare_export(model, ExportDeclarations) returns
the object to serialize and extract, leaving the caller’s model unchanged.
ExportDeclarations is a strict frozen record with class_labels only. Adapters
bind or reject declarations according to their native contracts; neither
deserialize nor extraction receives manifest fields. The shared pipeline
resolves labels only when extraction returns null labels for a classifier.
The registry imports only the four constant mlbundle module paths _sklearn,
_xgboost, _catboost, _onnx, and constructs their module’s Adapter class. Library
imports must be lazy, with dependency failures naming the extra to install.
Missing adapter modules raise AdapterNotImplementedError on load; export skips
unimplemented adapters while looking for one that recognizes its input. A missing
optional dependency reports pip install "mlbundle[EXTRA]". An unrecognized
object raises UnsupportedModelError.
export(model, destination, *, training_data=None, feature_names=None, class_labels=None, target_names=None, sample_rows=None, onehot_separator="__", metadata=None, overwrite=False, limits=None)
returns a MetadataView of the written
bundle. It serializes, extracts, reloads and compares both claims and structure
before writing. Model-exposed names take precedence over supplied names. It
captures distribution metadata, including claims.onnx in the environment for
the ONNX adapter. Required schema export owns local/default inference, frame alignment,
training dtypes, schema/report declarations and explicit sample selection.
load(source, *, limits=None) validates the container, resolves its closed enum,
deserializes, recomputes claims/structure and compares canonical bytes before
constructing a LoadedBundle. ClaimMismatchError identifies the claim without
echoing values; absent/present structure disagreements are checked separately,
including a present JSON null. Malformed native payloads raise InvalidModelError,
an MLBundleError subclass, without native exception text.
The sklearn adapter uses only skops.io.dumps/loads. Its fixed supplemental
trusted list includes sklearn.tree._tree.Tree,
sklearn.neural_network._stochastic_optimizers.AdamOptimizer,
sklearn.neural_network._stochastic_optimizers.SGDOptimizer, numpy.dtype and
the exact score/NumPy function names in the state contract.
It adds no arbitrary class, callable or I/O constructor.
Skops’ own defaults remain part of the dependency trust boundary. Dumped bytes
are audited with get_untrusted_types; every type outside the list is rejected
at export and independently at load. Exact outer Pipelines ending in a supported
XGBoost/CatBoost wrapper use the composite route: prefix-only
skops, native leaf bytes through the unchanged preflight/adapter, and closed Pipeline
reconstruction. Transformer parameters/state, data-free widths/names, leaf claims
and extended native structure are recomputed. Other native placements fail with
bounded unsupported types. Native adapters never receive skops-contained booster
state, and their trusted types stay excluded. All-sklearn pipelines stay unchanged.
MLPClassifier/MLPRegressor support adam, sgd and lbfgs, alone or in a Pipeline.
MLP structure is null; layer weights and complete fitted/optimizer state are
bound by the manifest state commitment. Optimizers permit ObjectNode only
(plus shared CachedNode references), never a constructor/reduce loader.
The nested skops archive is bounded before parsing; every schema-referenced
numeric array header is checked regardless of filename suffix. Headers must
match the stored byte sizes and cannot use object-array loading or zero-itemsize
dtypes. Tree constructor dimensions/class counts and state-array shapes are
bounded before native construction. Loaded tree dimensions, child/feature indices,
reachability and depth are checked before native feature-importance traversal or
prediction, and input/output/class counts must agree with owning estimators.
Closed state, shared-reference work and sparse-array storage rules are specified
in prediction state and apply identically to export and load.
Ensemble feature mappings must address the parent input width. Every loaded root object is
checked again for classifier/regressor identity, fitted state and probability
support. Multilabel classifiers with independent positive-class columns and
classifier chains are rejected because their probabilities cannot satisfy the
output-major contract; ordinary multi-output probability-matrix lists are supported.
Implemented XGBoost mapping
Only exact XGBClassifier and XGBRegressor classes are supported. Subclasses,
ranking, survival, custom objectives, raw Booster, and the deprecated
XGBRFClassifier/XGBRFRegressor wrappers are rejected. Raw boosters do not
carry enough wrapper identity or custom-objective history for this narrow
training-side boundary; RF wrappers add no required v0.1 contract. Supported
classifier objectives are binary:logistic and multi:softprob. Supported
regression objectives are reg:squarederror, reg:absoluteerror,
reg:pseudohubererror, reg:squaredlogerror, reg:logistic, count:poisson,
reg:gamma, and reg:tweedie. Only one target is supported. Binary classifiers
return two probability columns; softmax-only objectives are rejected.
Only native gbtree storage is admitted. In XGBoost 3.4.1, the deprecated dart
option uses gbtree storage with dropout; that representation is supported and
all native dump statistics are retained. Legacy native dart storage is rejected.
gblinear is rejected: its multi-class coefficient/importance dimensions and
deprecated format are outside the version 1 tree contract. There is no silently
omitted structure for an admitted XGBoost model.
Serialization is exclusively bytes(booster.save_raw(raw_format="ubj")) and
load_model(bytearray(data)), in memory. Class selection uses the closed adapter
map and the mlbundle booster attribute, never the manifest. That attribute is
exactly { "estimator_class": CLASS, "class_labels": LABELS_OR_NULL, "prediction": SETTINGS } encoded canonically. SETTINGS has exactly missing
(a finite binary64 value or the canonical NaN tag), importance_type (one of
gain, weight, cover, total_gain, total_cover; omitted native option
normalizes to gain), and enable_categorical (boolean). These settings survive
loading and are compared with the wrapper. The only other allowed booster keys
are best_iteration, best_score, scikit_learn; they are bounded and included
in verified hyperparameters. Prediction respects the verified best iteration.
hyperparameters has exactly these five keys:
{ "learner_model_param": {"base_score":"[5E-1]","boost_from_average":"1","num_class":"0","num_feature":"4","num_target":"1"}, "objective": {"name":"binary:logistic","reg_loss_param":{"scale_pos_weight":"1"}}, "gradient_booster": {"name":"gbtree","gbtree_model_param":{"num_parallel_tree":"1","num_trees":"3"}}, "prediction": {"missing":{"$float":"nan"},"importance_type":"gain","enable_categorical":true}, "attributes": {}}The first three records are a projection of the parsed native save_config():
retain every field in learner_model_param, objective, and
gradient_booster.gbtree_model_param, plus the booster name, with native value
types/spellings unchanged. prediction is the bound wrapper settings record.
attributes contains the allowed native attributes other than mlbundle.
Neither get_params() nor the whole config survives native model I/O. Training
settings such as learning rate, max depth, seed, threads and metrics are therefore
not claimed or reconstructed. Export compares this projection and the complete
structure before and after making the native copy, and again through the shared
serialization round trip. Unsupported unrecoverable claims fail publication.
Booster feature names and feature types become verified claims; absent values
are null. Structure is exactly the xgboost family above, with the wrapper’s
native float32-normalized importance vector for the bound importance type and
the complete native_model record, including routing and dropout state.
Prediction values and structure scalars promote float32 exactly to binary64.
Claim/structure comparisons use exact canonical bytes; probability row-sum
validation allows rtol=1e-6, atol=1e-7 for float32 arithmetic.
Before native parsing, the adapter bounds bytes, recognizes the native UBJSON prefix, validates the complete bounded document, and checks feature/class/target, tree and node-array dimensions, output-group indices, iteration pointers and child/feature indices. Numeric arrays remain byte views. At most 100,000 trees are supported; reachable tree depth is at most 14 so the complete nested dump fits the existing JSON depth ceiling of 32. Cycles, aliases and unreachable nodes are rejected. Native dump generation is count-bounded; total dump bytes and canonical structure bytes must also fit 256 MiB and caller-selected lower limits. No truncation is permitted. These checks do not sandbox the native parser or cap its internal allocations.
Predictors accept adapter-supported tabular objects without a core pandas/numpy
dependency. Regressors return list[float] for a single output or row-major
list[list[float]] for multiple outputs. Classifiers return row-major
list[list[float]] for one output or an output-major list of those matrices for
multiple outputs, aligned with class labels. Validate probability dimensions,
finite probabilities, range and row sums in the adapter. Classifier prediction
uses predict_proba; regressor prediction uses predict. Unsupported probability
or regression contracts fail rather than synthesizing outputs. Inputs must have
rank two and the native feature width. Numeric inputs reject strings, complex
values and infinities; sklearn/XGBoost permit NaN for native missing-value handling.
A sklearn Pipeline with a fitted OneHotEncoder/OrdinalEncoder/TargetEncoder also admits strings,
including fitted clones inside nested ColumnTransformer/FeatureUnion steps;
categorical XGBoost DataFrame columns retain native recoding. ONNX requires finite
numeric cells as specified separately. Input and remaining native failures become
bounded MLBundleError subclasses without cell contents. Shape errors additionally
retain ValueError compatibility through PredictionInputError.
MetadataView carries manifest, a tuple of MemberName values including
the manifest, required schema and optional samples JSON values; it has no model.
LoadedBundle additionally carries the exact
native object, a uniform predictor, required schema and optional structure/samples, and
properties for kind, library, class name, ordered feature names and origin, and
output-major original class labels. Calling the prediction method for the wrong
kind raises a bounded ValueError. Result records and typed models prevent field
reassignment, but nested JSON values and the native model are not deeply frozen.
Construction itself performs no model validation; load supplies the loaded-result
guarantee. Consumers must not mutate a validated
model and continue treating its original metadata as verified.
