ek.qe#
Reference-free quality estimation: the estimate_quality() side of ek.
This package implements the online (no-gold) half of evaluation as the strict
pipeline signal -> calibrate -> validate -> decide (misc/docs/ek_03):
signals (cheapest first): the deterministic
VerifierSignal(schema/checksum/cross-field, cost tier 1, runs always), free intrinsicIntrinsicConfidenceSignal/LogprobSignal(tier 2), and the flagship ROVER
AgreementSignalmulti-engine vote (tier 3);calibrate –
PlattCalibrator(default) / isotonic / temperature, with Mondrian grouping and ECE diagnostics: *non-optional before any gate*;decide –
CostSensitiveGate(default),ConformalGate, andRiskControlGate,reading only calibrated probabilities.
Everything is pure-Python and dependency-free by default; the ek[calibration]
(netcal/sklearn/MAPIE/crepes) and ek[agreement] (uqlm) extras supply
library-backed equivalents, imported lazily so import ek stays light.
Importing this package registers the built-in strategies (signals, aggregators,
checks, calibrators, policies) by name in ek.registry.
Functions
|
Align N hypotheses, vote per slot, and emit consensus + per-position agreement. |
|
Geometric mean of token probabilities, |
|
Length-normalised score |
|
Weakest-token probability |
|
Arithmetic mean of token probabilities |
|
Fuse calibrated per-family probabilities by their mean (the default combiner). |
|
Fuse calibrated per-family probabilities by their minimum (conservative). |
|
Expected Calibration Error: weighted mean gap between confidence and accuracy. |
|
Per-bin |
|
Persist a fitted calibrator's parameters to the |
|
Reconstruct a persisted calibrator by name (dispatched on its |
|
A calibrator backed by scikit-learn ( |
|
The risk-coverage trade-off: |
|
The split-conformal threshold: the |
|
A |
|
A |
|
A |
|
A |
|
A spec-driven |
|
Flag when |
|
Luhn (mod-10) checksum: credit cards, IMEIs, some national IDs. |
|
IBAN validity via the ISO 13616 / ISO 7064 mod-97 rule (must equal 1). |
|
Validate an ISBN-10 or ISBN-13 by length (digits/hyphens/spaces ignored). |
Classes
|
The result of a ROVER pass over N hypotheses. |
|
One branch point of the transition network: each engine's token (or NULL). |
|
ROVER multi-engine agreement as a reference-free |
|
Deterministic verifier evidence as a reference-free |
|
Aggregate token log-probabilities into one field score (a |
|
Aggregate an extractor's per-unit confidences into a field score. |
|
Platt scaling: |
|
Isotonic (monotonic non-decreasing) calibration via pool-adjacent-violators. |
|
Temperature scaling: |
|
Per-group (Mondrian) calibration: one calibrator per |
|
One calibrator per signal family, fused by a documented combination rule. |
|
Accept/flag/block from the cost ratio |
|
Split-conformal accept/flag with a finite-sample marginal guarantee. |
|
Mondrian (class-conditional) conformal: one |
|
Conformal Risk Control: accept |
- class ek.qe.AgreementSignal(cost_tier=3, use_confidence=True, conf_weight=0.5, null_conf=0.7, tokenize=None, max_tokens=5000)[source]#
Bases:
objectROVER multi-engine agreement as a reference-free
Signal.Cost tier 3 (
Nengine runs): try the deterministic verifier layer and any free intrinsic confidence first. Called on a collection of hypotheses, it runsrover()and returns the mean per-position agreement as the raw signal – uncalibrated, like every signal, so it must pass through aCalibratorbefore any gate reads it.Example
>>> sig = AgreementSignal() >>> round(sig(["the cat sat", "the cat sit", "the bat sat"]), 3) 0.778
- class ek.qe.ConformalGate(alpha=0.1, nonconformity=<function ConformalGate.<lambda>>, q=inf, _cal=<factory>)[source]#
Bases:
objectSplit-conformal accept/flag with a finite-sample marginal guarantee.
Fit on
(calibrated_prob, correct): the nonconformity scores of the correct calibration items set a thresholdqsuch that, among truly-correct test items exchangeable with them, at mostalphaare flagged. The guarantee is marginal, not per-field-type – useGroupConformalGatefor that.- Parameters:
- class ek.qe.CostSensitiveGate(rho=1.0, block_threshold=0.0, accept_threshold=None)[source]#
Bases:
objectAccept/flag/block from the cost ratio
rho = c_FN / c_FPon calibratedp.Accept when the expected cost of accepting is below that of routing to a human. Accepting risks an undetected error: cost
(1 - p) * c_FN. Flagging a value that was actually correct wastes a review: costp * c_FP(the false-positive cost is incurred only with probabilityp, not unconditionally). So accept iff(1 - p) * c_FN <= p * c_FP, i.e. the Bayes-optimal thresholdp >= c_FN / (c_FN + c_FP) = rho / (1 + rho). A largerrho(misses much costlier than reviews) raises the bar to auto-accept (tau -> 1asrho -> inf);rho = 1gives the symmetrictau = 0.5.block_thresholdadds an optional hard-fail band at the bottom.- Parameters:
rho (
float) – Cost ratioc_FN / c_FP(>= 0). The single lever; no magic numbers.block_threshold (
float) – Calibrated probability at or below which toblockrather thanflag(default0-> never block on probability alone; a failed verifier is what forces a block, upstream).accept_threshold (
Optional[float]) – Override the derived accept threshold explicitly.
- class ek.qe.GroupCalibrator(factory=<class 'ek.qe.calibrate.PlattCalibrator'>, by_group=<factory>, pooled=None, kind='group')[source]#
Bases:
objectPer-group (Mondrian) calibration: one calibrator per
NodeType/FieldSpec.Distribution-free conditional (per-field-type) coverage is impossible in general (Barber et al. 2019); calibrating separately per group restores it approximately.
fittakes a parallelgroupssequence;__call__routes by group key, falling back to a pooled calibrator for unseen groups.- Parameters:
factory (
Callable[[],Any]) – Zero-arg callable producing a fresh per-group calibrator (defaultPlattCalibrator).
- factory#
alias of
PlattCalibrator
- class ek.qe.GroupConformalGate(alpha=0.1, nonconformity=<function GroupConformalGate.<lambda>>, by_group=<factory>, pooled=None)[source]#
Bases:
objectMondrian (class-conditional) conformal: one
ConformalGateper group.Restores approximate per-field-type validity by calibrating separately per group key (e.g.
NodeType/FieldSpec), with a pooled fallback for unseen groups.
- class ek.qe.IntrinsicConfidenceSignal(pool='min', cost_tier=2)[source]#
Bases:
objectAggregate an extractor’s per-unit confidences into a field score.
- Parameters:
Example
>>> IntrinsicConfidenceSignal(pool="min")([0.99, 0.4, 0.95]) 0.4
- class ek.qe.IsotonicCalibrator(x=<factory>, y=<factory>, kind='isotonic')[source]#
Bases:
objectIsotonic (monotonic non-decreasing) calibration via pool-adjacent-violators.
More flexible than Platt; needs more calibration data and can overfit small sets. Predicts by linear interpolation between fitted points, clipped at the ends.
- class ek.qe.LogprobSignal(aggregator=<function geo_mean>, alpha=0.6, cost_tier=2)[source]#
Bases:
objectAggregate token log-probabilities into one field score (a
Signal).- Parameters:
aggregator (
Any) – A logprob aggregator (name registered underaggregatorsor a callable). Defaults togeo_mean().alpha (
float) – Length penalty passed tolength_normalized()only.cost_tier (
int) –2– free intrinsic signal (already computed at inference).
- class ek.qe.PlattCalibrator(a=1.0, b=0.0, max_iter=100, kind='platt')[source]#
Bases:
objectPlatt scaling:
sigmoid(a * score + b), fit by Newton/IRLS on labels.The default calibrator: works on any scalar (aggregated confidence/logprob), no logits required. Uses Platt’s target smoothing so it does not overfit small calibration sets.
- Parameters:
max_iter (
int) – Newton iterations (converges in a handful for a 2-parameter model).
- class ek.qe.RiskControlGate(target=0.05, loss=<function RiskControlGate.<lambda>>, loss_bound=1.0, lam=1.0)[source]#
Bases:
objectConformal Risk Control: accept
p >= lambdabounding the accepted-error rate.Picks the smallest threshold
lambda(the most coverage) whose finite-sample risk bound is at or belowtarget. The controlled quantity isE[ loss(item) * 1(accepted) ]– with the default 0/1 loss, the population rate of accepted-yet-wrong items.- Parameters:
target (
float) – Upper bound on the accepted-error rate (e.g.0.05).loss (
Callable[[bool],float]) –correct -> lossin[0, loss_bound]; default0if correct else1. Any monotone loss bounded byloss_boundworks.loss_bound (
float) – The loss’s upper boundB– the CRC finite-sample slack is+B/(n+1). Must matchloss’s range or the guarantee is invalid; keep the default1.0for the 0/1 loss.
- class ek.qe.RoverConsensus(tokens=<factory>, slots=<factory>, agreement=<factory>, n_engines=0)[source]#
Bases:
objectThe result of a ROVER pass over N hypotheses.
- property mean_agreement: float#
Mean per-position agreement over consensus tokens, a reference-free confidence in
[0, 1]:1.0means every engine agreed at every emitted position; lower means at least one engine dissented.An empty consensus (no token survived voting – NULL won every slot) means the engines agreed on nothing, so this returns
0.0when 2+ engines were fused, NOT a misleading1.0(which would feed a maximal raw confidence to the calibrator/gate). A single engine – or none – is vacuously1.0.
- class ek.qe.RoverSlot(entries=<factory>, winner=None, score=0.0, vote_share=0.0)[source]#
Bases:
objectOne branch point of the transition network: each engine’s token (or NULL).
- class ek.qe.SignalFamilyCalibrator(calibrators=<factory>, combine=<function mean_probability>, factory=<class 'ek.qe.calibrate.PlattCalibrator'>, fusion=None, kind='signal_family')[source]#
Bases:
objectOne calibrator per signal family, fused by a documented combination rule.
Averaging heterogeneous raw signals (an LLM-judge score, a token logprob, a ROVER agreement) and calibrating the mean discards exactly what a calibrator needs: each family has its own scale, reliability and direction of miscalibration. This calibrates every signal by name with its own calibrator, then fuses the calibrated probabilities with
combine.Pass it (or simply a
Mapping[str, Calibrator]) asestimate_quality(..., calibrator=...).The fused value must itself be calibrated before it is gated. Fusing calibrated probabilities (by mean, min, …) does not yield a calibrated probability: agreeing families make the mean under-confident, a weak family drags it toward the base rate. So
fit()also learns a second-stagefusioncalibrator oncombine’s output, and__call__applies it. Without afusioncalibrator (e.g. a hand-built mapping),ek.estimate_quality()refuses to gate a value fused from two or more families unless toldassume_calibrated=True(Hard Rule 1). A value from a single family is that family’s calibrated probability and gates as is.A field’s own upstream
confidenceis just one more family here, named"confidence": include it in the training rows under that name.- Parameters:
calibrators (
dict) –signal name -> Calibrator. A"*"entry is the fallback for any signal without its own entry. A signal with neither is refused (ValueError) rather than silently dropped or passed through raw (Hard Rule 1).combine (
Callable[[Mapping[str,float]],float]) –Mapping[str, float] -> floatfusing the calibrated values;mean_probability()(default) ormin_probability(), or any callable of that shape (e.g. a learned stacker).factory (
Callable[[],Any]) – Zero-arg callable producing a fresh calibrator, used byfit().fusion (
Any) – Second-stage calibrator applied tocombine’s output (learned byfit());Noneleaves the fused value uncalibrated.
Example
>>> rows = [{"judge": j, "logprob": l} for j, l in ... [(0.9, -0.1), (0.8, -0.2), (0.85, -2.0), (0.3, -0.1), (0.2, -2.5), (0.7, -1.8)]] >>> correct = [True, True, False, True, False, False] >>> cal = SignalFamilyCalibrator().fit(rows, correct) >>> sorted(cal.calibrators) ['judge', 'logprob'] >>> 0.0 <= cal({"judge": 0.9, "logprob": -0.1}) <= 1.0 True
- calibrate_signals(raw_signals, *, group=None)[source]#
{name: calibrated probability}for each raw signal (before fusion).- Return type:
- combine()#
Fuse calibrated per-family probabilities by their mean (the default combiner).
Each family’s calibrated value estimates the same event (“this field is correct”), so the mean is a defensible, assumption-light pooling rule.
- Return type:
>>> mean_probability({"judge": 0.9, "logprob": 0.7}) 0.8
- factory#
alias of
PlattCalibrator
- fit(signals, correct)[source]#
Fit one calibrator per signal name, then the
fusioncalibrator.Each family is fit over the rows where it appears with a finite value. A family with fewer than two such rows, or only one label class, cannot be calibrated and is refused (
ValueError) rather than turned into a constant that would then dilute the fusion.- Return type:
- class ek.qe.TemperatureCalibrator(T=1.0, kind='temperature', t_min=0.05, t_max=10.0, max_iter=60)[source]#
Bases:
objectTemperature scaling:
sigmoid(logit / T)with oneTfit on a holdout.Use only when you have logits:
__call__expects a logit, not a probability.T > 1softens overconfidence; the argmax is unchanged.
- class ek.qe.VerifierSignal(validators=<factory>, cost_tier=1)[source]#
Bases:
objectDeterministic verifier evidence as a reference-free
Signal.Cost tier 1 – the free, first-to-run layer. Runs a list of
Validators on a value and returns the fraction that passed in[0, 1]as the raw signal (1.0= every check passed). It also exposes theFindings it produced viafindings(), so the same object feeds both the score path (-> calibrate) and the audit path (-> review). Like every signal it is uncalibrated: a verifier failing is strong evidence, not a probability, so aCalibratorstill runs before any gate.Example
>>> v = VerifierSignal([checksum_validator("luhn")]) >>> v("79927398713") # valid -> all checks pass 1.0 >>> v("79927398710") # invalid -> the one check fails 0.0
- ek.qe.checksum_validator(kind='luhn', *, field_name='', layer='checksum')[source]#
A
Validatorthat flags a value failing a named checksum.
- ek.qe.enum_validator(members, *, field_name='', layer='enum')[source]#
A
Validatorthat flags a value not in an allowed set.
- ek.qe.expected_calibration_error(probs, correct, *, n_bins=10)[source]#
Expected Calibration Error: weighted mean gap between confidence and accuracy.
Bins predictions by confidence into
n_binsequal-width bins and averages|mean_confidence - accuracy|weighted by bin population.0is perfect. Non-finite probs are skipped; out-of-range probs are clamped into[0, 1].- Return type:
- ek.qe.geo_mean(logps)[source]#
Geometric mean of token probabilities,
exp(mean log p)(perplexity-inverse).- Return type:
- ek.qe.iban_check(iban)[source]#
IBAN validity via the ISO 13616 / ISO 7064 mod-97 rule (must equal 1).
- Return type:
- ek.qe.isbn_check(value)[source]#
Validate an ISBN-10 or ISBN-13 by length (digits/hyphens/spaces ignored).
- Return type:
- ek.qe.length_normalized(logps, *, alpha=0.6)[source]#
Length-normalised score
exp(Σ log p / T**alpha)(tunable length penalty).- Return type:
- ek.qe.load_calibrator(name, *, rootdir=None)[source]#
Reconstruct a persisted calibrator by name (dispatched on its
kind).Validates the stored record so a malformed or unknown
kindfails with an actionable error rather than a rawKeyErroror a load-then-crash-later.- Return type:
- ek.qe.luhn_check(number)[source]#
Luhn (mod-10) checksum: credit cards, IMEIs, some national IDs.
- Return type:
- ek.qe.mean_prob(logps)[source]#
Arithmetic mean of token probabilities
mean(exp(log p)).- Return type:
- ek.qe.mean_probability(probs)[source]#
Fuse calibrated per-family probabilities by their mean (the default combiner).
Each family’s calibrated value estimates the same event (“this field is correct”), so the mean is a defensible, assumption-light pooling rule.
- Return type:
>>> mean_probability({"judge": 0.9, "logprob": 0.7}) 0.8
- ek.qe.min_prob(logps)[source]#
Weakest-token probability
exp(min log p)– catches one bad character.- Return type:
- ek.qe.min_probability(probs)[source]#
Fuse calibrated per-family probabilities by their minimum (conservative).
Use when any one family flagging a problem should be enough to lower the gate.
- Return type:
>>> min_probability({"judge": 0.9, "logprob": 0.7}) 0.7
- ek.qe.range_validator(lo, hi, *, field_name='', layer='range')[source]#
A
Validatorthat flags a number outside[lo, hi].
- ek.qe.regex_validator(pattern, *, field_name='', layer='format')[source]#
A
Validatorthat flags a value not fully matchingpattern.
- ek.qe.reliability_curve(probs, correct, *, n_bins=10)[source]#
Per-bin
{confidence, accuracy, count}for a reliability diagram.
- ek.qe.risk_coverage_curve(probs, correct, *, thresholds=None)[source]#
The risk-coverage trade-off:
{threshold, coverage, selective_risk}per point.Coverage is the fraction auto-accepted at each threshold; selective risk is the error rate among the accepted. Lower-left is better; pick the operating point against a target risk or coverage.
- ek.qe.rover(hypotheses, *, use_confidence=True, conf_weight=0.5, null_conf=0.7, tokenize=None, max_tokens=5000)[source]#
Align N hypotheses, vote per slot, and emit consensus + per-position agreement.
- Parameters:
hypotheses (
Iterable[Any]) – The recognizer/extractor outputs to fuse. Each may be a string, anOcrResult-shaped object (.text/.mean_confidence), a list of token strings, or a list of(token, confidence)pairs.use_confidence (
bool) – Blend confidence into the vote (True) or vote purely by frequency (False, which forcesconf_weightto0).conf_weight (
float) – Weight on average confidence vs vote frequency in the slot score (Fiscus’s1 - alpha);0is frequency-only,1is confidence-only. Ignored whenuse_confidenceisFalse.null_conf (
float) – Confidence credited to a NULL vote (an engine that produced no token at a slot) – the lever for how readily a deletion wins.tokenize (
Optional[Callable[[Any],List[Tuple[Optional[str],float]]]]) – Optionalhypothesis -> [(token, confidence), ...]override; by default_as_units()handles strings/OcrResults/token lists.max_tokens (
Optional[int]) – Reject any hypothesis longer than this (the aligner isO(N*l*L*L'); an unbounded input is a quadratic time/memory DoS).Nonedisables the guard.
- Return type:
- Returns:
A
RoverConsensuswith the consensustokens/text, the per-slot breakdown, and the per-consensus-tokenagreementusable as a raw signal.
- ek.qe.save_calibrator(calibrator, name, *, rootdir=None)[source]#
Persist a fitted calibrator’s parameters to the
calibratorsstore.- Return type:
- ek.qe.schema_validator(value, *, spec=None)[source]#
A spec-driven
Validator: type anddomainchecks from a FieldSpec.Reads the
FieldSpectypeanddomain– a(lo, hi)numeric range, an enum tuple, or a regex string – and applies the matching check. With no spec it is a no-op, so it composes safely in any validator list.
- ek.qe.sklearn_calibrator(method='sigmoid')[source]#
A calibrator backed by scikit-learn (
method='sigmoid'Platt or'isotonic').Behind
ek[calibration]. Returns an object satisfying theCalibratorprotocol that wraps sklearn’s calibration. The pure-PythonPlattCalibrator/IsotonicCalibratorare the dependency-free defaults; use this for parity with an sklearn-centric stack.
- ek.qe.split_conformal_quantile(scores, alpha)[source]#
The split-conformal threshold: the
ceil((n+1)(1-alpha)) / nempirical quantile.scoresare nonconformity scores on an exchangeable calibration set. Returns+infwhennis too small to guarantee1 - alpha(so nothing is flagged – the honest behaviour at that sample size), and-infat thealpha -> 1limit (flag everything).- Return type:
- ek.qe.totals_consistent(record, *, total_key, item_keys, tol=0.01, layer='cross_field')[source]#
Flag when
record[total_key]does not equal the sum ofitem_keys(±tol).The canonical cross-field check (invoice total = Σ line items). Skipped (rather than raising or flagging) when the total or all item keys are absent or non-numeric, so it is safe on partial records.
Modules
Calibration: make a raw score mean a probability (the non-optional stage). |
|
Selective prediction: turn a calibrated probability into accept / flag / block. |
|
Intrinsic confidence signals: token logprobs and per-unit posteriors (cost tier 2). |
|
Deterministic verifier signals -- the cheapest, first-to-run QE layer. |