Lesson 2 · Choice, Score, Noul

About 30 minutes · four recorded call transcripts to read against

What a question is made of concept

Goal: name the four parts of a question and the three parts of a request.

A request to Jev is one JSON object with three keys (docs: API):

{
  "state": /* string | object | array of strings: the thing to judge */,
  "model": "jev-latest",             /* or a pinned version like "jev-1.13.0" */
  "questions": {
    "your_id": {                     /* 1. id: your key, never shown to the model */
      "type": "choice",              /* 2. type: choice | score | noul */
      "instructions": "…",          /* 3. what to judge, in plain words */
      "criteria": { … }              /* 4. the options, the levels, or the yes/no descriptions */
    }
  }
}
idAny string. It is the key the answer comes back under. The model never sees it, so q1 and caller_wants_quote behave identically. Name them for the person reading the code.
typeOne of the three primitives. Nothing else exists.
instructionsWhat to judge. Usually a sentence. Can also be a JSON object or array when structure helps (lesson 3).
criteriaChoice: a map of option → description (or null). Score: an ordered array of level descriptions. Noul: optional {true: …, false: …}.

Two rules that shape everything else

  • Every question is evaluated independently and in parallel. Question B never sees the answer to question A. If B depends on A, that is two requests, or one request with B asked for every possible A (docs: multiple questions).
  • The budget is generous. 64k tokens per request, 32k for the state plus the longest question (docs: Models). Asking ten questions about one transcript is normal.

The running example for the rest of this lesson is a small solar installer's phone line. Four short transcripts, five questions, all recorded live on 2026-09-19. Level 5 puts them all in front of you; levels 2 to 4 use them one primitive at a time.

Choice: one option out of a closed set primitive

Goal: write a Choice with good options and read its probabilities.

"outcome": {
  "type": "choice",
  "instructions": "What the caller wants from this call",
  "criteria": {
    "sales_lead":     "Caller wants to buy, get a quote or book a consultation",
    "support":        "Existing customer with a technical or billing problem",
    "vendor_or_spam": "Caller is selling something to the company",
    "unclear":        "Too little information to tell what the caller wants"
  }
}
Options2 to 255 keys. Keys are labels for you; the model reads key and description together, so a key like vendor_or_spam already carries meaning.
DescriptionsMay be null when the key is self-explanatory. Describe when two options could overlap, or when the label is company jargon.
An escape hatchThe docs recommend an other or unclear option (docs: Choice). Without one, a cut-off call still gets forced into a real category.

The response carries three things: the winning choice, a probability for every option (they sum to 1), and a confidence between 0 and 1. Run the two transcripts below and watch the outcome question only; the others come later.

Simulated playground: outcome on two calls

Reading it

Both calls are unambiguous and the numbers say so: sales_lead at 1.0 with confidence 1.0, vendor_or_spam at 1.0 with confidence 0.99. Compare with lesson 1's Stripe message (0.69 / 0.31, confidence 0.53). Same primitive, very different shape of answer. The probabilities are the answer; the top label is a summary of them.

For a messier example straight from the docs: a shoe-store ticket asked "which department" came back returns 0.60, billing 0.38, shipping 0.02 with confidence 0.39, because the customer wanted a refund and complained about delivery (docs: Choice, full example). That is a ticket for a human, not a bug in the model.

Score: a position on a ladder primitive + calculator

Goal: explain why a score of 1.12 is not an error, and why "levels 1 to 3" is a bad ladder.

"caller_mood": {
  "type": "score",
  "instructions": "How the caller comes across",
  "criteria": ["Friendly or neutral", "Impatient or worried", "Angry or hostile"]
}

Criteria is an ordered array of 2 to 10 level descriptions. Level 0 is the first entry. The model never sees the numbers; it judges each description independently and returns a probability per level. The score is the probability-weighted mean of the level indices: Σ level_index × probability (docs: Score). So it can, and often does, land between two levels.

Score calculator

Drag a level's probability; the others rescale to keep the total at 1. Starting point: the docs' "spinner never finishes" bug, judged 0 / 0.88 / 0.12 on a Cosmetic, Workaround, Blocking ladder, score 1.12.

Describe situations, not degrees

The same call was asked for its mood twice on 2026-09-19. With described levels it scored 1.00 with confidence 0.99. With the levels written as ["1", "2", "3"] and the instruction "rate the caller mood from 1 to 3", it scored 1.46 with probabilities 0.02 / 0.50 / 0.48 and confidence 0.26. The model has no idea what a "2" is. The docs report the same effect on their bug-severity ladder: numbers-only levels gave 0.57 with confidence 0.35, and adding a matching example to each level lifted confidence from 0.54 to 0.90 (docs: writing good criteria).

