Skip to content
learnjev
Tutorial 02/13FoundationsBeginner11 min

Noul, Choice and Score

Every judgment you will ever ask Jev for has to fit one of three shapes. They look interchangeable and they are not: they return different fields, behave differently at the margins, and TypeSafe's own docs publish a worked example where the same question asked two ways disagrees flatly.

By the end you'll be able to

  • Choose the right primitive for a given judgment
  • Write criteria that don't fight the instructions
  • Read probabilities rather than just the summary value
  • Avoid the Noul-as-a-spectrum trap
On this page
TypeAnswersReturnsLimits
NoulIs this true?noul (0–1)no confidence field
ChoiceWhich of these options?choice, probabilities, confidenceup to 255 options
ScoreWhich level?score, legend, probabilities, confidence2 to 10 levels
The complete surface area of Jev. There is no fourth type, no free-text extraction, no numeric regression.

Noul — the probability that something is true#

A Noul is a yes/no question that returns the probability of yes. TypeSafe has never said what the word stands for; treat it as a coined name. instructions is required, criteria is optional and takes a true/false pair when you want to pin down what each side means.

Python
from typesafe_sdk import Noul

questions = {
    "refund_requested": Noul(
        instructions="Does the customer request a refund?",
    ),
    "policy_covers_it": Noul(
        instructions="Does `refund_policy` cover this situation?",
        criteria={
            "true": "The policy explicitly permits a refund here",
            "false": "The policy is silent or excludes this case",
        },
    ),
}

Choice — one option from a known set#

A Choice takes a criteria map of {option: description}. The description can be null when the option name speaks for itself. You get back the winning option, the full distribution over every option, and a confidence score.

Python
from typesafe_sdk import Choice

Choice(
    instructions="Which team should handle this",
    criteria={
        "billing": "Payment or subscription issues",
        "technical": "Bugs or integration problems",
        "sales": "Pricing or account questions",
    },
)
choicedepartmentWhich team should handle this
choicetechnical
confidence0.82
  • technical
    0.85
  • billing
    0.08
  • sales
    0.07
From TypeSafe's API reference. The `choice` field is just the argmax — the distribution underneath it is the part worth acting on.

TypeSafe's guidance is to give the full list rather than a shortlist: options cost only a few tokens each, and the ceiling is 255. Above that cardinality their own sampler runs a two-stage process — score everything independently, then make an explicit pick — which is what causes the occasional slowdown they mention in the Wikiracing demo.

Score — a position along levels you define#

A Score takes an ordered array of level descriptions, low end first, between two and ten of them. The returned score is the probability-weighted position across those levels, so it can land between them: three levels give you a range of 0 to 2, and a score of 1.6 sits between the second and third.

Python
from typesafe_sdk import Score

Score(
    instructions="How frustrated the customer appears",
    criteria=[
        "Calm, just stating facts",
        "Frustrated but civil",
        "Very angry, strong language",
    ],
)
scorefrustrationHow frustrated the customer appears
score1.60
confidence0.78
  • 0Calm
    0.05
  • 1Frustrated
    0.30
  • 2Very angry
    0.65
From TypeSafe's API reference. `legend` echoes your levels back by index, which is what makes the score readable in a log six months later.

Picking between them#

TypeSafe's rule of thumb is the useful one: prefer the type whose answer your code can act on directly. A Choice between refund, rebook and information maps onto three branches. A Score maps onto a threshold. A Noul maps onto an if. If two types both seem to fit, the one that needs less translation in your code is the right one.

  • Unordered categories — routing, document type, language detection → Choice.
  • An ordered spectrum you can describe — severity, frustration, seniority, evidence strength → Score.
  • A clean predicate where the probability itself is the signal — does this ask for a refund, does this mention distributed systems → Noul.

The trap: the primitives do not agree with each other#

This is the most important thing on the page, and it is published by TypeSafe rather than discovered by a critic. Ask the same question as a Noul and as a yes/no Choice and you can get answers that point in opposite directions. On the ticket "I'm not happy with the fit. What are my options here?", asked as "Is the customer asking for a refund?":

Noul `noul`Choice `yes`Choice `no`Choice `confidence`
0.220.010.990.97
Same question, same ticket, two primitives. Published on TypeSafe's jev-1.13 jaggedness page.

And two Nouls that are logical negations of one another do not sum to 1. On "I was charged twice for the same order. Can someone look into this?":

`refund``not_refund`Sum
0.720.471.19

That difference is usable rather than merely annoying. TypeSafe's skill-suggestion cookbook uses both on the same shortlist: a Choice to pick which skill, and Nouls to decide whether any skill should be suggested at all. Those are genuinely different questions, and the two primitives are how you ask them.

Mix them freely in one request#

Every question in a request sees the same state, is evaluated independently and in parallel, and returns under the key you chose. There is no reason to send them separately.

triage.py
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

client = TypeSafeClient()

response = client.system_one(
    state=ticket,
    questions={
        "department": Choice(
            instructions="Which team should handle this",
            criteria={
                "billing": "Payment or subscription issues",
                "technical": "Bugs or integration problems",
                "sales": "Pricing or account questions",
            },
        ),
        "frustration": Score(
            instructions="How frustrated the customer appears",
            criteria=[
                "Calm, just stating facts",
                "Frustrated but civil",
                "Very angry, strong language",
            ],
        ),
        "is_urgent": Noul(
            instructions="The message conveys urgency or time-sensitivity",
        ),
    },
)

print(response.answers["department"].choice)   # "technical"
print(response.answers["frustration"].score)   # 1.035
print(response.answers["is_urgent"].noul)      # 0.999

Next#

The questions are only half the request. The other half is state, and how you shape it has more effect on accuracy than almost anything else. Continue to Designing state Jev can actually use.

Sources for this page

Last reviewed 2026-09-18. Jev is days old and moving — where a claim is TypeSafe's own rather than independently verified, this page says so in the sentence that carries it.