Skip to content
System One

Intent and model routing with System One models

One Jev call reads an incoming request and returns its intent as a label plus a difficulty rating on a scale you wrote. Your router reads both numbers and picks the handler: deterministic code, a cheap model, an expensive one, or a human queue.

The problem

Most requests arriving at an AI product don’t need the biggest model available. Someone asking where their order is needs a database lookup. Someone asking a policy question needs a specialist with the right documents loaded. Someone describing a situation nobody wrote a rule for needs a person. Sending all three to the same frontier model burns money on the easy ones and still gets the hard one wrong.

The usual fix is a router prompt: ask a cheap chat model which bucket a request falls into. That works until the router itself becomes the thing you’re debugging, because it answers in text, occasionally invents a bucket name, and gives you no signal when it was guessing. Jev replaces that with a typed answer. TypeSafe’s own use case map lists model routing as a job for Jev: build a custom router that chooses which model receives each prompt, set thresholds for your workflow, classify intent and domain, estimate difficulty and risk.

What the state looks like

State is what you want judged. Send the user’s message plus the small amount of surrounding context the routing decision depends on, and nothing else. A router that reads the whole conversation history is reading mostly noise, and Jev’s accuracy falls as unrelated material grows around the thing being judged.

{
  "message": "I ordered the wrong size and the return window closed two days ago, is there anything you can do?",
  "channel": "chat",
  "authenticated": true,
  "turn": 1
}

The questions you ask

Two questions, one call. One says what the request is, the other says how hard it looks.

from typesafe_sdk import Choice, Score, TypeSafeClient

client = TypeSafeClient()

questions = {
    "intent": Choice(
        instructions="The primary intent of this customer message",
        criteria={
            "order_status": "Asking about an existing order",
            "product_question": "Asking about a product before buying",
            "return_exchange": "Wants to return or exchange something",
            "complaint": "Unhappy with experience, wants resolution",
        },
    ),
    "complexity": Score(
        instructions="How complex is this request to resolve",
        criteria=[
            "Simple lookup or standard procedure",
            "Requires some judgment or multi-step process",
            "Unusual situation, edge case, or escalation needed",
        ],
    ),
}

response = client.system_one(state=message, questions=questions, model="jev-latest")

Intent is a Choice, a pick-one question over labels you define. You get back the winning label, a probability for every label that sums to 1, and a confidence value from 0 to 1 describing how concentrated that spread is. Because the labels are yours, the router can never return a bucket your code doesn’t handle. Up to 255 options fit in one Choice, which is more than most routing tables need.

Complexity is a Score, an ordered scale where you write one sentence per level. The returned number is the probability-weighted mean of the level positions counting from 0, so a request that sits between “standard procedure” and “needs judgment” comes back near 0.5 instead of being forced onto a rung. That in-between value is the useful part: it’s what separates a request a small model can finish from one that should go up a tier. The primitives guide has more on how the two shapes differ.

Decision policy

Jev hands back two readings. The router below is what actually spends money on a model call or a person’s time.

intent = response.answers["intent"]
complexity = response.answers["complexity"]

if intent.confidence < 0.5:
    return route_to_human_agent(ticket_id)

if intent.choice == "order_status":
    return lookup_order(ticket_id)            # no model call at all

if intent.choice == "complaint" and (complexity.score > 1 or complexity.confidence < 0.5):
    return route_to_human_agent(ticket_id)

if complexity.score > 1.4:
    return handle_with_llm(ticket_id, FRONTIER_MODEL)

return handle_with_llm(ticket_id, SMALL_MODEL)

The 0.5 floor is the pattern TypeSafe documents: below it, the model is telling you the request didn’t land cleanly in any bucket, and guessing costs more than asking a person. Above the floor, each branch sets its own bar according to what a wrong answer does. Order lookups are cheap to get wrong and get no model at all. Complaints get a second gate, because a low confidence complexity reading on an angry customer is exactly the case where automation goes badly.

The thresholds are yours, and TypeSafe says plainly that the right values depend on your domain and your traffic, so measure before you trust the numbers above. And the router only decides where a request goes: the downstream model still needs its own output checks, which is the job of LLM guardrails.

Community work exists here already. jev-router wires this shape into an LLM gateway.

When not to use this

Don’t ask the router to do arithmetic on its way to a decision. “Is this customer inside their contract term” is a date comparison, and Jev reads dates as text rather than as ordered quantities, so extract the parts and compare them in code. Same for anything that depends on counting prior tickets.

Indirection is the other trap. A question like “would the specialist model handle this better than the general one” is a question about a property of a property, and multi-hop reasoning costs accuracy. Ask directly instead: how complex is the request, what kind of request is it. Then let code map those two readings onto model names. And if the message itself might contain instructions aimed at your system, remember that Jev has no built-in defence against injected text, so screen the input before the router acts on it.

FAQ

Why not let the frontier model route itself?

It can, and it costs a full generation call plus its latency on every turn before any real work starts. A typed router is a small separate decision made before the expensive resource is touched. TypeSafe reports Jev latency of 70ms to 500ms, which is the company’s own measurement, so verify the saving on your own traffic.

How do I pick the complexity levels?

Write them as sentences describing states you can recognise in real traffic, then check them against tickets you already know the answer for. Two levels is the minimum and ten the maximum. Fewer levels give you cleaner separation; more give you finer gradations but blur the boundaries. Start with three or four and adjust once you can see the distribution.

Can one call route and triage at the same time?

Yes, and it usually should. Extra questions in the same request cost nothing in latency according to TypeSafe’s fan-out pattern, so a router can also ask the questions used for support inbox triage and hand the answers downstream. Your code ignores whichever answers turn out not to apply to the branch it took.

What if two intents are genuinely both present?

The Choice spreads probability between them and confidence drops, which your floor catches. If compound requests are common rather than rare, a Choice is the wrong shape: ask one Noul per intent instead, since each Noul is an absolute judgment and several can be high at once. TypeSafe documents that the two shapes answer different questions.

Examples in the wild

Article

[AINews] Jev: a System One Model that only decides/classifies/routes/scores

AI News daily roundup leading with the Jev launch, summarising RLCD and TypeSafe's 20-200x speed and 40-400x cost claims. Frames Jev as part of a broader move toward task-specialised models.

Project

Ran Jev against an existing classifier eval that previously used Gemini 2.5 Flash Lite

Vercel CTO reports Jev saturated an existing classifier eval that had used Gemini 2.5 Flash Lite and ran about 6x faster. Original post; TypeSafe's quote-tweet is a separate item.

Discussion

Introducing System One Models and Jev (Hacker News launch thread)

The 1,863-point, 490-comment launch thread. Top comments argue the frontier-model framing is misleading, dispute the cannot-hallucinate claim on the grounds that type safety is not factual correctness, and note grammar-constrained decoding on ordinary LLMs already covers much of the interface.

Alternative

DSPy: programming, not prompting, language models

Declares typed input/output Signatures for LLM modules and optimizes the underlying prompts and weights against a metric. Its Signature abstraction is the closest widely-used open equivalent of Jev's typed-question interface. Star count is GitHub's rounded display figure.

Alternative

SetFit: efficient few-shot text classification

Fine-tunes Sentence Transformer embeddings plus a lightweight head for high-accuracy classification from a handful of labeled examples, with no prompting. The standard cheap alternative to an LLM classification call when the label set is fixed. Star count is GitHub's rounded display figure.

More workflow control use cases