Decision Models¶
Decision-model judges, the confidence cascade, and the offline tools that calibrate and replay a cascade.
Overview¶
A decision model answers typed questions with probabilities instead of generating text, and grades an item with one request for the whole rubric. DecisionModelConfig configures one; it is accepted wherever a judge config is (CriterionGrader(judge_model_config=...), JudgeSpec). EscalationConfig turns a grader with one decision-model judge into a confidence cascade that escalates uncertain criteria to LLM judges. replay_escalation, calibrate_escalation and escalation_stats choose and check a cascade's threshold offline, without new calls.
The decision-model client needs the optional TypeSafe SDK: pip install 'autorubric[typesafe]'.
For a guided introduction see Decision-Model Judges; for the end-to-end calibration workflow see the cascade recipe.
Quick Example¶
from autorubric import (
DecisionModelConfig, EscalationConfig, LLMConfig,
calibrate_escalation, replay_escalation,
)
from autorubric.graders import CriterionGrader, JudgeSpec
jev = DecisionModelConfig(model="jev-latest", cache_enabled=True)
gemini = LLMConfig(model="gemini/gemini-3-flash-preview")
# Standalone
grader = CriterionGrader(judge_model_config=jev)
# Cascade
grader = CriterionGrader(
judge_model_config=jev,
escalation=EscalationConfig(judges=gemini, threshold=0.72),
seed=7,
)
# Offline calibration from a decision-model run and an LLM run over the same items
curve = calibrate_escalation(calib, dm_calib, llm_calib)
best = curve.best(tolerance=0.005)
hybrid = replay_escalation(dm_test, llm_test, best)
DecisionModelConfig¶
Configuration of a decision-model judge: endpoint, framings, threshold, cache and price.
DecisionModelConfig
dataclass
¶
DecisionModelConfig(model: str, *, api_key: str | None = None, api_base: str | None = None, timeout: float = 60.0, max_retries: int = 3, max_parallel_requests: int | None = None, cache_enabled: bool = False, cache_dir: str | Path = '.autorubric_cache', cache_ttl: int | None = None, extra_headers: dict[str, str] = dict(), binary_framing: BinaryFraming = 'noul_framed', ordinal_framing: OrdinalFraming = 'choice', decision_threshold: float = 0.5, input_cost_per_token: float | None = None)
Configuration of a decision-model judge.
A decision model grades an item with one request for the whole rubric: the
submission (with the prompt, a reference submission and rubric guidelines when present)
is sent once as shared state, and each criterion becomes one typed question. It
returns probabilities, not text, so its votes carry probabilities and confidence
and have no explanation (reason is None).
Use it wherever a judge config is accepted, e.g.
CriterionGrader(judge_model_config=DecisionModelConfig(model="jev-latest")) or
JudgeSpec(DecisionModelConfig(...), "jev") in an ensemble. Building the grader
builds the client, which needs the TypeSafe SDK (pip install
'autorubric[typesafe]'); constructing this config does not. The client's HTTP
requests honour the environment's TLS and proxy settings (SSL_CERT_FILE,
HTTPS_PROXY and the like), and settings no HTTP client can be built with (e.g. a CA
file that does not exist) fail the grader's construction with ValueError. Field names match
LLMConfig wherever the concept is the same. Every field after model is
keyword-only.
Few-shot examples (FewShotConfig, training_data) apply to LLM judges only and
are never sent to a decision model; a grader whose only judges are decision models
rejects them. A decision model's calibration channels are rubric guidelines and, in a
cascade, the escalation threshold.
Decision-model answers are not bit-deterministic: repeating an identical request can
return slightly different probabilities, so a verdict whose probability lies near
decision_threshold can change between runs. Enable cache_enabled to make
re-runs reproducible: a cached request returns the stored answers, usage included.
| ATTRIBUTE | DESCRIPTION |
|---|---|
model |
Model name or alias sent with each request, e.g.
TYPE:
|
api_key |
API key, sent as a bearer token.
TYPE:
|
api_base |
Base URL of a System One-compatible endpoint; requests go to
TYPE:
|
timeout |
Per-request timeout in seconds, applied to each HTTP operation, as in
TYPE:
|
max_retries |
Total attempts per request, including the first, as in
TYPE:
|
max_parallel_requests |
Maximum concurrent requests to the endpoint, as in
TYPE:
|
cache_enabled |
Cache responses on disk, as in
TYPE:
|
cache_dir |
Directory of the response cache. It is the same store that LLM judges
use when their
TYPE:
|
cache_ttl |
Lifetime of a cache entry in seconds;
TYPE:
|
extra_headers |
Additional HTTP headers sent with every request, header names to
values, both strings. Building a client checks each header with the HTTP/1.1
layer's own rule (
TYPE:
|
binary_framing |
How a binary criterion is posed. The criterion's
TYPE:
|
ordinal_framing |
How an ordinal multi-choice criterion is posed. The requirement is
the bare instructions and option labels are always sent verbatim, in the
rubric's order. Nominal criteria are always posed as
A criterion its framing cannot express is left out of the item's request and
fails alone, as a
TYPE:
|
decision_threshold |
Cut on P(MET) for the Noul framings, in [0, 1]. Above it the verdict is MET, below it UNMET; at exactly the threshold the verdict is the worst case for the criterion's weight sign, the same tie rule as ensemble aggregation. Choice and Score answers ignore it. It is not part of the cache key: changing it re-reads cached answers instead of asking again.
TYPE:
|
input_cost_per_token |
Price in USD per input token, e.g.
TYPE:
|
Examples:
>>> jev = DecisionModelConfig(model="jev-latest", cache_enabled=True)
>>> self_hosted = DecisionModelConfig(
... model="my-org/rubric-dm-7b",
... api_base="https://xyz.endpoints.huggingface.cloud",
... api_key="hf_...",
... )
EscalationConfig¶
Configuration of a confidence cascade: the LLM escalation judges and the thresholds.
EscalationConfig
dataclass
¶
EscalationConfig(judges: LLMConfig | Sequence[JudgeSpec], threshold: float, per_criterion: Mapping[str, float] | None = None)
Configuration of a confidence cascade: which LLM judges take over, and when.
Passed as CriterionGrader(escalation=...) to a grader whose one judge is a decision
model. The decision model answers every criterion of an item, in its one request per
item; a criterion is escalated when its vote errored, abstained (CANNOT_ASSESS
or an NA option), or has a confidence below the criterion's threshold
(threshold_for). The escalation judges vote on the escalated criteria only, and
only their votes decide such a criterion's verdict (aggregated with the grader's
aggregation / ordinal_aggregation / nominal_aggregation); the decision
model's vote is kept with superseded=True and never used as a fallback. An
escalation judge is called as the grader's llm_calls says: by default once per
escalated criterion, about that criterion alone; under llm_calls="per_item" once
per item with any escalated criterion, about the whole rubric, keeping its judgments
of the escalated criteria only. Either way each call is the one the judge would make
in the same grader without the cascade.
Validated at construction (dataclasses.replace validates again) and frozen: its
fields cannot be reassigned. The judges list and the per_criterion dict can
still be changed in place, so a grader validates and keeps its own copy
(dataclasses.replace) when it is built; later changes to the config do not reach
it.
| ATTRIBUTE | DESCRIPTION |
|---|---|
judges |
The escalation judges, LLM judges only, in order. A bare
TYPE:
|
threshold |
A criterion is escalated when the decision model's confidence is below
it, in [0, 1].
TYPE:
|
per_criterion |
Thresholds for particular criteria, keyed by criterion name (each
in [0, 1]); other criteria, and unnamed ones, use
TYPE:
|
Example
jev = DecisionModelConfig(model="jev-latest") grader = CriterionGrader( ... judge_model_config=jev, ... escalation=EscalationConfig( ... judges=LLMConfig(model="gemini/gemini-3-flash-preview"), ... threshold=0.72, ... per_criterion={"factuality": 0.95}, ... ), ... )
Validate and store the configuration.
| PARAMETER | DESCRIPTION |
|---|---|
judges
|
One LLM judge as a bare |
threshold
|
The confidence threshold, in [0, 1].
TYPE:
|
per_criterion
|
Thresholds by criterion name, each in [0, 1]. Copied.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/autorubric/graders/criterion_grader.py
threshold_for
¶
threshold_for(criterion: Criterion) -> float
The confidence threshold below which criterion is escalated.
Its per_criterion threshold, looked up by the criterion's name, else
threshold; an unnamed criterion always gets threshold.
Source code in src/autorubric/graders/criterion_grader.py
replay_escalation¶
Build the EvalResult a cascade would have produced from a decision-model run and an LLM run, with no calls.
replay_escalation
¶
replay_escalation(dm_result: EvalResult, llm_result: EvalResult, escalation: EscalationPoint | float, *, per_criterion: Mapping[str, float] | None = None, cannot_assess_config: CannotAssessConfig | None = None, normalize: bool = True, llm_calls: LLMCalls = 'per_criterion') -> EvalResult
Build the EvalResult a confidence cascade would have produced, with no calls.
dm_result is a run of the decision model alone and llm_result a run of the LLM
judge(s) alone, over the same items; items are paired by item_idx. For each
criterion of each item, the decision model's recorded vote either stands or is
superseded, by the rule of a live cascade (CriterionGrader with escalation=): the
criterion is escalated when the vote failed (an error), abstained (CANNOT_ASSESS
or an NA option), or has a confidence below the criterion's threshold (its
per_criterion threshold by name, else threshold).
- A kept criterion gets the decision-model run's report of it: its one vote, and the final verdict aggregated from it.
- An escalated criterion gets the LLM run's report of it, with the decision model's vote
in front,
superseded=True, andescalated=True. A live cascade aggregates the votes that are not superseded, which are exactly the LLM run's, so the final verdict,final_reason,errorandagreementare the ones the LLM run's aggregation recorded; an LLM panel's votes stay aggregated with the LLM run'saggregation,ordinal_aggregationandnominal_aggregationand its judges' weights. - Replayed votes keep their
judge_ids.judge_scoresholds the decision model's own score over every criterion (its superseded votes included) andNonefor each LLM judge, by role. Scores are computed withscore_reportsundercannot_assess_configandnormalize, as the cascade's grader would.
Exactness. The replay equals a live cascade exactly when each run was graded by a
CriterionGrader configured as the cascade for everything that stage uses, over the
same items and rubrics (guidelines included), and this call's cannot_assess_config
and normalize are the cascade's:
- the decision-model run: one judge, the cascade's decision model (the same config,
judge_idand weight); - the LLM run: the cascade's escalation judges (
judges=escalation.judges, the same configs,judge_ids and weights; a bareLLMConfiggiven toEscalationConfigbecomesJudgeSpec(config, "escalation")) with every setting they use as the cascade's: itsseed(CriterionGrader.seed, generated when not given),llm_calls,shuffle_options,system_prompt,multi_choice_system_prompt,training_dataandfew_shot_config, and the default response formats (a cascade cannot have others); - both: the cascade's
auto_na_optionand aggregation settings (aggregation,ordinal_aggregation,nominal_aggregation).
For CriterionGrader(judge_model_config=dm, escalation=escalation, seed=S) with
otherwise default settings, the two runs are graded by
CriterionGrader(judge_model_config=dm) and
CriterionGrader(judges=escalation.judges, seed=S); for a cascade that also has
llm_calls="per_item", the LLM run's grader has it too, and so does this call. Only
the criteria are checked (as graded, NA options that auto_na_option adds
included): an EvalResult records no grader configuration, so another setting goes
unnoticed. With only another seed or judge_id, the LLM prompts differ in the
order of a shuffled multi-choice criterion's options and in the few-shot examples, and
the replay equals the cascade in distribution only; with another prompt setting
(llm_calls, shuffle_options, a system prompt, the few-shot settings) the prompts
themselves differ, and the replay is not the cascade's run.
How the LLM run was called. This call's llm_calls says how the LLM run's judges
were called, and so which share of its per-item cost and time a live cascade would
spend (see Estimates). The replay cannot check it, since an EvalResult records
no grader configuration: like the conditions above, it is a declared fact, and for an
exact replay it is the cascade's llm_calls too. It changes nothing but the
estimates: whatever it says, the replayed verdicts are the LLM run's. A wrong
llm_calls therefore biases only the estimated costs and durations, and so only the
cost_usd and compute_seconds of escalation_stats, on which
EscalationCurve.best never selects.
Estimates. Per-criterion LLM cost and time are not recorded, only per-item totals. An item's LLM cost and time are therefore its LLM run totals times the share of the LLM run's calls for the item that a live cascade would make:
- Called per criterion (the default
llm_calls), a judge made a call per criterion, and a live cascade makes one per escalated criterion. An item'scompletion_costis its decision-model cost plus its LLM cost pro-rated by the share of its criteria that were escalated, and itsduration_secondsits decision-model duration plus its LLM duration pro-rated the same way. - Called per item (
llm_calls="per_item"), a judge made one call about the item's whole rubric, and a live cascade makes that same call for an item with any escalated criterion, and no call otherwise. An item'scompletion_costis its decision-model cost plus, when any of its criteria was escalated, its LLM cost in full, and itsduration_secondsits decision-model duration plus, likewise, its LLM duration in full.
An item's completion_cost is None when it is not positive, as in a live
report. The run's total duration is the decision-model run's total plus the LLM run's,
pro-rated by the share of the LLM run's calls (per judge) that a live cascade would
make: its escalated pairs over its pairs when called per criterion, its items with an
escalated criterion over its items (those with any criterion) when called per item. An
item whose decision-model grading raised (it has no votes) makes no call, as a live
cascade sends it to no LLM judge, while its calls in the LLM run count among that
run's. These are estimates, which escalation_stats sums into cost_usd and
compute_seconds. The result says so in its experiment_name, its descriptive
field ("replay of decision-model run '<name>' and LLM run '<name>' (estimated cost
and time)"), since a replay is not an experiment on disk (experiment_dir is
None). It carries no token_usage, whatever llm_calls says. Token usage is
recorded per item, not per criterion, so for judges called per criterion an item's
cannot be split; for judges called per item it would rest on llm_calls, which the
replay cannot check and which changes nothing but the estimates marked as such.
Length penalty. It is not re-applied: compare replayed scores with runs graded without one. Criterion-level metrics are unaffected.
An item whose grading raised in dm_result (it has no votes) stays failed, as it
would in a live cascade, whose decision model grades first. An item every
decision-model judgment of which failed is replayed like any other: its failed votes
escalate, as a live cascade escalates them.
| PARAMETER | DESCRIPTION |
|---|---|
dm_result
|
A run of one decision-model judge alone (e.g.
TYPE:
|
llm_result
|
A run of the LLM judge(s) alone over the same items and criteria, with a vote of every judge on every criterion of every item: whichever criteria a threshold escalates, the run costs a full LLM grading of its items.
TYPE:
|
escalation
|
The threshold, a number in [0, 1], or an
TYPE:
|
per_criterion
|
Thresholds for particular criteria, by criterion name, each in
[0, 1]; only with a number as
TYPE:
|
cannot_assess_config
|
How abstentions score (
TYPE:
|
normalize
|
Whether scores are normalized to [0, 1], as
TYPE:
|
llm_calls
|
How the LLM run's judges were called, its grader's
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EvalResult
|
An ordinary |
EvalResult
|
|
EvalResult
|
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the threshold or a |
| WARNS | DESCRIPTION |
|---|---|
UserWarning
|
If a |
Source code in src/autorubric/escalation.py
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 | |
calibrate_escalation¶
Replay and measure a cascade at every threshold of a sweep.
calibrate_escalation
¶
calibrate_escalation(dataset: RubricDataset, dm_result: EvalResult, llm_result: EvalResult, *, thresholds: Sequence[float] | None = None, metric: str | Callable[[MetricsResult], float | None] = 'criterion_accuracy', per_criterion: bool = False, min_pairs_per_criterion: int = 30, cannot_assess_config: CannotAssessConfig | None = None, normalize: bool = True, llm_calls: LLMCalls = 'per_criterion') -> EscalationCurve
Measure a confidence cascade at every threshold of a sweep, with no calls.
For each threshold of thresholds the cascade is replayed from the two runs
(replay_escalation, scoring as the cascade's grader scores, by
cannot_assess_config and normalize) and the replay measured against dataset
(escalation_stats, whose metric is read from compute_metrics(replay,
dataset)). Each point of the curve carries its thresholds, its escalation_rate,
the decision model's accuracy on the pairs it kept and on those it deferred, the
fallback's accuracy on the same deferred pairs (see EscalationPoint), metric,
the replay's estimated
cost_usd and compute_seconds (see replay_escalation; they follow how the LLM
run was called, llm_calls), and the
calibration_fingerprint of the calibration items: the items of dataset the two
runs graded (by item_idx). EscalationCurve.best picks a point, and
replay_escalation replays one.
Cost. The sweep makes no calls, but its inputs are two full gradings of the
calibration items: the decision model's, and the LLM judges' votes on every criterion
of every item (llm_result), whichever criteria a threshold would escalate. That LLM
run is the price of calibration; calibrate on a sample of the items the cascade is
meant to grade.
Same-data caveat. Every point is measured on the pairs the thresholds are chosen
from, so the metric of the point best picks is optimistic: it is the best of many
measurements on the same pairs. Measure the chosen point on held-out items graded by the
same judges (e.g. the other part of RubricDataset.split_train_test), with
replay_escalation or a live cascade. Replaying it on its own calibration items warns,
by its calibration_fingerprint, when either run records the items' submissions: runs
loaded with EvalResult.from_experiment record none, so replaying the point on them
cannot be recognized and does not warn.
Exactness. Each replay is the run a live cascade at the point's thresholds would
produce when the two runs meet replay_escalation's exactness conditions (each
graded by a CriterionGrader configured as the cascade for everything that stage
uses: for the LLM run, judges=escalation.judges with the cascade's seed,
llm_calls, shuffle_options, system prompts, few-shot settings,
auto_na_option and aggregation settings; e.g.
CriterionGrader(judges=escalation.judges, seed=S) for a cascade built with
seed=S and otherwise default settings) and when cannot_assess_config and
normalize are the cascade's grader's: score-level metrics (score_rmse,
score_pearson, ...) read the scores they set. With only another seed or
judge_id the LLM prompts differ in the order of a shuffled multi-choice criterion's
options and in the few-shot examples, and the curve holds for the cascade in
distribution only; with another prompt setting it does not describe the cascade.
This call's llm_calls states how the LLM run was called, which the runs do not
record: it sets only how each point's cost_usd and compute_seconds are
estimated, so a wrong one biases those two alone, on which neither
EscalationCurve.best nor the per-criterion fitting below selects.
Per-criterion thresholds (per_criterion=True). A threshold of its own
(EscalationConfig.per_criterion) applies to every criterion of a name, so it is
fitted on that name's pairs, across the calibration items.
- Which criteria. Each named criterion with at least
min_pairs_per_criterionlabelled pairs (a pair's item graded, its label not an abstention; seeEscalationPoint). The others use the swept global threshold: unnamed criteria, which cannot have a threshold of their own, and named criteria with fewer labelled pairs, which the call warns about. - The same selection rule, on the run's metric. The fit starts from the global
threshold that
EscalationCurve.best's rule, at its defaults (no budget, no tolerance), picks on the sweep alone. Each fitted criterion's threshold in turn, in order of first appearance, is then the threshold of the sweep that the same rule picks on the whole run'smetric, with the global threshold and the thresholds already fitted held where they are. A criterion's threshold decides whether its own pairs escalate and nothing else, so each fit is made on that criterion's pairs, by what they do tometric, asmetriccounts them.criterion_accuracy(whose pooling on each ofcompute_metrics' pathsEscalationPointdescribes), for one, leaves out an escalated pair whose final verdict abstains or stands in for failed calls, but for a genuine NA verdict it counts as an option (in a rubric without binary criteria, on the per-criterion path); a criterion it does not read (a multi-choice criterion in a rubric with binary criteria, on the per-criterion path) escalates as little as the sweep lets it, while on the pooled path it reads every criterion. No fit lowers the metric, so the point at the start's global threshold measures at least as high with the fits as without them, and so does the pointbestpicks at its defaults. Ifmetricis undefined at every threshold of the sweep, there is no start, and the call fits nothing and warns. - The points. Each point is one global threshold of the sweep plus the fitted
thresholds, the same at every point: its
thresholdandper_criterion, exactly whatreplay_escalation(dm_result, llm_result, point)replays and what a live cascade is configured with (EscalationConfig(judges, point.threshold, per_criterion=point.per_criterion)), measured on the whole run like any point.besttherefore picks among global thresholds with the fits in place: its budget and tolerance trade on the criteria that follow the global threshold. A point records neithercannot_assess_configandnormalizenorllm_calls, so its replay scores, and estimates cost and time, as this call did only when given the same ones.
Fitting replays the sweep once more for each fitted criterion, and once with the fits
in place.
Fitting on a few dozen pairs is where the same-data caveat bites hardest;
min_pairs_per_criterion bounds it.
| PARAMETER | DESCRIPTION |
|---|---|
dataset
|
The calibration items with their ground truth: the dataset both runs graded.
TYPE:
|
dm_result
|
A run of the cascade's decision-model judge alone over the calibration
items (as for
TYPE:
|
llm_result
|
A run of the cascade's escalation judge(s) alone over the same items,
with a vote on every criterion (as for
TYPE:
|
thresholds
|
The global thresholds to sweep, each a number in [0, 1], in the order the curve keeps; default 0.00, 0.02, ..., 1.00.
TYPE:
|
metric
|
The metric of a whole replay, higher being better: the name of a
TYPE:
|
per_criterion
|
Whether named criteria get thresholds of their own, fitted as above.
TYPE:
|
min_pairs_per_criterion
|
The labelled pairs a criterion needs for a threshold of its own, a positive int.
TYPE:
|
cannot_assess_config
|
How abstentions score in the replays (
TYPE:
|
normalize
|
Whether the replays' scores are normalized to [0, 1], as the cascade's grader's.
TYPE:
|
llm_calls
|
How the LLM run's judges were called, its grader's
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EscalationCurve
|
The curve: one point per threshold of |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
| WARNS | DESCRIPTION |
|---|---|
UserWarning
|
With |
Source code in src/autorubric/escalation.py
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 | |
escalation_stats¶
Diagnostics of a live or replayed cascade run.
escalation_stats
¶
escalation_stats(result: EvalResult, dataset: RubricDataset, *, metric: str | Callable[[MetricsResult], float | None] = 'criterion_accuracy') -> EscalationPoint
Diagnostics of a live or replayed cascade run, as an EscalationPoint.
Reads each criterion report's escalated and superseded flags: a kept criterion's
one vote is the decision model's, and an escalated criterion's first vote is the
decision model's, superseded, while its final verdict is the fallback's. It pools
(item, criterion) pairs, so it works on datasets whose items have different rubrics,
where compute_metrics has no per-criterion table. The pairs and accuracies are
defined in EscalationPoint. Like compute_metrics, it covers the items both in
result and in dataset; items whose grading raised have no pairs (an item every
judgment of which failed keeps its pairs: its decision model's failures escalated),
but their grading time counts in compute_seconds.
The point's threshold and per_criterion are None (an EvalResult does
not record its grader's configuration), as is its calibration_fingerprint.
cost_usd and compute_seconds are exact sums for a live run and sums of
estimates for a replay (replay_escalation).
| PARAMETER | DESCRIPTION |
|---|---|
result
|
A cascade run:
TYPE:
|
dataset
|
The dataset with the ground truth.
TYPE:
|
metric
|
The metric of the whole run: the name of a
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EscalationPoint
|
The run's |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/autorubric/escalation.py
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 | |
EscalationCurve¶
The points of a threshold sweep and the rule that picks one.
EscalationCurve
¶
Bases: BaseModel
The points of a threshold sweep (calibrate_escalation), and the rule that picks one.
Each point is a cascade at one global threshold (plus the fitted per-criterion
thresholds, when the calibration fitted any), measured on the calibration items, and
carries their calibration_fingerprint. best picks a point by one rule;
replay_escalation(dm_result, llm_result, point), given the scoring settings and the
llm_calls the calibration was given (a point records neither), rebuilds the point's
run on the runs it was calibrated on, or replays its thresholds on held-out runs.
The selection rule (best). Among the points within the escalation budget
(escalation_rate <= max_escalation_rate; every point when there is no budget), find
the best metric; of the points whose metric is within tolerance of it
(inclusively, a gap equal to tolerance up to floating-point rounding included), pick
the one that escalates least. A higher metric is better. Ties are broken in order by
the higher metric, then the lower threshold (equal here, but on new items a lower
threshold never escalates more), then the earlier point. A point without a metric
(None, or NaN) is passed over; one without an escalation_rate (a run with no
pairs) is outside any budget and ranks after every point that has one; one without a
threshold ranks after those with one.
tolerance > 0 trades metric for less escalation: best(tolerance=0.005) gives up
at most half a point of accuracy. Escalation judges called per criterion make fewer
calls as fewer pairs escalate, so the trade is "about the same accuracy for fewer LLM
calls". Called per item (llm_calls="per_item"), they make one call per item with any
escalated criterion, so escalating fewer pairs can save no call at all: compare the
points' cost_usd and compute_seconds before giving up metric for it.
Same-data caveat. Every point was measured on the pairs the sweep chose from, so the
metric of the point best picks is optimistic: it is the best of many measurements
on those pairs. Measure the chosen point on held-out items graded by the same judges
(replay_escalation on their runs, or a live cascade). Replaying it on its own
calibration items warns (calibration_fingerprint) when either run records the items'
submissions; runs loaded with EvalResult.from_experiment record none, so there the
check cannot recognize them.
| ATTRIBUTE | DESCRIPTION |
|---|---|
points |
The operating points, in the order of the sweep.
TYPE:
|
best
¶
best(max_escalation_rate: float | None = None, tolerance: float = 0.0) -> EscalationPoint
The point the selection rule picks (see the class docstring).
| PARAMETER | DESCRIPTION |
|---|---|
max_escalation_rate
|
The escalation budget, a share of pairs in [0, 1]
(inclusive), or
TYPE:
|
tolerance
|
How far below the best metric within the budget a point's metric may be, in the metric's units (a number >= 0).
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
EscalationPoint
|
The least escalating point within |
EscalationPoint
|
budget. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If |
Source code in src/autorubric/escalation.py
to_dataframe
¶
The points as a pandas DataFrame: a row per point, a column per field of
EscalationPoint (per_criterion holds each point's dict, or None).
pandas is imported here, as MetricsResult.to_dataframe imports it.
Source code in src/autorubric/escalation.py
EscalationPoint¶
One operating point of a cascade: its thresholds and what they achieve.
EscalationPoint
¶
Bases: BaseModel
One operating point of a confidence cascade: its thresholds, and what they achieve.
escalation_stats measures one on a cascade run, live or replayed, and
calibrate_escalation one per threshold of a sweep. Given to replay_escalation, a
point replays a cascade at its threshold and per_criterion thresholds. A point
records neither the scoring settings it was measured with nor how the LLM run's judges
were called (llm_calls), so a replay of a calibrated point scores, and estimates
cost and time, as the calibration did only when given the same ones.
Pairs and accuracy. A pair is one criterion of one item whose grading did not raise,
even one whose every criterion's judgment failed. A pair is labelled when its item has
ground truth for the criterion that does not abstain: a binary label other than
CANNOT_ASSESS, or a multi-choice label that is not an NA option. An accuracy is the
share of a set of labelled pairs, binary and multi-choice pooled, on which a prediction
equals the label: the verdict of a binary criterion, the selected option of a
multi-choice one. As compute_metrics measures one criterion under its default
handling (cannot_assess="exclude", na_mode="exclude":
CriterionMetrics.accuracy, exact_accuracy), an abstention (CANNOT_ASSESS, an
NA option) is not a prediction, so a pair on which the predictor abstained counts in
neither the numerator nor the denominator. (The run-level criterion_accuracy of
compute_metrics pools otherwise. On its per-criterion path, for items that share one
rubric, it reads the binary pairs alone when there are any, else the multi-choice pairs
with an NA prediction or label counted as one more option. On its pooled path, for items
with different rubrics, it reads the pairs of every scale, leaves out a pair whose
prediction abstains, and counts one whose label abstains as a miss. On either path, a
criterion whose judgment failed is no pair.)
- The decision model is measured as
compute_metrics(..., per_judge=True)measures a judge: on its own votes, superseded ones included, leaving out a failed judgment (a vote with anerror), which is no prediction of the decision model's. A decision-model failure or abstention escalates at any threshold, so it counts inescalation_ratebut in no accuracy: it leaves the decision model no prediction to measure, or to compare the fallback's with. - The fallback's predictions are the escalated pairs' final verdicts, which the
escalation judges' votes alone decide, read as
compute_metricsreads a run's: a final verdict that abstains is no prediction, and neither is one that stands in for the judges' failed calls (such as the worst case of anunknownfailure), which the run's metrics leave out too. - The deferred pairs are the escalated labelled pairs the decision model answered:
it escalated them for a confidence below the threshold.
dm_accuracy_escalatedandfallback_accuracy_escalatedare both measured on the deferred pairs whose final verdict is a prediction, where both judges made one, so they compare the two judges on the same pairs.
| ATTRIBUTE | DESCRIPTION |
|---|---|
threshold |
The confidence threshold below which a criterion is escalated.
TYPE:
|
per_criterion |
Thresholds for particular criteria, by criterion name (as
TYPE:
|
escalation_rate |
The share of the run's pairs, labelled or not, that were escalated.
TYPE:
|
dm_accuracy_kept |
The decision model's accuracy on the pairs it kept. A cascade can pay only if its confidence separates these from the pairs it defers.
TYPE:
|
dm_accuracy_escalated |
The decision model's accuracy on the deferred pairs whose final verdict is a prediction (it neither abstains nor stands in for failed calls).
TYPE:
|
fallback_accuracy_escalated |
The accuracy of the final verdicts, which the fallback
LLM judges decide, on the same pairs. On them, escalating rather than keeping
the decision model's verdicts gains right verdicts exactly when this beats
TYPE:
|
metric |
The chosen metric of the whole run, from
TYPE:
|
cost_usd |
The run's cost: the sum of its items'
TYPE:
|
compute_seconds |
The sum of the items' grading times (
TYPE:
|
calibration_fingerprint |
The fingerprint of the items the point was calibrated
on (
TYPE:
|
Accuracies are None when their set of labelled pairs is empty.