jevcourse

Module 0 · 7 min read

Start here

By the end of this module you will have made one call to Jev, read every field of the answer, and know when to reach for it instead of an LLM.

The one-table version

Jev is the first System One model, made by TypeSafe AI. You send a state (text or JSON) and a map of typed questions. You get back one typed answer per question, with probabilities. Jev never writes a sentence. It picks, rates, or says yes or no.

LLM (chat model)Trained classifierJev
Inputpromptone textstate + typed questions
Outputprose you parseone labeltyped values with probabilities
Answer spaceopenfixed at trainingfixed per request, by you
Calibrated uncertaintynosometimesyes, by design
Latencysecondsmilliseconds70 to 500 ms
Setupa promptlabeled data, traininga question, written in English
Cost per callcentsfractions of a centfractions of a cent ($0.042 per million input tokens, output free)
Can explain, write, generateyesnono

The last row is the whole point. Jev cannot write a reply or summarize a document. It answers the question you wrote, inside the options you gave, and tells you how sure it is. Your code branches on the number.

The three primitives

Every question has an id you choose, a type, instructions, and for Choice and Score a criteria field.

TypeQuestion shapeReturns
Choicewhich one of these options?choice, probabilities (one per option), confidence
Scorewhere on this ordered scale?score (weighted position), legend, probabilities, confidence
Noulis this true?noul, a probability between 0 and 1

A Choice takes up to 255 options. A Score takes 2 to 10 levels, described as situations. A Noul has no separate confidence: the probability is the signal, and 0.5 means "as likely yes as no".

Your first call

Get a key at console.typesafe.ai. Then:

bash
export TYPESAFE_API_KEY=ts_...
curl -s https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "state": "Hi, my Stripe connection has failed for three days and I am losing sales. Please help today.",
    "model": "jev-latest",
    "questions": {
      "department": { "type": "choice", "instructions": "Which team should handle this?",
        "criteria": { "billing": "Payments, invoices, refunds", "technical": "Bugs, outages, integrations", "sales": "Pricing, upgrades, new accounts" } },
      "frustration": { "type": "score", "instructions": "How frustrated is the customer?",
        "criteria": ["Calm, stating facts", "Frustrated but civil", "Very angry, strong language"] },
      "is_urgent": { "type": "noul", "instructions": "Does the message convey urgency or a deadline?" }
    }
  }'

The answer, trimmed:

json
{
  "model": "jev-1.13.0",
  "answers": {
    "department": { "type": "choice", "choice": "technical", "probabilities": { "billing": 0.19, "technical": 0.80, "sales": 0.01 }, "confidence": 0.62 },
    "frustration": { "type": "score", "score": 1.1, "legend": { "0": "Calm, stating facts", "1": "Frustrated but civil", "2": "Very angry, strong language" }, "probabilities": { "0": 0.05, "1": 0.8, "2": 0.15 }, "confidence": 0.78 },
    "is_urgent": { "type": "noul", "noul": 0.99 }
  },
  "usage": { "input_tokens": 312, "output_tokens": 48 }
}

Read it like this:

The same call in TypeScript

The raw HTTP shape is the one you will paste everywhere in this course, because it works from any runtime and the question JSON is the deliverable. Here is a caller you can keep:

ts
// jev.ts
const URL = 'https://api.typesafe.ai/v1/systemone'

export type Question =
  | { type: 'choice'; instructions: unknown; criteria: Record<string, unknown> }
  | { type: 'score'; instructions: unknown; criteria: unknown[] }
  | { type: 'noul'; instructions: unknown; criteria?: { true?: unknown; false?: unknown } }

export async function ask<Q extends Record<string, Question>>(state: unknown, questions: Q, model = 'jev-latest') {
  let res: Response | undefined
  for (let attempt = 0; attempt < 4; attempt++) {
    res = await fetch(URL, {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({ state, model, questions }),
      signal: AbortSignal.timeout(8000),
    })
    if (res.status !== 429 && res.status !== 529) break
    await new Promise((r) => setTimeout(r, 300 * 2 ** attempt))
  }
  if (!res || !res.ok) throw new Error(`jev ${res?.status}: ${await res?.text()}`)
  return res.json() as Promise<{ model: string; answers: Record<keyof Q, any>; usage: { input_tokens: number; output_tokens: number } }>
}
ts
import { ask } from './jev'

const r = await ask(
  'Hi, my Stripe connection has failed for three days and I am losing sales. Please help today.',
  {
    department: { type: 'choice', instructions: 'Which team should handle this?', criteria: { billing: 'Payments, invoices, refunds', technical: 'Bugs, outages, integrations', sales: 'Pricing, upgrades, new accounts' } },
    is_urgent: { type: 'noul', instructions: 'Does the message convey urgency or a deadline?' },
  },
)
console.log(r.answers.department.choice, r.answers.is_urgent.noul, r.model)

TypeSafe also ships an official SDK for JavaScript (npm install @typesafe-ai/sdk) with choice(), score(), noul() helpers and a TypeSafeClient that retries 429 and 529 for you. Use it if you like; the JSON you send is identical.

The same call in Python

bash
pip install typesafe-sdk
python
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient

client = TypeSafeClient()  # reads TYPESAFE_API_KEY, defaults to jev-latest

r = client.system_one(
    state="Hi, my Stripe connection has failed for three days and I am losing sales. Please help today.",
    questions={
        "department": Choice(
            instructions="Which team should handle this?",
            criteria={"billing": "Payments, invoices, refunds", "technical": "Bugs, outages, integrations", "sales": "Pricing, upgrades, new accounts"},
        ),
        "frustration": Score(instructions="How frustrated is the customer?", criteria=["Calm, stating facts", "Frustrated but civil", "Very angry, strong language"]),
        "is_urgent": Noul(instructions="Does the message convey urgency or a deadline?"),
    },
)
print(r.answers["department"].choice, r.answers["frustration"].score, r.answers["is_urgent"].noul)

When to reach for Jev

Reach for it when all three hold:

  1. The answer is one of a set you can write down (options, levels, yes or no).
  2. A knowledgeable person would answer in about a second, given the right context.
  3. Your code needs the answer as a value, to branch, sort, gate or rank.

Keep an LLM when you need text: a reply, a summary, code, an explanation. The strongest products use both: Jev routes, gates and verifies; the LLM writes inside the boundary Jev drew.

Numbers to keep in mind

EndpointPOST https://api.typesafe.ai/v1/systemone
Modeljev-latest, currently jev-1.13.0
Price$0.042 per million input tokens, output free
Latency70 to 500 ms per request, whatever the number of questions
Limits64k tokens per request, 32k for the state plus the longest question, 1 200 requests per minute (adjusted with load)
Inputtext only: string, JSON object, array of text
Errors401 key, 422 validation, 429 rate limit, 529 overloaded; back off exponentially on the last two

Checklist

Next: where a decision like this belongs in a real product.