# Python API reference

Every public class and function, grouped by task.

Most names import from the top-level package: `from paperlab_space import run`. Each entry shows its import path.

## Running

Run an evaluation and estimate its cost.

### run

```python
from paperlab_space import run

def run(
    config: RunConfig,
    data: Dataset | MappedDataset | None = None,
    *,
    backend: Backend | None = None,
    on_progress: ProgressCallback | None = None,
    previous: RunResults | None = None,
    checkpoint: str | Path | None = None,
) -> RunResults
```

Run an evaluation and return outputs and scores.

Works in scripts and in Jupyter (where an event loop is already running).
See `arun` for the arguments.

### arun

```python
from paperlab_space import arun

async def arun(
    config: RunConfig,
    data: Dataset | MappedDataset | None = None,
    *,
    backend: Backend | None = None,
    on_progress: ProgressCallback | None = None,
    previous: RunResults | None = None,
    checkpoint: str | Path | None = None,
    sleep: Sleep = asyncio.sleep,
    clock: Clock = time.monotonic,
) -> RunResults
```

Run an evaluation asynchronously.

**Arguments**

- `config`: What to run.
- `data`: The data. If omitted, it is loaded from `config.dataset`. A plain `Dataset` is mapped with `config.mapping`.
- `backend`: Where to send requests. Defaults to `LiteLLMBackend`.
- `on_progress`: Called after each row finishes.
- `previous`: Results of an earlier, interrupted run with the same config. Rows that already have an output are reused instead of requested again.
- `checkpoint`: A `results.json` path to save finished rows to while the run goes, and when it stops early (Ctrl+C, an error). Pass the file as `previous` to continue. When the run finishes, the full results are written there.
- `sleep, clock`: Injection points for tests.

### estimate

```python
from paperlab_space import estimate

def estimate(
    config: RunConfig,
    data: Dataset | MappedDataset,
    *,
    backend: Backend | None = None,
    latency_s: float = 3.0,
) -> Estimate
```

Estimate tokens, cost, and wall time for each model.

Token counts use a script-aware heuristic, not the model's tokenizer, so treat them
as ballpark figures. `latency_s` is the assumed time per request.

### Estimate

```python
from paperlab_space.runner import Estimate

Estimate(models: list[ModelEstimate])
```

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `models` | `list[ModelEstimate]` | required |  |

**Properties**

- `cost_usd` (`float | None`)
- `minutes` (`float`): Models run in parallel, so the slowest one sets the total.

### Progress

```python
from paperlab_space import Progress

Progress(
    model: str,
    done: int,
    failed: int,
    total: int,
    record_id: str,
    row: RowOutput | None = None,
)
```

A progress update, sent after each row finishes.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `model` | `str` | required | The model's label. |
| `done` | `int` | required | Rows finished for this model, successful or not. |
| `failed` | `int` | required | Rows that failed so far for this model. |
| `total` | `int` | required | Rows to run for this model. |
| `record_id` | `str` | required | The row that just finished. |
| `row` | `RowOutput \| None` | `None` | Its output, so callers can checkpoint partial results (see `previous` in `arun`). |

### LiteLLMBackend

```python
from paperlab_space import LiteLLMBackend

LiteLLMBackend(
    api_keys: Mapping[str, str] | None = None,
    api_bases: Mapping[str, str] | None = None,
)
```

