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) // how concentrated those probabilities are, 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 how concentrated that distribution is. Confidence is not the winner's probability: 0.45 against a runner-up of 0.44 is a very different situation from 0.45 with the rest scattered thinly.
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.13.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.82
},
"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.78
}
},
"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). Currently jev-1.13.0. |
jev-preview |
The latest release, whether or not it's official. |
jev-1.13.0 |
A pinned version. |
Aliases move when TypeSafe ships a new release. If you've tuned thresholds against a particular version, pin it by name; see Making Calls.
Jev reads text only: strings, JSON objects, and arrays of text. English is its primary language; other languages work, but test them on your own data. It isn't trained on customer requests.