TypeSafeAPI.Answer (TypeSafe AI v0.1.0-alpha.3)

Copy Markdown View Source

Decodes one wire answer against the question that produced it, and turns any answer into a routing decision.

Decoding needs the question (to know which type is expected, and to check the answer against the options or levels that were actually sent) and the TypeSafeAPI.Keys registry built for the request (to hand ids and Choice options back under the caller's own atoms or strings, never by calling String.to_atom/1 on anything the API sent).

Decoding is strict: a probability that is not a number, an option or level the caller never sent, a Noul outside 0.0..1.0, or a choice that is not the highest-probability option all decode to an Error.unexpected/2 rather than to a struct that is confidently wrong. The typed layer exists to catch exactly these, and TypeSafeAPI.Result's raw still holds the response as it arrived.

gate/2 and confidence/1 work the same way across all three answer types, so a caller who only cares about "should I trust this" doesn't need to branch on which question it was. yes?/2 is Noul-only: a Choice or Score has no yes/no reading.

Summary

Functions

The confidence value gate/2 uses: max(noul, 1 - noul) for Noul, and the wire confidence for Choice and Score.

Decodes one answer from the wire answers object.

Routes an answer into :act, :review, or :escalate by comparing confidence/1 against two thresholds.

Whether a Noul answer clears a probability threshold. Defaults to 0.5.

Types

Functions

confidence(arg1)

@spec confidence(t()) :: float()

The confidence value gate/2 uses: max(noul, 1 - noul) for Noul, and the wire confidence for Choice and Score.

The API returns no confidence for a Noul answer, only the probability, so the Noul value is this library's convention rather than something the model reported. It reads distance from 0.5 as certainty: 0.92 and 0.08 both give 0.92, and 0.5 gives 0.5. That makes a Noul comparable to a Choice or Score in gate/2, but it is not the same measurement, so do not tune one threshold against numbers from the other. It is also the confidence field on TypeSafeAPI.Answer.Noul, so all three answer structs have the same shape.

decode(raw_answer, wire_id, question, keys)

@spec decode(map(), String.t(), TypeSafeAPI.Question.t(), TypeSafeAPI.Keys.t()) ::
  {:ok, t()} | {:error, TypeSafeAPI.Error.t()}

Decodes one answer from the wire answers object.

wire_id is the string key the answer was found under; question is the normalized question it answers. A question/raw_answer type mismatch, an unrecognized "type", a missing or invalid field, or a value that disagrees with the question all decode to an Error.unexpected/2.

gate(answer, opts)

@spec gate(t(), keyword()) :: :act | :review | :escalate

Routes an answer into :act, :review, or :escalate by comparing confidence/1 against two thresholds.

Both :act and :review are required. Raises ArgumentError if :act is lower than :review, since that would make the review band unreachable.

A Noul answer's confidence is max(noul, 1 - noul), which has a floor of 0.5, so :escalate is unreachable for a Noul unless :review is above 0.5. Rather than silently never escalating, gate/2 raises ArgumentError when a Noul answer is given a :review threshold of 0.5 or lower. A Noul sitting at 0.5 is the most uncertain answer the model can give, so the useful escalation threshold is just above it (review: 0.55, say). Choice and Score confidences come from the API and can be anything in 0.0..1.0, so they take any thresholds.

yes?(answer, threshold \\ 0.5)

@spec yes?(TypeSafeAPI.Answer.Noul.t(), number()) :: boolean()

Whether a Noul answer clears a probability threshold. Defaults to 0.5.

Noul answers only. A Choice or Score answer has no yes/no reading, so this raises ArgumentError rather than inventing one; use gate/2 to route on confidence, or compare the fields you care about instead.