Calls models through [LiteLLM](https://docs.litellm.ai).

API keys are read from the usual environment variables (`OPENROUTER_API_KEY`,
`GEMINI_API_KEY`, `OPENAI_API_KEY`, ...) unless passed in `api_keys`, keyed by
provider prefix (e.g. `{"openrouter": "sk-or-..."}`). Keys are never stored in the
run config or results.

**`complete`**

```python
async def complete(
    model: ModelSpec,
    messages: Messages,
    *,
    seed: int | None,
    timeout: float,
) -> Completion
```

**`price`**

```python
def price(model: ModelSpec) -> tuple[float, float] | None
```

## Configuration

The run config and its parts. See the [config file reference](https://paperlab.space/docs/reference/config.md) for the YAML form.

### RunConfig

```python
from paperlab_space import RunConfig
```

Everything needed to run (and re-run) an evaluation.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `dataset` | `DatasetSpec \| None` | `None` | Where to load the data from. Optional when the data is passed to `run` directly. |
| `mapping` | `ColumnMapping` | required | Which columns are inputs, context, the answer key, and the row ID. |
| `prompt` | `PromptTemplate` | required | The prompt template: a string, or an object with `user` and `system`. |
| `models` | `list[ModelSpec]` | required | Models to evaluate, as IDs or objects with options. |
| `metrics` | `list[MetricSpec]` | `[]` | Metrics to compute, as names or objects with options. Empty collects outputs only. |
| `seed` | `int \| None` | `None` | Default seed for models that do not set their own. |
| `concurrency` | `int` | `4` | Parallel requests per model (rate limits still apply). |
| `retry` | `RetrySettings` | `RetrySettings()` | Retry and timeout settings. |

**`model_seed`**

```python
def model_seed(model: ModelSpec) -> int | None
```

**`from_dict`** (classmethod)

```python
def from_dict(data: dict[str, Any]) -> RunConfig
```

Build from a dict; also accepts a reproducibility file (config under `config`).

**`from_yaml`** (classmethod)

```python
def from_yaml(path: str | Path) -> RunConfig
```

Load a YAML config. A relative dataset path is resolved against the file's folder.

**`to_dict`**

```python
def to_dict() -> dict[str, Any]
```

**`to_yaml`**

```python
def to_yaml(path: str | Path | None = None) -> str
```

### ModelSpec

```python
from paperlab_space import ModelSpec
```

A model to evaluate, addressed by its LiteLLM ID (`provider/model`).

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `str` | required | LiteLLM model ID with its provider prefix, e.g. `gemini/gemini-3.5-flash`. |
| `label` | `str \| None` | `None` | Display name in tables. Defaults to the model ID without the provider prefix. |
| `temperature` | `float` | `0.0` | Sampling temperature. 0 gives the most repeatable outputs. |
| `max_tokens` | `int \| None` | `None` | Cap on output tokens per response. |
| `top_p` | `float \| None` | `None` | Nucleus sampling cutoff. |
| `seed` | `int \| None` | `None` | Overrides the run-level `seed` for this model. |
| `requests_per_minute` | `float \| None` | `None` | Override the default rate limit for this model. |
| `concurrency` | `int \| None` | `None` | Override the run-level number of parallel requests for this model. |
| `params` | `dict[str, Any]` | `{}` | Extra provider parameters passed through to LiteLLM. |

**Properties**

- `provider` (`str`)
- `name` (`str`): The label used in outputs.

### MetricSpec

```python
from paperlab_space import MetricSpec
```

A metric name plus its options, e.g. `{"name": "exact_match", "ignore_case": false}`.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `str` | required | A registered metric name; `paperlab-space metrics` lists them. |
| `label` | `str \| None` | `None` | Column name in outputs; needed to use the same metric twice with different options. |
| `params` | `dict[str, Any]` | `{}` | Metric options such as `labels` or `ignore_case`. In YAML they can also sit next to `name`. |

**Properties**

- `key` (`str`)

### DatasetSpec

```python
from paperlab_space import DatasetSpec
```

Where the data comes from. Optional when the dataset is passed in directly.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `path` | `str` | required | Data file. A relative path is resolved against the config file's folder. |
| `sheet` | `str \| int \| None` | `None` | Excel sheet name or zero-based index. Defaults to the first sheet. |

### RetrySettings

```python
from paperlab_space import RetrySettings
```

How failed requests are retried, and how long to wait for a response.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `max_attempts` | `int` | `5` | Tries per row, including the first. |
| `base_delay` | `float` | `2.0` | Seconds before the first retry; doubles on each attempt. |
| `max_delay` | `float` | `60.0` | Upper bound on the wait between retries, in seconds. |
| `timeout` | `float` | `120.0` | Seconds to wait for one model response. |

### ColumnMapping

```python
from paperlab_space import ColumnMapping
```

Which columns are inputs, extra context, the expected answer, and the row ID.

Input and context columns become variables in the prompt template. The expected
column is never exposed to the prompt, so answers cannot leak into it.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `input` | `list[str]` | required | Columns available to the prompt template. One name or a list. |
| `context` | `list[str]` | `[]` | More columns available to the prompt, such as metadata. |
| `expected` | `str \| None` | `None` | The answer key that outputs are scored against. Never available to the prompt. |
| `id` | `str \| None` | `None` | A stable row ID. Defaults to the 1-based row number. |
| `answer_delimiter` | `str \| None` | `None` | Split the expected cell on this string to allow several valid answers, e.g. `"\|"`. |

**Properties**

- `fields` (`list[str]`): Columns available to the prompt template.
- `used_columns` (`list[str]`)

### PromptTemplate

```python
from paperlab_space import PromptTemplate

PromptTemplate(user: str | None = None, /, **data: Any)
```

A user prompt (and optional system prompt) with column placeholders.

```python
>>> PromptTemplate(user="Translate to English: {sentence}").render({"sentence": "สวัสดี"})
[{'role': 'user', 'content': 'Translate to English: สวัสดี'}]
```

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `user` | `str` | required | The user message, with `{column}` placeholders. |
| `system` | `str \| None` | `None` | An optional system message, with the same placeholders. |

**Properties**

- `variables` (`list[str]`): Placeholder names in order of first appearance (user prompt, then system).

**`check`**

```python
def check(columns: Iterable[str]) -> None
```

Raise `TemplateError` if a placeholder is not one of `columns`.

**`render`**

```python
def render(fields: Mapping[str, str | None]) -> list[dict[str, str]]
```

Fill the template and return chat messages. Empty cells become empty strings.

## Data

Load, map, and validate spreadsheet data.

### load_dataset

```python
from paperlab_space import load_dataset

def load_dataset(path: str | Path, sheet: str | int | None = None) -> Dataset
```

Load an `.xlsx` or `.csv`/`.tsv` file.

The first non-empty row is used as the header. Fully empty rows are dropped.

**Arguments**

- `path`: File to load.
- `sheet`: Excel sheet name or zero-based index. Defaults to the first sheet. Ignored for CSV files.

### list_sheets

```python
from paperlab_space import list_sheets

def list_sheets(path: str | Path) -> list[str]
```

Return the sheet names of an Excel file (a CSV file has a single unnamed sheet).

### Dataset

```python
from paperlab_space import Dataset

Dataset(
    columns: list[str],
    rows: list[Row],
    source: str | None = None,
    sheet: str | None = None,
    sha256: str | None = None,
    encoding: str | None = None,
    warnings: list[str] = [],
)
```

A rectangular table of string cells with named columns.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `columns` | `list[str]` | required |  |
| `rows` | `list[Row]` | required |  |
| `source` | `str \| None` | `None` |  |
| `sheet` | `str \| None` | `None` |  |
| `sha256` | `str \| None` | `None` |  |
| `encoding` | `str \| None` | `None` |  |
| `warnings` | `list[str]` | `[]` |  |

**`column`**

```python
def column(name: str) -> list[str | None]
```

Return all values of one column.

**`map`**

```python
def map(mapping: ColumnMapping) -> MappedDataset
```

Apply a column mapping, producing records ready for prompting and scoring.

**`from_records`** (classmethod)

```python
def from_records(
    records: Sequence[dict[str, Any]],
    source: str | None = None,
) -> Dataset
```

Build a dataset from a list of dicts (column order follows first appearance).

### MappedDataset

```python
from paperlab_space import MappedDataset

MappedDataset(
    records: list[Record],
    mapping: ColumnMapping,
    dataset: Dataset,
)
```

A dataset with a column mapping applied, ready for prompting and scoring.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `records` | `list[Record]` | required |  |
| `mapping` | `ColumnMapping` | required |  |
| `dataset` | `Dataset` | required | The unmapped data. |

**Properties**

- `has_answer_key` (`bool`)

**`from_dataset`** (classmethod)

```python
def from_dataset(dataset: Dataset, mapping: ColumnMapping) -> MappedDataset
```

**`validate`**

```python
def validate() -> ValidationReport
```

Check the data for empty cells, duplicate IDs, and encoding problems.

**`preview`**

```python
def preview(n: int = 5) -> list[dict[str, Any]]
```

The first `n` records as JSON-ready dicts.

**`to_jsonl`**

```python
def to_jsonl(path: str | Path | None = None) -> str
```

Serialise all records as JSON Lines (UTF-8, non-ASCII kept as-is).

### Record

```python
from paperlab_space.data import Record

Record(
    id: str,
    fields: dict[str, str | None],
    expected: list[str] | None,
    row_number: int,
)
```

One evaluation example.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `str` | required | The row ID, or the row number if the mapping has no ID column. |
| `fields` | `dict[str, str \| None]` | required | Input and context values by column name. |
| `expected` | `list[str] \| None` | required | Valid answers, or `None` without an answer key entry. |
| `row_number` | `int` | required | 1-based position among the data rows (header excluded). |

**`to_json`**

```python
def to_json() -> dict[str, Any]
```

### ValidationReport

```python
from paperlab_space.data import ValidationReport

ValidationReport(issues: list[Issue], n_records: int)
```

The result of validating mapped data.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `issues` | `list[Issue]` | required |  |
| `n_records` | `int` | required |  |

**Properties**

- `ok` (`bool`): `True` if there are no errors (warnings are allowed).
- `errors` (`list[Issue]`)
- `warnings` (`list[Issue]`)

### Issue

```python
from paperlab_space.data import Issue

Issue(
    level: Level,
    code: str,
    message: str,
    rows: list[int] = [],
    column: str | None = None,
)
```

One problem found in the data.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `level` | `Level` | required | `"error"` blocks a run; `"warning"` does not. |
| `code` | `str` | required | A stable identifier, e.g. `empty_input` or `duplicate_id`. |
| `message` | `str` | required |  |
| `rows` | `list[int]` | `[]` | 1-based data row numbers (header excluded). |
| `column` | `str \| None` | `None` | The column the problem is in, if any. |

## Results

What a run returns.

### RunResults

```python
from paperlab_space import RunResults

RunResults(
    config: RunConfig,
    data: MappedDataset,
    runs: dict[str, ModelRun],
    scores: dict[str, dict[str, MetricResult]] = {},
    started_at: str | None = None,
    finished_at: str | None = None,
    environment: dict[str, str] = ...,
)
```

Everything a run produced: outputs, scores, and the config and data behind them.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `config` | `RunConfig` | required |  |
| `data` | `MappedDataset` | required | The data the run used, with the mapping applied. |
| `runs` | `dict[str, ModelRun]` | required | Model name -> outputs, in config order. |
| `scores` | `dict[str, dict[str, MetricResult]]` | `{}` | Model name -> metric name -> result. |
| `started_at` | `str \| None` | `None` | ISO 8601 timestamp (UTC). |
| `finished_at` | `str \| None` | `None` | ISO 8601 timestamp (UTC). |
| `environment` | `dict[str, str]` | `...` | Python, paperlab-space, and LiteLLM versions. |

**Properties**

- `model_names` (`list[str]`)

**`output`**

```python
def output(model: str, record_id: str) -> RowOutput
```

**`to_dict`**

```python
def to_dict() -> dict[str, Any]
```

**`save_json`**

```python
def save_json(path: str | Path) -> None
```

**`from_dict`** (classmethod)

```python
def from_dict(data: dict[str, Any]) -> RunResults
```

**`load_json`** (classmethod)

```python
def load_json(path: str | Path) -> RunResults
```

### ModelRun

```python
from paperlab_space import ModelRun

ModelRun(model: ModelSpec, rows: list[RowOutput])
```

One model's outputs for every row.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `model` | `ModelSpec` | required |  |
| `rows` | `list[RowOutput]` | required | One output per data row, in data order. |

**Properties**

- `name` (`str`)
- `n_failed` (`int`)
- `cost_usd` (`float | None`)
- `input_tokens` (`int`)
- `output_tokens` (`int`)
- `model_versions` (`list[str]`)

### RowOutput

```python
from paperlab_space import RowOutput

RowOutput(
    record_id: str,
    output: str | None,
    error: str | None = None,
    attempts: int = 0,
    latency_s: float | None = None,
    input_tokens: int | None = None,
    output_tokens: int | None = None,
    cost_usd: float | None = None,
    model_version: str | None = None,
)
```

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `record_id` | `str` | required | The ID of the data row. |
| `output` | `str \| None` | required | The model's answer, or `None` if every attempt failed. |
| `error` | `str \| None` | `None` | Why the request failed, if it did. |
| `attempts` | `int` | `0` | Requests sent for this row, including retries. |
| `latency_s` | `float \| None` | `None` | Seconds the successful request took. |
| `input_tokens` | `int \| None` | `None` |  |
| `output_tokens` | `int \| None` | `None` |  |
| `cost_usd` | `float \| None` | `None` | Cost in USD, if the provider's price is known. |
| `model_version` | `str \| None` | `None` | The exact model name the provider reported. |

**Properties**

- `ok` (`bool`)

### MetricResult

```python
from paperlab_space import MetricResult

MetricResult(
    name: str,
    value: float | None,
    per_row: list[float | None] | None = None,
    extra: dict[str, float] = {},
    n_scored: int = 0,
    higher_is_better: bool = True,
)
```

The score of one metric for one model.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `str` | required | The metric's key: its label, or its name. |
| `value` | `float \| None` | required | The headline number (`None` if no row could be scored). |
| `per_row` | `list[float \| None] \| None` | `None` | Row-level scores aligned with the dataset records, if the metric has them. |
| `extra` | `dict[str, float]` | `{}` | Secondary numbers, e.g. precision and recall next to F1. |
| `n_scored` | `int` | `0` | Rows that counted towards the score. |
| `higher_is_better` | `bool` | `True` |  |

## Metrics

Score results and add your own metrics. See [Custom metrics](https://paperlab.space/docs/extend/custom-metrics.md).

### score

```python
from paperlab_space import score

def score(results: RunResults) -> dict[str, dict[str, MetricResult]]
```

Compute every configured metric for every model in `results`.

### available_metrics

```python
from paperlab_space import available_metrics

def available_metrics() -> dict[str, type[Metric]]
```

### register

```python
from paperlab_space import register

def register(cls: type[Metric]) -> type[Metric]
```

Class decorator that makes a metric available by name in run configs.

### Metric

```python
from paperlab_space.metrics import Metric
```

Base class for metrics. Subclasses declare their options as `__init__` arguments.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `str` | required | The name used in run configs. |
| `description` | `str` | `''` | One sentence, shown by `paperlab-space metrics`. |
| `requires_reference` | `bool` | `True` | Whether the metric needs an expected-answer column. |
| `higher_is_better` | `bool` | `True` | Which way is better, so the LaTeX table bolds the right score. |

**`compute`**

```python
def compute(predictions: Predictions, references: References) -> MetricResult
```

Score all rows of one model.

**`result`**

```python
def result(value: float | None, **kwargs: Any) -> MetricResult
```

### Normalizer

```python
from paperlab_space.metrics import Normalizer

Normalizer(
    nfkc: bool = True,
    ignore_case: bool = True,
    trim_punctuation: bool = True,
    ignore_punctuation: bool = False,
    remove_articles: bool = False,
)
```

How to normalise text before comparing a prediction to a reference.

Defaults suit short answers and labels: NFKC (so full-width `ＡＢＣ` equals `ABC`),
case-insensitive, whitespace collapsed, and quotes/punctuation trimmed from the ends
(so `"Positive."` matches `positive`).

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `nfkc` | `bool` | `True` | Apply Unicode NFKC, so full-width characters equal their ASCII forms. |
| `ignore_case` | `bool` | `True` | Compare case-insensitively. |
| `trim_punctuation` | `bool` | `True` | Strip punctuation and quotes from the start and end only. |
| `ignore_punctuation` | `bool` | `False` | Remove all punctuation, including inside the text. |
| `remove_articles` | `bool` | `False` | Remove English articles (a, an, the), as in SQuAD-style evaluation. |

### tokenize

```python
from paperlab_space.metrics import tokenize

def tokenize(text: str) -> list[str]
```

Split on whitespace, and into single characters for scripts written without spaces
between words (Chinese, Japanese, Thai, Lao, Khmer, Burmese, Tibetan).

Korean uses spaces between words, so Hangul is split on whitespace like Latin text.

### extract_label

```python
from paperlab_space.metrics import extract_label

def extract_label(text: str, labels: Sequence[str], normalize: Normalizer) -> str
```

Map a free-form answer to one of `labels`.

Returns the matching label if the normalised answer equals one, or if exactly one label
appears in it (`"The sentiment is positive."` -> `positive`). Otherwise the
normalised answer is returned unchanged and will not match any label.

## Export

Write results to files. `from paperlab_space import export`.

### write_all

```python
from paperlab_space.export import write_all

def write_all(results: RunResults, out_dir: str | Path) -> dict[str, Path]
```

Write every export format into `out_dir` and return the paths by format.

### write_excel

```python
from paperlab_space.export import write_excel

def write_excel(results: RunResults, path: str | Path) -> Path
```

Write `results` to an `.xlsx` workbook with Summary, Outputs, and Config sheets.

### write_latex

```python
from paperlab_space.export import write_latex

def write_latex(results: RunResults, path: str | Path, **options: Any) -> Path
```

Write `latex_table` output to `path`. Options are passed through.

### latex_table

```python
from paperlab_space.export import latex_table

def latex_table(
    results: RunResults,
    *,
    metrics: Sequence[str] | None = None,
    caption: str | None = None,
    label: str | None = 'tab:results',
    decimals: int = 1,
    percent: bool = True,
    bold_best: bool = True,
    floating: bool = True,
) -> str
```

Build a model-by-metric comparison table.

**Arguments**

- `metrics`: Metric keys to include, in order. Defaults to all configured metrics.
- `percent`: Show scores in [0, 1] as percentages.
- `bold_best`: Bold the best score in each column (all of them on ties).
- `floating`: Wrap the tabular in a `table` environment with caption and label.

### write_repro

```python
from paperlab_space.export import write_repro

def write_repro(results: RunResults, path: str | Path) -> Path
```

### reproducibility

```python
from paperlab_space.export import reproducibility

def reproducibility(results: RunResults) -> dict[str, Any]
```

## Backends

Send requests somewhere other than LiteLLM. See [Custom backends](https://paperlab.space/docs/extend/custom-backends.md).

### Backend

```python
from paperlab_space.runner import Backend
```

A protocol: any class with these methods works.

**`complete`**

```python
async def complete(
    model: ModelSpec,
    messages: Messages,
    *,
    seed: int | None,
    timeout: float,
) -> Completion
```

Return one completion, raising `RetryableError` or `ProviderError`.

**`price`**

```python
def price(model: ModelSpec) -> tuple[float, float] | None
```

USD per input token and per output token, or `None` if unknown.

### Completion

```python
from paperlab_space.runner import Completion

Completion(
    text: str,
    input_tokens: int | None = None,
    output_tokens: int | None = None,
    cost_usd: float | None = None,
    model_version: str | None = None,
)
```

One model response, as a backend returns it.

| Attribute | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `str` | required | The model's answer. |
| `input_tokens` | `int \| None` | `None` | Prompt tokens, as reported by the provider. |
| `output_tokens` | `int \| None` | `None` | Output tokens, as reported by the provider. |
| `cost_usd` | `float \| None` | `None` | Cost in USD, if known. |
| `model_version` | `str \| None` | `None` | The exact model name reported by the provider, if any. |

### RetryableError

```python
from paperlab_space.runner import RetryableError

RetryableError(message: str, retry_after: float | None = None)
```

A transient failure (rate limit, timeout, server error) worth retrying.

Subclass of `PaperLabError`.

### ProviderError

```python
from paperlab_space.runner import ProviderError

ProviderError(message: str, fatal: bool = False)
```

A permanent failure for one request.

If `fatal` is true the problem affects every request to this model (bad API key,
unknown model), so the runner stops sending requests to it.

Subclass of `PaperLabError`.

## Errors

Every error the package raises on purpose is a `PaperLabError`.

### PaperLabError

```python
from paperlab_space import PaperLabError
```

Base class for all paperlab-space errors.

Subclass of `Exception`.

### ConfigError

```python
from paperlab_space import ConfigError
```

A run configuration is invalid.

Subclass of `PaperLabError`.

### DataError

```python
from paperlab_space import DataError
```

The input data or column mapping is invalid.

Subclass of `PaperLabError`.

### TemplateError

```python
from paperlab_space import TemplateError
```

A prompt template is invalid or references unknown columns.

Subclass of `PaperLabError`.
