Answers, Distributions and Explicit Policy

Copy Markdown View Source

SystemOneResponse is the single public response. evaluate enriches the existing NoulAnswer, ChoiceAnswer and ScoreAnswer; there is no second result wrapper. Responses retain raw, unknown_answers, raw HTTP response, request ID, model, usage, retry count and elapsed time. Semantic elapsed_ms includes local validation, Pristine execution and decoding; runtime_elapsed_ms retains Pristine's reported timing. Batch responses also carry zero-based batch_index.

team = TypeSafeSDK.Response.fetch!(response, :team)
{:ok, team} = TypeSafeSDK.Response.fetch(response, :team)
TypeSafeSDK.Response.choices(response)
TypeSafeSDK.Response.nouls(response)
TypeSafeSDK.Response.scores(response)

Fetch uses exact keys, not atom/string coercion. fetch! lists available IDs in its KeyError. Raw/parity responses use strings; semantic responses preserve the caller key type. Every typed answer retains its own raw payload and ID.

Relational validation

Known answers must match requested IDs and types. Choice selections and the probability-key domain must match submitted options. Score must lie in the requested range; legend and probability keys must match level indices. All probabilities/confidence lie in [0,1]. Distribution sums must be within probability_tolerance (default 0.02, permitted range 0..0.1); values are never silently renormalized. Tighten to zero when your application requires exact sums (up to the implementation's floating-point comparison epsilon).

A requested ID missing entirely is an error. An entry with an unknown future tag is retained/skipped, even for a known question; applications must handle a missing typed answer in that case. Unexpected known IDs fail. Negative or noninteger usage counts fail; absent optional usage counts remain nil. A structural/relationally valid answer can still make an incorrect judgment.

Information helpers

TypeSafeSDK.Answer.Choice.ranked(team)
TypeSafeSDK.Answer.Choice.margin(team)
TypeSafeSDK.Answer.confidence(team)

severity = response.answers.severity
TypeSafeSDK.Answer.Score.ranked(severity)
TypeSafeSDK.Answer.Score.expected_level(severity)
TypeSafeSDK.Answer.Score.max_level(severity)
TypeSafeSDK.Answer.Score.normalized(severity)

Choice ranking is descending; ties follow caller order (deterministic key order for unenriched legacy answers). Margin is the difference between the top two probabilities, NOT the provider's confidence. For a Score with probabilities %{0 => 0.45, 1 => 0.1, 2 => 0.45} and expected score 1, the rounded expected level is 1 but the modal helper returns 0, breaking equal probabilities toward the lowest index. Normalization divides by the highest legend index; legacy single-level rubrics return 0.0. Semantic scores retain original rubric labels and descriptions while preserving the server's legend separately.

Noul has no wire confidence. Answer.confidence(noul) derives max(p, 1-p); that describes certainty, not whether the answer is true or safe to act on. Answer.yes?(noul, threshold) compares probability of true; its convenience default is 0.5 and is not a recommended risk policy.

No hidden gate policy

case TypeSafeSDK.Answer.gate(team, act: 0.90, review: 0.70) do
  :act -> :application_may_continue
  :review -> :request_review
  :escalate -> :use_fallback
end

Both thresholds are mandatory, with 0 <= review <= act <= 1. These numbers are illustrative. A confident Noul false answer can return :act: separately inspect yes? before deciding which business action to perform. Confidence is not a calibration guarantee. Choose thresholds from task-specific evidence and costs.

Run mix run examples/live_semantic.exs for a complete live walkthrough. See the live example catalog for setup and API-call costs.