Skip to content

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.