> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish traditional ML predictions

> Monitor a traditional / tabular ML model by publishing its predictions to Openlayer — streamed as they happen or uploaded in batches.

For traditional ML systems (tabular classification, tabular regression, and other non-LLM models),
there is no LLM call to wrap, so the `@trace` decorator and provider wrappers used in
[Instrument your code](https://docs.openlayer.com/monitoring/instrument.md) do not apply. Instead, you publish your model's
**predictions** directly to an inference pipeline.

You can do this in two ways — pick the one that matches how your system runs:

* **Stream** predictions as they happen (online serving, one request at a time).
* **Batch** upload predictions periodically (a scheduled scoring job, or a backfill).

Both paths use the same **typed configuration** that describes how your columns map to Openlayer's
semantics. The config class depends on your project's [task type](https://docs.openlayer.com/development/openlayer-json.md).

> [!NOTE]
>
> **Prerequisites**:
>
> * A [project](https://docs.openlayer.com/workspace-and-projects/creating-and-loading-projects.md) with monitoring mode enabled,
>   and an [inference pipeline](https://docs.openlayer.com/monitoring/overview.md) in it.
>
> * An [Openlayer API key](https://docs.openlayer.com/workspace-and-projects/find-your-api-key.md).
>
> * The [Openlayer Python SDK](https://docs.openlayer.com/api-reference/sdk/overview.md) installed.

## Define the config for your task type

Import the config class matching your project's task type from
`openlayer.types.inference_pipelines.data_stream_params`:

```python Tabular classification theme={null}
from openlayer.types.inference_pipelines import data_stream_params

config = data_stream_params.ConfigTabularClassificationData(
    categorical_feature_names=["Gender", "Geography"],
    class_names=["Retained", "Exited"],
    feature_names=["CreditScore", "Geography", "Gender", "Age", "Balance"],
    predictions_column_name="predictions",          # model's predicted class index
    prediction_scores_column_name="prediction_scores",  # per-class probability lists (optional)
    label_column_name="Exited",                     # ground truth, when available
    inference_id_column_name="inference_id",         # enables later ground-truth updates
)
```

```python Tabular regression theme={null}
from openlayer.types.inference_pipelines import data_stream_params

config = data_stream_params.ConfigTabularRegressionData(
    feature_names=["age", "bmi", "bp", "s1", "s2", "s3"],
    predictions_column_name="predictions",   # model's predicted value
    target_column_name="target",             # ground truth, when available
    inference_id_column_name="inference_id",  # enables later ground-truth updates
)
```

The keys in your streamed rows (or the columns of your batch DataFrame) must exactly match the
`*_column_name` values declared in the config.

> [!WARNING]
>
> For tabular classification, send `class_names`, `feature_names`, **and**
> `categorical_feature_names` together — the server requires all three even
> though the SDK type hints mark only `class_names` as required. Pass
> `categorical_feature_names=[]` when your model has no categorical features;
> omitting it returns a `400 ... not valid under any of the given schemas`.

## Option A — Stream predictions as they happen

Use `client.inference_pipelines.data.stream(...)` to publish one or a few rows per call, right after
your model scores a request:

```python Python theme={null}
from openlayer import Openlayer

client = Openlayer(api_key="YOUR_OPENLAYER_API_KEY_HERE")

row = {
    "CreditScore": 600,
    "Geography": "France",
    "Gender": "Male",
    "Age": 40,
    "Balance": 100000,
    "predictions": 1,
    "prediction_scores": [0.15, 0.85],
    "inference_id": "a1b2c3",
}

client.inference_pipelines.data.stream(
    inference_pipeline_id="YOUR_INFERENCE_PIPELINE_ID_HERE",
    rows=[row],
    config=config,
)
```

## Option B — Upload predictions in batches

For a scheduled scoring job or a backfill, score a whole DataFrame and upload it at once with
`upload_batch_inferences`:

```python Python theme={null}
from openlayer import Openlayer
from openlayer.lib import data

data.upload_batch_inferences(
    client=Openlayer(api_key="YOUR_OPENLAYER_API_KEY_HERE"),
    inference_pipeline_id="YOUR_INFERENCE_PIPELINE_ID_HERE",
    dataset_df=df,   # columns must match the config's *_column_name values
    config=config,
)
```

## Add ground truth later

When the true labels/targets arrive after you have already published predictions, patch them in by
correlating on the `inference_id` you set above:

```python Python theme={null}
from openlayer.lib import data

data.update_batch_inferences(
    client=Openlayer(api_key="YOUR_OPENLAYER_API_KEY_HERE"),
    inference_pipeline_id="YOUR_INFERENCE_PIPELINE_ID_HERE",
    dataset_df=labels_df,  # must include the inference_id column + the label/target column
    config=config,         # config.inference_id_column_name must be set
)
```

See [Update production data](https://docs.openlayer.com/monitoring/updating-data.md) for more on delayed ground truth, and
[Upload a reference dataset](https://docs.openlayer.com/monitoring/uploading-reference-dataset.md) to add a baseline for data-drift
tests.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.