Concepts¶
System One models¶
Large language models produce text for people to read. When code needs a decision, that means coaxing a text generator into structured output and parsing it back. Jev is TypeSafe's System One model, named after the fast, intuitive "System 1" thinking described in Thinking, Fast and Slow. It never generates text. It evaluates typed questions against a state and returns typed answers with probabilities.
| Text LLM | Jev | |
|---|---|---|
| Output | Prose to parse | Typed answers, constrained to the options you define |
| Uncertainty | Implicit, often overconfident | Calibrated probabilities on every answer |
| Speed | Seconds | About 100 ms per request |
| Cost of more questions | Longer prompts, longer answers | A few extra input tokens; output is free |
Jev is trained with reinforcement learning for calibrated decisions: across many answers, outcomes given probability 0.8 happen about 80% of the time. That's a property of groups of answers, not a guarantee about any single one.
State, questions, and answers¶
Every request has three parts.
State
: The content Jev reads: a message, a document, a JSON record, or any @Serializable value. See
State.
Questions : Typed judgments about that state, each with an id you choose. The id is only for your code; it's never sent to the model, so each question's instructions must state the whole question.
Answers : One typed answer per question, returned under the same id.
Here's one request with a question of each type:
val result =
jev.query(state = "The export button crashes the settings page in Safari. It works in Chrome.") {
// Every question below is answered in parallel, in one request, against the same state.
noul("is_bug", "Does the message report something that is broken?")
noul("has_workaround", "Does the message mention a way to get the task done anyway?")
choice("area", "Which part of the product is affected?") {
options("settings", "billing", "reports", "other")
}
score("severity", "How severe is the reported issue?") {
level("Cosmetic; no impact to functionality")
level("Broken or degraded feature, but a workaround exists")
level("Blocking issue; no workaround exists")
}
}
And here's what comes back:
val isBug = result.noul("is_bug") // NoulAnswer
println(isBug.noul) // probability of yes, 0..1
val area = result.choice("area") // ChoiceAnswer<String>
println(area.choice) // the most likely option, e.g. "settings"
println(area.probabilities) // every option -> its probability, summing to 1
println(area.confidence) // the winner's lead over an even split, 0..1
val severity = result.score("severity") // ScoreAnswer
println(severity.score) // probability-weighted level, e.g. 1.3 (between levels 1 and 2)
println(severity.probabilities) // level number -> probability
println(severity.confidence)
Questions run in parallel¶
All questions in a request are evaluated in parallel and independently against the same state.
- Asking more barely changes latency. It costs only the tokens of the extra questions. That's why jev4k encourages putting every question about a state into one request, including speculative ones that only matter on some code paths.
- Answers don't influence each other. One question's answer is never context for another. If a later question truly depends on an earlier answer (to fetch more evidence, or to pick the next options), make a second request.
Probabilities and confidence¶
Every answer is a probability distribution over the answers you allowed. The model can't return anything outside it.
- A Noul answer is one number, the probability of yes. Near 0.5 means yes and no are about equally likely. It does not mean "medium"; measure degree with a Score.
- Choice and Score answers carry the full distribution plus
confidence, a 0–1 summary of it: 1 when all of the probability is on one answer, 0 when it's spread evenly. Confidence is not the winner's probability, but a Choice's depends on nothing else, so a close runner-up shows only inprobabilities.
Confidence & Thresholds shows how to act on these values.
What goes over the wire¶
jev4k builds exactly the JSON the TypeSafe API expects. This query:
val result =
jev.query(state = "Help! My payouts have been failing for 3 days.") {
noul("is_urgent", "Does this convey urgency?") {
whenTrue("Explicitly time-sensitive")
whenFalse("No urgency expressed")
}
choice("department", "Which team should handle this?") {
"billing" means "Payments, invoicing, refunds"
"technical" means "Bugs, outages, integrations"
"sales" means "Pricing, upgrades, new accounts"
}
score("frustration", "How frustrated is the customer?") {
levels("Calm", "Frustrated", "Very angry")
}
}
is sent as this request body to POST https://api.typesafe.ai/v1/systemone:
{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "Does this convey urgency?",
"criteria": { "true": "Explicitly time-sensitive", "false": "No urgency expressed" }
},
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
"sales": "Pricing, upgrades, new accounts"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Frustrated", "Very angry"]
}
}
}
and comes back as:
{
"model": "jev-1.14.0",
"answers": {
"is_urgent": { "type": "noul", "noul": 0.92 },
"department": {
"type": "choice",
"choice": "technical",
"probabilities": { "billing": 0.08, "technical": 0.85, "sales": 0.07 },
"confidence": 0.78
},
"frustration": {
"type": "score",
"score": 1.6,
"legend": { "0": "Calm", "1": "Frustrated", "2": "Very angry" },
"probabilities": { "0": 0.05, "1": 0.3, "2": 0.65 },
"confidence": 0.4
}
},
"usage": { "input_tokens": 312, "output_tokens": 48 }
}
jev4k maps that response to typed answers. It also keeps the raw body, the reported model, token usage,
and the x-typesafe-request-id header on the JevResult.
Models¶
| Name | Meaning |
|---|---|
jev-latest |
The latest stable release (the default). |
jev-preview |
The latest release, official or not. |
jev-1.14.0 |
A pinned version. |
Aliases move when TypeSafe ships a new release. On 2026-10-14 TypeSafe released jev-1.14.0 as jev-preview
and announced that jev-latest, then jev-1.13.0, would move to it about a week later. jev-1.13.0 stays
available for a while, and TypeSafe has said it will give 60 days' notice before deprecating it. If you've tuned
thresholds against a particular version, pin it by name; see Making Calls.
Jev reads text: strings, JSON objects, and arrays of text. It also reads PNG and JPEG images, and its Box question type finds where something is in an image; both are experimental, as Images & Box explains. English is Jev's primary language; other languages work, but test them on your own data. It isn't trained on customer requests.