Calibrate Your Policy
A confidence of 95% is only useful if answers given at 95% are right about 95% of the time on your data. This page shows how to check that for your own policy and pick an escalation threshold. You need a policy (Your first decision policy) and some labelled examples.
1. Label a sample
Section titled “1. Label a sample”Collect real inputs with the answer you consider correct, one JSON object per line. Columns named
expected_<question id> are labels; the rest are template variables:
{"id": "r01", "request": "Ordered a blue kettle, the box had a red toaster in it.", "expected_refund_eligible": "yes", "expected_reason": "wrong_item"}{"id": "r04", "request": "Package never showed up, tracking stuck for 10 days.", "expected_refund_eligible": "no", "expected_reason": "late"}Aim for 100+ labelled answers per question, including the hard cases you actually see; a sample of easy cases will look perfectly calibrated and tell you nothing.
2. Run the policy on it
Section titled “2. Run the policy on it”Use the dataset runner from Run a dataset
(set TEMPLATE_PATH, DATASET_PATH and your gateway URL). It writes results_receipt.jsonl with each
question’s answer, confidence and hesitation. Keep samples at what you will use in production.
3. Read the report
Section titled “3. Read the report”python3 scripts/policy_calibration.py results_receipt.jsonlExample from a 12-row refund dataset (illustrative size; recorded on a Vertex G4 endpoint):
== refund_eligible: n=12 accuracy=91.7% Brier(top)=0.0769 ECE(10 bins)=0.0937 confidence bin n mean conf accuracy 0.7-0.8 1 0.793 1.000 0.9-1.0 11 0.993 0.909 keep if hesitation < kept accuracy of kept escalated 5% 75.0% 100.0% 3 16% 83.3% 100.0% 2 35% 91.7% 90.9% 1 any 100.0% 91.7% 0- Accuracy: share of answers that match your labels.
- Brier (top answer) and ECE: how far confidence is from accuracy (0 is perfect). The reliability table shows it bin by bin: answers given at ~99% were right 91% of the time here.
- Hesitation thresholds: what happens if every answer above the cut-off goes to a person or a larger model. At 16% the policy handles 83% of answers on its own, all correct in this sample, and escalates 2 of 12.
The one error is instructive: “package never showed up” was answered eligible at 94% confidence, yet its hesitation was 34%. On a yes/no question 94% vs 6% is still a noticeable split, which is why hesitation (entropy scaled to the number of options) is a better gate than raw confidence. The label itself is also debatable: the policy text only mentions damaged or wrong items. Errors on a small sample are often policy wording, not the model.
4. Choose what to do with hesitant answers
Section titled “4. Choose what to do with hesitant answers”| Situation | Action |
|---|---|
| Few hesitant answers, errors are costly | Escalate above ~16% hesitation to a person |
| Many hesitant answers, a larger model is acceptable | Stage 2 cascade: "cascade_mode": "entropy" sends only hesitant items to Gemini (authoring guide) |
| Hesitation clusters on one question | Rewrite that question or its option descriptions; add missing options (taxonomy discovery) |
| You need an error bar per decision | samples: 4 adds standard error and agreement (~+35–40 ms) |
| Confidence is systematically too high or too low | Fit a temperature on held-out data (below) |
What we measured on public benchmarks: an offline cascade escalating answers above 16% hesitation to Gemini reached 221/231 on JevBench with 39% escalated (Gemini on everything: 225) and 48/50 on the calibration suite with 34% escalated (EXP-14). Your escalation rate will depend on your data.
5. Temperature scaling (optional)
Section titled “5. Temperature scaling (optional)”A temperature rescales probabilities after the fact: softens overconfident answers, sharpens them, and the answer itself never changes. Fit it on your own labelled sample, on data you did not use to choose it. There is no single served default: a temperature fitted on our other development suites and applied to a new one made calibration worse on 4 of 8 suites, and so did one per question type (PROP-20). Fitted on the suite itself (held out by 5-fold cross-validation), it helps on every suite.
Fit it. For the built-in suites, --auto-temperature fits by 5-fold cross-validation and scores each item with
the fitted on the other folds (the same folds as the regression matrix). Its temperature_fit block lists the fold
temperatures and raw vs held-out ECE:
./bin/dgem bench-calibration --from-receipt <receipt.json> --auto-temperature./bin/dgem bench-jev --from-receipt <receipt.json> --auto-temperatureFor your own policy, run it on your labelled rows (the dataset runner) and fit one per question type the same way. On our suites yes/no questions wanted about 3, choices about 1.3 and scores about 1.1.
Use it. Put the fitted value in the policy, or send it per request:
| Where | How |
|---|---|
| Template | "temperature": 1.4 or "temperature": {"noul": 3.0, "choice": 1.3, "score": 1.1} next to "questions" |
dgem decide | --temperature 1.4 (overrides the template) |
Gateway POST /api/decide | "temperature": 1.4 or the per-type object (overrides the template) |
| MCP decide tools | temperature and temperature_by_type |
| Web Studio | Temperature field next to Prompt layout (also used by the batch runner) |
/v1/systemone adapter | dgem systemone serve --temperature, or dgem serve --systemone-temperature |
The temperature is applied after Stage 1 and after the Gemini cascade: the cascade still gates on raw entropy, and
answers the cascade resolved are left as Gemini gave them. Responses report the temperature applied in temperature.
6. Re-check when things change
Section titled “6. Re-check when things change”Re-run the report after changing the policy wording, the options, samples, or the serving image. A new vLLM
version can change a few percent of individual answers even when overall accuracy is unchanged
(image parity run).