Simulated playground: the numbers-only ladder, recorded

Two more things from the docs worth keeping: use the extra levels only when each one is a genuinely distinct situation (3 to 5 is the sweet spot for most workflows), and remember that legend in the response maps indices back to your descriptions so logs stay readable.

Noul: a yes/no with a number attached primitive

Goal: phrase a Noul so that high means yes, and know what it does not promise.

"needs_attention_today": {
  "type": "noul",
  "instructions": "The caller describes a problem that needs attention today"
}

"callback_requested": {
  "type": "noul",
  "instructions": "The caller asks to be called back or agrees to an appointment",
  "criteria": {
    "true":  "An explicit request for a call, or accepting a proposed time",
    "false": "No follow-up contact is requested or agreed"
  }
}
One numbernoul from 0 to 1, the probability the statement holds. There is no confidence field on a Noul; the number already carries it (docs: Noul).
Phrase as a statementWrite it so that high means yes: "the caller is angry", not "is the caller calm?". Keep negations out; ask the positive and invert in code if needed.
Optional criteria{true: …, false: …} sharpens the boundary when "yes" needs a definition, as with the callback above.

Recorded on the four calls

Callcallback_requestedneeds_attention_todayReading
1 · roof quote, agreed a visit0.950.03Book it, no rush
2 · inverter fault, second day dark0.070.93Escalate today, nobody asked for a callback
3 · cut off after ten words0.050.20Nothing to act on
4 · cold caller0.040.07Ignore
jev-1.13.0, recorded 2026-09-19. Full transcripts in level 5.

Nouls are independent estimates, not two halves of one answer

On call 2 the statement "the caller is angry" scored 0.82 and "the caller is not angry" scored 0.29. They add to 1.11. The docs document the same effect and say it plainly: structural invariants like noul + not_noul = 1 are not guaranteed, and a Noul and a two-option Choice asking the same thing can disagree (docs: Jev 1.13 jaggedness). Ask one Noul per fact, phrased positively, and threshold it in code.

All four calls, all five questions hands-on

Goal: predict every answer before revealing it, and notice where you were surprised.

The five questions: outcome (Choice), callback_requested (Noul), caller_mood (Score), language (Choice with null descriptions), needs_attention_today (Noul). Pick a call, read the state, write your five predictions down, then run the replay. The live button opens the same request in the Playground on your own account.

Simulated playground: the solar hotline

What the recordings show

  • German works here. Call 2 and 3 are German; every answer landed where a human would put it. The docs say English is the primary language and others may score lower (docs: language support). Four calls are not a benchmark; the community's 40-call run had one German miss (lesson 4).
  • The cut-off call went to unclear at 0.99. That only worked because unclear existed. Delete it and the model must pick a real category for ten words of nothing.
  • Mood on the cut-off call scored 0.11, confidence 0.84. Mostly neutral, a little worried. A fractional score on a fragment is the honest answer.
  • Every request cost between 532 and 591 input tokens, about $0.000025, and the model's own evaluation time was 80 to 125 ms. Network adds the rest; the benchmark measured 319 ms median end to end from Europe.

The response envelope, for completeness: model (the versioned ID that actually answered, even when you asked for jev-latest), answers keyed by your ids, and usage with input_tokens and output_tokens (docs: API response).

Quiz, then one request of your own quiz

Goal: three primitives, read cold.

1. A Score over three levels returns 1.46 with probabilities 0.02 / 0.50 / 0.48 and confidence 0.26. Best reading?

That is the recorded numbers-only ladder. A near-even split between two levels with low confidence means the descriptions did not let the model tell them apart. Describe situations.

2. Which field does a Noul answer NOT contain?

Noul returns noul only. Confidence exists on Choice and Score, where it summarizes a whole distribution.

3. Two Nouls, "caller is angry" and "caller is not angry", return 0.82 and 0.29. What does this tell you?

Documented behaviour: no structural invariants across questions. Ask the positive statement once and threshold it in code.

4. Why add an unclear option to a Choice about call outcomes?

Probabilities must sum to 1 across the options you give. If the honest answer is "nothing to see", it needs a place to go.

Ship it

Take one of the three decisions you wrote down in lesson 1. Write it as one Choice, one Score and one Noul that all make sense for the same input, open the Playground, paste a real (anonymised) input, and run. Keep the JSON; lesson 3 will improve it.

Primary source: the three primitive pages, Choice, Score, Noul. Each has a complete request and response with numbers.