Skip to main content
This cookbook takes one auto-insurance claim, runs a 14-question rubric over it 15 times, and checks whether each answer holds still across the repeats. Every check is a Noul, so each answer is P(true) for one True/False question. In a claims-triage pipeline, which sorts incoming claims into pay, deny, or send-to-a-human, probabilities guide the decision. Small changes near a threshold can change which action is taken. The rubric is 14 Nouls, and each run is one call that answers all 14. We do NUM_SAMPLES = 15 repeats per condition, where a condition is one model plus one setting, and show every probability that came back. The conditions:
  • Non-reasoning LLMs claude-haiku-4-5 and gpt-5.4-mini, at temperature 0 and the API default.
  • The same two non-reasoning models in True/False mode: one bare yes or no per question, mapped to 1.0 and 0.0.
  • Reasoning LLMs gpt-5.5 and claude-opus-4-8, which have no temperature dial.
  • TypeSafe: one system_one call over the 14 Nouls, with a fresh uid field (a throwaway unique value) on each call.
What to look for: the LLM answers move from run to run, at temperature 0 too, and on the judgment calls the models disagree with themselves. TypeSafe’s mean per-question probability standard deviation is 0.0102, below all LLM probability conditions here. Its covered answers span 0.43 to 0.53, crossing a 0.5 decision threshold. We also turn probabilities from 0.30 through 0.70 into an explicit uncertain outcome for human review. The final illustration maps TypeSafe probabilities to these actions while keeping the underlying probabilities visible.

Setup

then set TYPESAFE_API_KEY, ANTHROPIC_API_KEY, and OPENAI_API_KEY. This run uses jev-latest on the production API, sampled on 2026-09-11.

The state: an auto-insurance claim, as JSON

One claim with a few borderline calls built in:
  • The loss happened at a track-day event (the policy excludes “track/competitive driving”), but in the parking lot while the car was stationary, not on the circuit.
  • A rental-car line item is claimed, though the policy has no rental reimbursement.
  • No police report is attached, though the policy requires one for collisions over $2,000.
  • An auto-triage note already marks the claim “approved, pay full amount” before any human review, and without withholding the deductible.
Some rubric questions below are clear-cut; several are the borderline kind where sampled LLM answers scatter and the models disagree. The claim is a JSON structure. The LLMs get json.dumps(CLAIM) in the prompt; TypeSafe takes the structure as the state directly.

The rubric: 14 Nouls

One key -> question entry per row, phrased so a yes means the thing we are checking for is true. That keeps every row comparable: each model’s probability and TypeSafe’s noul measure the same thing.

How we ask

Each LLM call is one prompt holding json.dumps(CLAIM) and all 14 questions. The model returns a JSON object mapping each question’s key to a probability. Calls route to Anthropic or OpenAI by model name: non-reasoning models take a temperature (0 or the API default), reasoning models think first and take no temperature. The non-reasoning models also run a True/False variant: they answer each question with a bare yes or no, which we map to 1.0 and 0.0. This forces a hard decision and shows what these models do when they cannot leave any mass in the uncertain middle. The TypeSafe call is one system_one request over the same claim and the same 14 Nouls. Each answer’s noul is P(true). Every query also gets a fresh uid, a throwaway unique value that changes each run while leaving the claim and rubric unchanged. It appears in the LLM prompt and as an extra field in the TypeSafe state. This setup cannot separate sensitivity to the irrelevant field from variation that would occur on identical requests.
Note - despite the “ONLY a JSON object” instruction, claude-haiku-4-5 wraps nearly every reply in a ```json ... ``` fence that strict json.loads rejects (the other models return bare JSON). The helper peels the fence; a reply that still fails to parse becomes a parse failure, counted but not scored.
Each helper returns the answer, an estimated cost, and the round-trip latency.

Experimental Conditions

Experiment Grid

  • Each ✓ marks one condition with 15 repeats; — means the combination is not tested.
  • The default column sends no temperature argument: non-reasoning models use the API default, and reasoning models and TypeSafe run without a temperature setting.
  • Yes/no answers map to 1.0 / 0.0.
  • Temperature 0 is commonly suggested for repeatability, so we compare it with the API default.
We draw NUM_SAMPLES = 15 repeats per condition. Each repeat has its own cache key and counts as a distinct draw, and the cache (json_cache.json) ships with the cookbook, so re-rendering reuses it and spends no API calls. Delete the cache to sample live again.

Cost + speed (per rubric query)

Costs below use the historical price assumptions in Setup, including the speed_latest rate for TypeSafe. They are not verified jev-latest prices or current billing amounts. One row is one full 14-question rubric call. time/call and cost/call average the 15 calls, and the vs ts_noul columns divide by the TypeSafe figures.
In this run TypeSafe has a mean round-trip latency of 111ms. The LLM conditions range from 1.1 to 13.9 seconds per call under the concurrency settings above.

Plot: every sample as a heatmap

How to read it:
  • Outer row group: the question.
  • Inner row: the condition.
  • Column: one full rubric call.
  • Cell color: red is a higher P(yes), green is lower. For the risk questions, red usually means flagged.
typesafe_noul varies most on covered (0.43 to 0.53) and exclusion (0.53 to 0.62). Some LLM rows vary at temperature 0 too. Conditions disagree on judgment calls.
output The clear factual checks hold steady across most conditions. The judgment-heavy ones are where the LLM rows move: exclusion, rental_eligible, fraud_flag, and manual_review shift across samples or disagree across models. TypeSafe’s covered row crosses 0.5; its other 13 questions stay on one side of that threshold throughout this run.

Allow an uncertain decision instead of forcing yes or no

With a threshold of 0.5, probabilities 0.49 and 0.51 cause opposite actions even though both express substantial uncertainty. The application can instead return:
  • no below 0.30;
  • uncertain from 0.30 through 0.70, including both boundaries;
  • yes above 0.70.
Send uncertain cases to a human. This is application logic over the returned probability, not a new question or another API call. The band is illustrative, not a calibrated guarantee or optimized threshold. Set production boundaries using labeled examples and the cost of incorrect decisions and review. The illustration below applies this band to the recorded TypeSafe probabilities.
output A review band can absorb fluctuations around 0.5 without issuing opposite automatic actions. Values near its outer boundaries can still move between uncertain and yes or no. This does not make the model deterministic or prove automatic decisions are correct.

Open it in the TypeSafe playground

The link below opens the same claim and rubric in the playground: one claim, the same 14 Nouls, and TypeSafe jev-latest. It omits the changing uid field used above.
Open this claim + rubric in the TypeSafe playground →