Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

FlexPredict

FlexPredict is a small, composable inference layer for tabular machine-learning models. It gives NumPy, scikit-learn-compatible, PyTorch, TensorFlow/Keras and custom models a consistent input and output contract, while allowing every model to keep its own schema and preprocessing pipeline.

FlexPredict 0.2 is an alpha redesign. It intentionally does not preserve the old unipredict API.

Why FlexPredict?

Application inputs rarely look exactly like a model matrix. A service may receive one JSON object, a dictionary of columns or a list of records, while different models in an ensemble require different normalization, devices and output handling. FlexPredict composes those concerns without becoming a training framework or a model server.

Core properties:

  • zero-configuration inference for array inputs;
  • named and schema-validated tabular inputs;
  • callable, sklearn-style and built-in preprocessing;
  • canonical prediction results with shape (n_samples, n_outputs);
  • lazy optional dependencies for PyTorch and TensorFlow;
  • safe regression and classification ensembles;
  • global ensemble weights from lists, arrays or .npy files;
  • a registry for custom inference engines.

Installation

Development install:

git clone https://github.com/NewMrPotato/ml-flex-predict.git
cd ml-flex-predict
pip install -e .

Optional frameworks:

pip install -e ".[sklearn]"
pip install -e ".[torch]"
pip install -e ".[tensorflow]"
pip install -e ".[pandas]"
pip install -e ".[dev]"

The base package depends only on NumPy. Importing flexpredict does not import or require PyTorch, TensorFlow, pandas or scikit-learn.

Quick start

Array input with no configuration

from flexpredict import Predictor

predictor = Predictor(model)
result = predictor.predict([[1.0, 2.0], [3.0, 4.0]])

print(result.values)       # always a 2D NumPy array
print(result.task)         # inferred for sklearn-compatible estimators

Output contract

PredictionResult.values is always a non-empty two-dimensional array. FlexPredict rejects non-finite numeric output and applies the following dtype rules:

Output kind Supported values
Regression or generic values finite real integers and floats
probabilities finite real numbers between 0 and 1
logits finite real numbers
Classification labels strings, booleans and finite real numbers

Complex numbers and arbitrary Python objects such as dictionaries, nested lists and None are rejected. A custom classifier returning string labels should declare its semantics when they cannot be inferred:

predictor = Predictor(
    custom_classifier,
    task="classification",
    output_kind="labels",
)

Named input

predictor = Predictor(model, features=["age", "income", "score"])

result = predictor.predict({
    "age": 31,
    "income": 150_000,
    "score": 0.82,
})

print(result.single())

The same predictor accepts a dictionary of columns or a list of records:

predictor.predict({
    "age": [31, 45],
    "income": [150_000, 90_000],
    "score": [0.82, 0.61],
})

predictor.predict([
    {"age": 31, "income": 150_000, "score": 0.82},
    {"age": 45, "income": 90_000, "score": 0.61},
])

features=[...] is a feature-selection shortcut: declared features are required, selected from a named input and passed to the model in the declared order; unrelated fields are ignored. This allows one shared request to feed ensemble members with different feature subsets. Use an explicit InputSchema when extra fields must be rejected.

Pandas DataFrame input remains a DataFrame for generic and sklearn-compatible models. Column names, their declared order and pandas dtypes are preserved, so sklearn pipelines using ColumnTransformer with named columns or dtype selectors continue to work. PyTorch and TensorFlow engines convert DataFrame input to a NumPy matrix before tensor inference.

Schema-aware input

Use an explicit schema only when validation is useful:

from flexpredict import FeatureSpec, InputSchema, Predictor

schema = InputSchema((
    FeatureSpec("age", int, validators=(lambda value: 18 <= value <= 120,)),
    FeatureSpec("income", float, validators=(lambda value: value >= 0,)),
    FeatureSpec("score", float, default=0.5),
))

predictor = Predictor(model, schema=schema)
result = predictor.predict({"age": "31", "income": 150_000})

By default, values are coerced to their declared types and extra fields are rejected for an explicit schema. Errors identify the invalid or missing feature before the model is called.

Preprocessing

FlexPredict accepts a callable or an object with transform(X):

from flexpredict import Predictor, Standardizer

predictor = Predictor(
    model,
    features=["x1", "x2", "x3"],
    preprocessor=Standardizer(
        mean=[10.0, 20.0, 30.0],
        std=[2.0, 5.0, 10.0],
    ),
)

Each member of an ensemble can use a different schema and preprocessor.

Ensembles and weights files

aggregation_weights are coefficients controlling the contribution of each model. They are not the trained parameters inside a neural network.

from flexpredict import EnsemblePredictor

