embedding
Evals on your labels

Docs/Evals on your labels

Evals on your labels

vegaml-eval against your own labelled decisions, what each number in the report means, and which fix each bad number points at.


Nobody's test split is your traffic, and the published numbers for any decision model, this one included, are measured on its own. vegaml-eval measures the model on your labelled decisions instead, which is the number that should decide whether you ship it.

bash
vegaml-eval decisions.jsonl --questions @questions.json

Each JSONL row is one labelled state:

json
{"state": "I was billed twice, refund me or I cancel.", "labels": {"churn": "true"}}

--questions is the same sheet decide() takes, inline JSON or @file. A row may carry its own "questions" where the instruction varies per example. From Python it is one call:

python
from vegaml.evals import evaluate

report = evaluate(v, rows, questions)

What the report says#

Per question, and pooled overall:

numberwhat it measures
accuracyagainst your labels, nothing clever
ecethe gap between claimed confidence and measured accuracy: the honesty number
coveragehow often the true label was inside the conformal set. At alpha=0.1 it should sit near 0.9.
mean_set_sizehow wide the set had to be to achieve that coverage. A set that covers by always holding every option is measured here, not hidden.
abstain_ratehow often the case was too hard to commit on
unbound_ratehow often the input sat off the calibrated map, reported separately on purpose
latency_ms_p50 / p95and n_tokens_per_decision, overall

Read coverage and mean_set_size together, always. Coverage alone is trivially satisfiable by a model that keeps every option in every set, and that model has told you nothing.

What to do with a bad number#

The three numbers point at three different fixes, and mixing them up wastes a day.

symptomwhat it meansfix
low accuracy, honest ECEthe engine does not know your taskfit heads
decent accuracy, bad ECEit knows, but it is miscalibrated on your trafficrecalibrate
coverage far from 1 - alphausually the same thing as the row aboverecalibrate, then eval again on held-out rows
many unboundsyour traffic has drifted away from the calibrated distributionrecalibrate on recent rows
many abstainsthe cases are genuinely hard for the enginefit heads

Thirty to a hundred labelled rows per question is enough to start trusting the direction of these numbers. The eval costs one decide() per row, so a thousand rows is a coffee.

Watch out

Report both error rates, never one.

On any two-class question, a single headline number can be perfect for the wrong reason. A model that blocks everything catches every attack; a model that forwards everything never refuses a customer. Both score well on one metric and are worthless. vegaml.guard.score exists precisely to make that hard to do by accident, and degenerate() names the failure in one line. See the guard recipe.