Skip to content

Jev Practices & Patterns

Choice, Score, or Noul? How to Pick the Right Primitive

The most common Jev design mistake is asking a question that does not match the primitive. Here is how to choose by the shape of the answer you need back.
Published
Category
Jev Practices & Patterns
Resources cited
5
Reading time
5 min
Tags#primitives#choice#score#noul#design

Every Jev request starts with the same design question: what shape is the answer? Jev can answer three kinds of questions, a Choice, a Score, or a Noul, and picking the wrong one is the most common reason a question comes back with a low confidence or a wrong label.

The primitives overview is the canonical reference. This is the design-side companion: how to choose between the three when you are staring at a feature request.

Start with the shape of the answer

Noul: one probability

A Noul asks a yes/no question and returns a single probability from 0 to 1. There is no separate confidence, because the probability already describes the whole distribution. In code, you threshold it:

wants_human = response.nouls["is_human_escalation"].noul > 0.9

Reach for a Noul when the answer is genuinely binary and both outcomes are actionable: is this urgent, does this message ask for a refund, does this draft contain a leaked secret. Optional criteria.true and criteria.false descriptions are how you pin down the boundary when the question is subtle.

Two design notes. First, ask one question per Noul; "is this urgent and about billing" is two questions wearing a trench coat. Second, a Noul is not a degree scale. A value of 0.7 does not mean "quite urgent", it means a 70% chance of yes. If you need intensity, that is a Score.

Score: an ordered spectrum

A Score rates content against an ordered list of 2 to 10 levels and returns a probability-weighted score that can land between levels. Levels are numbered by array position from 0, and the model sees the descriptions, not the numbers:

"severity": Score(
    instructions="How severe is the reported issue?",
    criteria=[
        "Cosmetic; no impact to functionality",
        "Broken or degraded feature, but workaround exists",
        "Blocking issue; no workaround exists",
    ],
),

Scores are the right primitive for triage, ranking, and rubric work. Two traps: levels are judged separately, so describing one level as "worse than the previous" adds no signal, and the primitive is weak at exact numeric calibration. Use the score to route into bands in code, not as a measurement. If you need several dimensions, break the judgment into atomic scores and combine them yourself, as the composite scoring pattern shows.

Choice: one of your options

A Choice picks one option from a set you define, up to 255, and returns the chosen option, the full probability distribution, and a confidence. When an option name is self-explanatory, its description can be null; otherwise the description is your chance to define scope, including what the option is not for.

Choice is the workhorse for classification, intent routing, and tool selection. For deep hierarchies, chain choices level by level rather than passing the whole tree: the hierarchical classification cookbook walks patent and product taxonomies that way.

A decision table

What you needPrimitiveWhat comes back
A yes/no gateNoulOne probability, thresholded in code
An intensity or severity bandScoreWeighted score, per-level probabilities, confidence
One label from a setChoiceLabel, full distribution, confidence
A label plus "how sure"ChoiceUse the confidence to route
A measurement like "how many"NoneCount in code, one question per item

That last row is deliberate. Counting and arithmetic are among Jev's known weak spots, covered in the jaggedness list. The primitive that does not exist is the one you should implement yourself.

Mix them in one request

Primitives compose. A single call can carry a Noul, a Choice, and a Score, and each runs in parallel:

questions={
    "billing": Noul(instructions="Is this ticket about billing?"),
    "tone": Choice(
        instructions="What is the customer's tone?",
        criteria={"calm": None, "frustrated": None, "angry": None},
    ),
    "urgency": Score(
        instructions="How urgent is this ticket?",
        criteria=["can wait", "this week", "today"],
    ),
}

Answers come back grouped by type as response.nouls, response.choices, and response.scores. The speculative fan-out pattern builds on this idea: ask everything the branches might need in one request, then let code ignore the answers it does not use.

The same three shapes in open models

When a question is answered by a downloaded checkpoint instead of the hosted API, the primitives survive but their implementations differ, and the differences show up at the edges of a question design. Encoder models like Laya score every option at its own marker token and softmax over the question's options, which keeps the answer space defined at request time — and it also means options share a fixed head budget, so a question with 77 labels gives each one three to four tokens and accuracy falls off a cliff. If your Choice is wide, that limit decides before your prompt wording does.

Score scales vary too. Liquid d1-3B takes 2–10 ordered levels and returns a legend alongside the score; AutoTrust's decision head uses fixed score slots rather than free-form levels. And Noul has a documented quirk in Laya's English checkpoint: the false/true label pair can dominate the answer, returning a confident no for clearly positive input. The card's workaround is to ask the same question as a two-option Choice with neutral keys — a reminder that the primitive is a contract, not a guarantee that every implementation reads it the same way.

The mapping above still holds. What changes is that the primitive's limits now come from the checkpoint you chose, and the selection guide walks through matching one to your question shapes.

When the answer is "not sure"

No primitive returns an "I don't know" label. Uncertainty shows up as a low confidence on a Choice or Score, or as a Noul value near the middle. Deciding what to do with those values is the actual design work, and it is the subject of confidence gates in production.

As a starting point: set thresholds per action by the cost of being wrong, keep human review in the middle band, and pin your questions and thresholds behind labeled fixtures before you automate anything.

The short version

  • Binary and both outcomes actionable: Noul.
  • Ordered intensity: Score, then band it in code.
  • One label from a list you control: Choice.
  • Numbers, dates, or counting: keep the model out of it.

Get that mapping right and most of the remaining design work is state, thresholds, and tests, which is exactly what the patterns section is for.

From the Jev directory

The resources behind this article.

Pick one option from a set you define. Returns the option, the full probability distribution, and 0-1 confidence, with up to 255 options and parallel questions that barely add latency.
Jev Practices & PatternsDocs#official#primitives#choice
Official
Rate content against 2 to 10 ordered levels. Returns a probability-weighted score that can land between levels, per-level probabilities, and confidence, with the arithmetic left to code.
Jev Practices & PatternsDocs#official#primitives#score
Official
The yes/no primitive: one probability from 0 to 1, where the value is the answer and no separate confidence is needed. True and false criteria pin down subtle boundaries.
Jev Practices & PatternsDocs#official#primitives#noul
Official
The three TypeSafe question types, the typed answers they return, how to choose between them, and how to ask several in a single call.
Jev Practices & PatternsDocs#official#primitives#choice
Official
Instructions, option descriptions, score levels, and noul criteria all accept JSON, so you can label question parts, pass schemas and taxonomies, and keep candidate paths alive.
Jev Practices & PatternsDocs#official#primitives#architecture
Official
Back to all articles

More Jev articles

How Pydantic AI, Mastra, TanStack AI, DSPy, Spring AI, and LiteLLM expose Jev: where the typed decision sits in the loop, what each integration returns, and which big frameworks are only adjacent.
Jev Integrations & Adapters#agents#frameworks#integrations
Read article
Tool menus can outgrow the context that reads them. The catalog's Jev-based routers and gates narrow the list: benchmarked tool search, per-tool scoring, fail-closed approvals, and WebMCP in the browser.
Jev Agents & MCP#mcp#tool-routing#agents
Read article

From the Jev AI model community

Posts from builders shipping with Jev right now.

Follow @typesafeai