ensemble = EnsemblePredictor(
    predictors=[predictor_a, predictor_b, predictor_c],
    aggregation="weighted_mean",
    aggregation_weights="models/global_weights.npy",
)

result = ensemble.predict(data)

Weights files are loaded with np.load(path, allow_pickle=False). The array must be one-dimensional, contain one finite non-negative weight per predictor and have a positive sum. Weights are normalized automatically and exposed as the read-only ensemble.aggregation_weights array in predictor order. A Python list or NumPy array can be passed instead of a file path.

Every member receives the original input independently, so its schema can select and order features for that model. Members must still agree on task, output kind, batch shape and classification classes before aggregation. Errors identify the failing member by index and name. Majority-vote ties are deterministic: the prediction from the earliest member wins.

Loading model artifacts

Complete sklearn/joblib and Keras artifacts can be turned into predictors directly:

sklearn_predictor = Predictor.from_file(
    "models/random_forest.joblib",
    features=["age", "income", "score"],
)

keras_predictor = Predictor.from_file(
    "models/network.keras",
    features=["age", "income", "score"],
)

PyTorch state dicts need a model factory because weights alone do not describe the Python architecture:

torch_predictor = Predictor.from_torch_weights(
    "models/network_weights.pth",
    model_factory=lambda: MyNetwork(input_size=3),
    features=["age", "income", "score"],
    task="regression",
    engine_options={"device": "cuda", "dtype": "float32"},
)

PyTorch weights are loaded with weights_only=True. A complete trusted PyTorch model can be loaded explicitly with loader="torch_model"; .pt and .pth are never guessed because the same extensions are commonly used for incompatible artifact types.

Supported regression aggregations are mean, weighted_mean, median, min, max and a callable. Classification probabilities can be averaged after verifying class metadata. Class labels require the explicit voting strategy.

Classification

predictor = Predictor(
    classifier,
    features=["age", "income", "score"],
    task="classification",  # inferred for sklearn classifiers
)

labels = predictor.predict(data)
probabilities = predictor.predict_proba(data)

print(labels.values)
print(probabilities.values)
print(probabilities.classes)

For a PyTorch or Keras model whose regular forward output already contains probabilities, configure output_kind="probabilities".

Engine options

Framework-specific runtime settings stay grouped and optional:

predictor = Predictor(
    torch_model,
    features=["x1", "x2", "x3"],
    task="regression",
    engine_options={
        "device": "cuda",
        "dtype": "float32",
    },
)

Unknown engine options are rejected instead of being silently ignored.

Custom InferenceEngine implementations receive NumPy input by default. An engine that intentionally supports pandas semantics can declare preserves_dataframe = True.

Development

python -m pytest
python -m pytest --cov=flexpredict --cov-fail-under=85
python -m ruff check flexpredict tests examples
python -m mypy flexpredict
python -m compileall -q flexpredict tests examples
python -m build

The fast unit suite requires only NumPy and pytest. Framework integration suites are kept separate so that schema and ensemble tests do not require heavy optional packages. The current fast suite enforces at least 85% coverage; this threshold will increase as the optional-framework integration matrix grows.

GitHub Actions additionally installs and tests the sklearn, torch and tensorflow extras in isolated Python 3.11 jobs.

See the contribution guide for the development workflow and the release checklist for the alpha-release gate.

Security

Only load model artifacts and preprocessing objects from trusted sources. Python pickle, joblib and some framework serialization formats may execute code while loading. NumPy ensemble-weight files are always loaded with pickle support disabled.

License

MIT

Metadata

Release files for flexpredict 0.2.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for flexpredict 0.2.0a1
File Size Uploaded
flexpredict-0.2.0a1.tar.gz 33.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flexpredict 0.2.0a1
File Interpreter ABI Platform
flexpredict-0.2.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 57.3 kB

Release files / flexpredict-0.2.0a1.tar.gz

Download URL flexpredict-0.2.0a1.tar.gz
Size 33.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e1d888ef35161299d6e9e963145303525225e4c3ea32da9cce981c4ffa660224
BLAKE2b-256 checksum
How to use checksums
ef7594387508f4af5cdc3c8c0dd3e4409282f1100ede0479617c1fdec4e25240
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 3, 2026.

Transparency log

Release files / flexpredict-0.2.0a1-py3-none-any.whl

Download URL flexpredict-0.2.0a1-py3-none-any.whl
Size 23.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
12b5459b10385ada391a3f7eabf1ba22d7c4eb5d79d8b4e581d8bb386c19b872
BLAKE2b-256 checksum
How to use checksums
31ae0da9426a8921f1d93b88279f5d37db1f126ec5c7d7d377ac52880b720499
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0a1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page