executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
decision-model-calibrationTurning a model's class probabilities into numbers you can set a threshold on: building the labelled corpus, measuring accuracy and calibration honestly (ECE with a noise floor, Brier, risk–coverage), fitting temperature/Platt, removing option-order bias, conformal prediction sets, and choosing per-action thresholds. Applies to Jev and any typed classifier …
Independent GitHub snapshot · Automated classification · Original documentation governs
name: decision-model-calibration description: | Turning a model's class probabilities into numbers you can set a threshold on: building the labelled corpus, measuring accuracy and calibration honestly (ECE with a noise floor, Brier, risk–coverage), fitting temperature/Platt, removing option-order bias, conformal prediction sets, and choosing per-action thresholds. Applies to Jev and any typed classifier with probabilities, and says which LLM providers expose logprobs at all.
USE WHEN: user asks whether a confidence/probability can be trusted, how to pick a threshold, about "calibration", "ECE", "reliability diagram", "temperature scaling", "Platt", "isotonic", "CalibratedClassifierCV", "conformal prediction", "abstain", "selective prediction", "risk coverage", "position bias", "option order", "logprobs", or wants to evaluate Jev or an LLM classifier against labels.
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:decision-calibrationand one of these topics:
calibration: ECE variants, debiased estimators, isotonic and vector scaling, bootstrap CIs, contextual calibration.order-bias: position and label bias, flip-rate measurement, PriDe.conformal: LAC, APS and RAPS, plus class-conditional coverage.provider-logprobs: request examples for each provider.eval-harness: a complete runnable harness that compares Jev with an LLM on your own corpus and writes the decision report.
A probability is useful only if "0.9" means right about nine times in ten on
your data. No vendor can promise that for you: calibration depends on your
inputs, your language and your label set, and it shifts with every model
version. This skill is the procedure; typed-decision-models has the evidence
about Jev in particular. The code below was run on simulated data with known
over-confidence before it was written here (Python 3.12, numpy 2.5, scipy 1.18,
scikit-learn 1.9.1).
It outlives every model you try against it, and it is the only thing that lets you compare them.
import numpy as np
def ece(confidence, correct, n_bins=10):
"""Expected calibration error with equal-mass bins (less biased than equal-width)."""
confidence = np.asarray(confidence, float)
correct = np.asarray(correct, float)
order = np.argsort(confidence)
total = len(confidence)
return sum(
len(b) / total * abs(correct[b].mean() - confidence[b].mean())
for b in np.array_split(order, n_bins)
if len(b)
)
def ece_noise_floor(confidence, n_bins=10, n_sims=2000, seed=0):
"""ECE a *perfectly calibrated* model would show on this many items with these
confidences. A measured ECE inside this band is indistinguishable from noise."""
rng = np.random.default_rng(seed)
confidence = np.asarray(confidence, float)
sims = [ece(confidence, rng.random(confidence.size) < confidence, n_bins) for _ in range(n_sims)]
return np.percentile(sims, [50, 95])
def brier(probs, labels):
"""Multiclass Brier score; probs is (n, k), labels are column indices."""
probs = np.asarray(probs, float)
onehot = np.eye(probs.shape[1])[labels]
return np.mean(np.sum((probs - onehot) ** 2, axis=1))
Report, per question:
ece_noise_floor). A bare ECE is uninterpretable;What "confidence" means:
p_max, or the probability of the chosen option.max(p, 1 − p), with correctness judged at your threshold.Do not calibrate Jev's confidence field. It is a dispersion statistic
derived from the distribution ((p_max − 1/n)/(1 − 1/n) for Choice), not a
probability. Calibrate probabilities or noul.
from scipy.optimize import minimize_scalar
from scipy.special import softmax
# Jev rounds probabilities to 0.01 and returns exact zeros. log(0) is -inf, and a
# tiny floor (1e-6) turns every zero into a huge negative logit that inflates the
# fitted temperature. Half the rounding quantum is the honest floor.
FLOOR = 0.005
def to_matrix(answers, options):
"""[{'billing': 0.88, ...}, ...] -> (n, k) array in a fixed option order."""
return np.array([[a[o] for o in options] for a in answers], float)
def fit_temperature(probs, labels, floor=FLOOR):
logits = np.log(np.clip(probs, floor, 1.0))
labels = np.asarray(labels)
def nll(t):
p = softmax(logits / t, axis=1)
return -np.mean(np.log(p[np.arange(len(labels)), labels]))
return minimize_scalar(nll, bounds=(0.05, 20.0), method="bounded").x
def apply_temperature(probs, t, floor=FLOOR):
return softmax(np.log(np.clip(probs, floor, 1.0)) / t, axis=1)
T > 1 means the model was over-confident; T < 1 means it was under-confident. The floor is not a detail:
from scipy.special import logit
from sklearn.linear_model import LogisticRegression
def fit_platt(p_yes, y, floor=FLOOR):
x = logit(np.clip(np.asarray(p_yes, float), floor, 1 - floor)).reshape(-1, 1)
return LogisticRegression(C=1e6).fit(x, y) # effectively unregularised: two parameters
def apply_platt(model, p_yes, floor=FLOOR):
x = logit(np.clip(np.asarray(p_yes, float), floor, 1 - floor)).reshape(-1, 1)
return model.predict_proba(x)[:, 1]
Platt's slope also corrects compression toward 0.5, which independent work reports for Jev and which a pure shift cannot fix.
scikit-learn ≥ 1.8 has method="temperature" alongside "sigmoid" and
"isotonic". cv="prefit" was deprecated in 1.6 and 1.9.1 rejects it with
InvalidParameterError; wrap an already-fitted model in FrozenEstimator:
from sklearn.calibration import CalibratedClassifierCV
from sklearn.frozen import FrozenEstimator
calibrated = CalibratedClassifierCV(FrozenEstimator(clf), method="temperature").fit(X_cal, y_cal)
Fit one correction per question. Never share one across Noul, Choice and Score, or across fields.
Multiple-choice selectors favour positions:
jev-1.13 "leans toward the option that comes first".Cyclic permutation averages over n orders instead of n!. With Jev, all the orders go into one request: questions are independent and share one read of the state, so the extra orders cost tokens, not a round trip.
def rotations(criteria, limit=None):
"""Cyclic rotations of a Choice's options (Zheng et al., ICLR 2024): n orders, not n!."""
items = list(criteria.items())
n = len(items)
count = n if limit is None else min(limit, n)
shifts = sorted({round(j * n / count) % n for j in range(count)}) # evenly spread, first order kept
return [dict(items[i:] + items[:i]) for i in shifts]
def order_checked_choice(client, state, instructions, criteria, limit=None):
"""Ask the same Choice under several option orders in ONE request and average.
Questions in a request are independent and share one read of the state, so
the extra orders cost only their question tokens, not another round trip.
"""
from typesafe_sdk import Choice
orders = rotations(criteria, limit)
questions = {f"order_{i}": Choice(instructions=instructions, criteria=c) for i, c in enumerate(orders)}
result = client.system_one(state, questions)
answers = [result.choices[key] for key in questions]
averaged = {o: sum(a.probabilities[o] for a in answers) / len(answers) for o in criteria}
winner = max(averaged, key=averaged.get)
stable = all(a.choice == winner for a in answers)
return winner, averaged, stable
limit (e.g. 3–4 evenly spaced rotations).stable=False rate justifies
the tokens.def risk_coverage(confidence, correct):
"""For each candidate bar: the share of items kept and the error rate among them."""
confidence = np.asarray(confidence, float)
correct = np.asarray(correct, bool)
rows = []
for bar in np.unique(confidence):
kept = confidence >= bar
rows.append((bar, kept.mean(), 1 - correct[kept].mean()))
return rows
def lowest_bar_for(target_error, confidence, correct, min_kept=30):
"""Lowest bar whose kept items err at most target_error, with at least min_kept items."""
for bar, coverage, error in risk_coverage(confidence, correct):
if error <= target_error and coverage * len(confidence) >= min_kept:
return bar, coverage, error
return None
When acting needs a guarantee and a single label is too strong, return a set. Use split conformal with the LAC score (Kumar et al. 2023), on calibrated probabilities:
def conformal_qhat(p_true_label, alpha):
"""p_true_label: calibrated probability each calibration item gave its *true* label."""
scores = np.sort(1.0 - np.asarray(p_true_label, float))
n = len(scores)
k = int(np.ceil((n + 1) * (1 - alpha)))
return 1.0 if k > n else scores[k - 1] # too few items: the set must be everything
def prediction_set(probabilities, qhat):
return [option for option, p in probabilities.items() if 1.0 - p <= qhat]
| Provider | Class probabilities | Notes |
|---|---|---|
| TypeSafe Jev | yes, native | probabilities / noul, rounded to 0.01 |
| Anthropic (Claude) | no | Messages API has no logprobs; OpenAI-compat logprobs/top_logprobs are "Ignored", response field "Always empty". Structured outputs give a typed enum, not a distribution |
| OpenAI | yes, restricted | logprobs + top_logprobs 0–20. On GPT-6, with reasoning effort other than none these must be removed — no logprobs from a reasoning model that is reasoning |
| Google Gemini | documented, not on 3.x | responseLogprobs: true, logprobs 0–20; text/x.enum for enums. A Google staff member stated on the developer forum (2026-08-05) that "logprobs are no longer returned for 3.X models"; Vertex users report 2.5 losing them too. Test the exact model |
| vLLM | yes | logprobs (cap max_logprobs, default 20); structured choice output. Default logprobs_mode is raw_logprobs — before logit processors, so a constraint is not reflected |
| llama.cpp server | yes | n_probs; post_sampling_probs for after the sampling chain; grammar / json_schema constraints |
With logprobs, constrain the label to a single token. For a single-token enum, a grammar's renormalisation is exactly the conditional class posterior. For multi-token labels it is not.
Without logprobs (Claude), each option costs more than one call:
Post-training makes this worse: GPT-4's MMLU ECE went from 0.007 pre-trained to 0.074 after RLHF (GPT-4 Technical Report, Fig. 8). Calibrate whatever you end up with.