Jev and System One, explained step by step
A LLM writes text for humans. Jev, the TypeSafe AI model, does something else: we send it a state and typed questions, it returns structured answers, with probabilities and a level of confidence, which your code uses directly. This course starts from zero and builds, notion after notion, up to patterns, SDKs and known limits.
One request, three questions, one typed answer
Click on a question to see what Jev returns (example of Quick start Documentation).
jev-1.13.0 ; output tokens are free of chargeModules marked Interactive use values from documentation (recorded replies from jev-1.13.0, tables, examples of code). Illustration Simplify a principle with fictitious values or a calculation reported as such. no network call It never questions the TypeSafe API. The terms of the API remain in English ("state", "choice", "confidence"...). When a point is not documented, the course says so.
Why a decision model?
You know the LLM by the use: you type a question, you read an answer. code which must consume a judgment, the text becomes an obstacle. It is from this shift that TypeSafe leaves.
- The gap between "producing text" and "making a decision that can be used by code".
- Three ways of architecture software with AI: traditional, agent, AI powered software.
- What Jev promises: typified values, probabilities, confidence.
Text offset / decision
The document introductory page summarizes the problem in one sentence: the large language models are designed for produce text that humans read. As soon as you want a model to render a judgment that the code will consume ("is this ticket urgent?", "what service should treat it?"), you force a text generation system to exit a structured decision, then you re-spend the result to do something that the code may depend on.
Everyone has already written this kind of code: a prompt that begs the model to "respond only by a valid JSON", a regular expression to recover the value, a try/except for the day when the model adds a phrase of politeness. And even when the JSON is clean, one thing is missing: how safe the model is. A LLM can write "urgent" with the same aplomb, whether it is obvious or dice.
What Jev does instead
Jev is the flagship model of TypeSafe and the first model said System OneIt evaluates the questions typed against one state (the content to be judged) and returns structured results: no text generation, no parsing. The code receives typed values and probability distributions on which it can connect, sort, route. Choice and Score questions also refer to a "confidence", a number between 0 and 1 that the code uses to decide if it does, and how.
Three software architectures
Page How to build with TypeSafe Jev place in a three-box landscape. We must understand which Jev is aiming, because it is not not the agent's.
/concepts/how-to-build-with-system-one) : traditional software, LLM agents and software powered by AI, seen as three different architectures.Jev is designed to build software powered by AIHe doesn't generate code, he doesn't choose his next action, he doesn't write anything. AI primitives The code keeps control of the flow, the model renders judgment of common sense on unstructured data.
System One: the idea and training
Where the name comes from, what "calibrated" means, and why TypeSafe invented a third post-training path next to the RLHF and RLVR.
- System 1 / System 2: quick judgments, not long reasoning.
- Calibration: A probability of 0.8 must be verified 80% of the time over a set of predictions.
- RLHF, RLVR, RLCD: three training objectives, three types of models.
The name comes from Kahneman
Page System One explains: the name takes over the concept popularized by Daniel Kahneman in Thinking, Fast and Slow. System 1 is fast and intuitive; System 2 is slow and deliberate. Jev is firmly placed in the first: judgments rapid and targeted, the kind of thing that a competent person decides in a second when it is given the right context.
It is a design rule as well as a name. "Is this message urgent?" is a good question. "Analysing this message and determining the best course of action" is not one: it requires slow reasoning, and the documentation sees it as the signal that needs to be done. cut the task in small questions, then compose the answers in the code.
Like an LLM, a System One model includes natural language in the input. Unlike an LLM, it returns typed decisions and probabilities, not text. Two documented limits: Jev accepts only text (chains, JSON objects, text tables; neither image, audio, or video "for now"), and its main training language is English, with other languages being accepted with less precision.
Calibrated: what it means, and what it does not guarantee
The word comes back everywhere in the documentation: Jev is trained for calibrated decisions. Its probabilities are optimized against real results to reflect uncertainty. Specifically, on a large number of predictions of a well calibrated model:
- the outcomes to which it attributes a probability of
0.2occur about 20% of the time; - those to
0.8, about 80% of the time; - those to
1.0100% of the time.
The documentation immediately adds the nuance that counts: these rates describe groups The calibration of predictions. does not guarantee a response taken in isolation. A confidence of 1.0 describes the model's response, not proof that it is fair.
Three post-training paths
L'AI primer TypeSafe bet: large-scale automation will be dominated by interactions IA-to-IA and IA-to-software, about 99% machine-to-machine and 1% human. The machine interface therefore counts more than the chat interface. TypeSafe calls this Machine Native Intelligence : an AI with software properties, structure, reliability, observability, testability, speed, consistency, low cost. The formula of the page: " Building prod, not God build for production, not a model that does everything.
Learning by human return
Transformed pre-entry models into chatbots: they learn how to produce the answers people prefer. The page recalls that the RLHF was co-invented by Diogo Almeida, co-founder of TypeSafe.
Verifiable awards
Produced the models of reasoning, strong in mathematics for example, but slower and more expensive.
Calibrated decisions
The Path of TypeSafe: Reinforcement Learning for Calibrated Decisions. The model does not generate text; it returns decisions and probabilities, and a higher probability must correspond to a greater chance of being right.
/introduction/machine-learning-primer) : from a pre-entry language model, the RLHF and RLVR paths in retreat, and the RLCD path to a prominent decision model.The problem of the RLHF, seen by TypeSafe
The RLHF teaches a model to say what people prefer. This goal works well for a chatbot, but it can also reward the sycophancy and the hallucinations said with confidence. Optimizing preferences also causes what the page calls the mode dropping : the model learns to favor a style (follow instructions, for example) and reduces the probability of other possible outputs. It is an attenuated version of the mode collapse GAN, where a generator ends up always producing the same type of output because it continues to deceive the discriminator.
The conclusion of the page is measured: the RLHF remains a good choice for conversational models. But an output can be convincing for a person without being reliable enough for unsupervised automation. Human preference and machine reliability are two different optimization targets; automation in production requires, according to TypeSafe, an objective centered on constraints and calibrated uncertainty.
The documentation describes the objective of the LCDR (decisions + calibrated probabilities) but not the training recipe : neither the data, nor the reward function, nor Jev's architecture, nor its size. The course does not invent anything about it. The page Models only specifies that Jev is neither fine-tuned nor adapted by LoRA with customer data: the same weights serve all accounts, and the adaptation to the domain passes through the request (state, instructions, criteria).
Anatomy of a request and an answer
Before detailing each type of question, let's look at the form of a full exchange with the API: three input fields, one answer per question in output.
- The three fields of any request:
state,model,questions. - Question IDs: selected by you, never seen by the model.
- The answer:
model,answers,usage.
One request
Everything goes through a single point of entry, POST https://api.typesafe.ai/v1/systemone, with a header API key Authorization: BearerThe JSON body always has the same high-level shape:
state: the content to be evaluated. A string, object or table (Chapter 4).model: the model that processes the request, for example"jev-latest"(chapter 17).questions: one map You choose each key; the answers return under the same keys.
Each question has a type (choice, score or noul), the instructions (the question asked) and, depending on the type, criteria (options, levels, or definition of yes and no).
The documentation repeats it on each primitive page: the key you choose (department, is_urgent...) is used for your code to find the answer. The model doesn't see it. Write the complete question in instructionsEven when the identifier seems to be talking about himself.
Answer
The answer contains three fields: model, the versioned identifier of the model that responded (e.g. jev-1.13.0, even if you asked jev-latest) ; answers, one entry per question, under your identifiers; and usage, the number of tokens in and out. Each response has a type which corresponds to the type of the question, then to the fields specific to the type:
| Type | What he answers | Fields returned | How to read it |
|---|---|---|---|
| Choice | Which of these options? | choice, probabilities, confidence | choice is the most likely option; probabilities distribution on all options; confidence sums up the extent to which this distribution is piqued. |
| Score | Which level? | score, legend, probabilities, confidence | score is a position along your levels, which can fall between two; legend Reminds the levels by number. |
| Noul | Is that true? | noul | The probability that the answer is yes. Nearly 1 : yes frank ; near 0 : no frank ; close to 0.5 : uncertain. confidence Separated. |
Two properties make these responses composing, and the literature highlights them:
- Each response is constrained by the options provided. The model returns a distribution on your options or levels, never a value outside. The code never has to recover a value in prose.
- Each response is independent. The answer to one question is not a hidden context for another. You can add or remove questions without changing the results of others.
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"state": "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.",
"model": "jev-latest",
"questions": {
"urgency": {
"type": "noul",
"instructions": "Does this message express urgency?"
}
}
}
EOF
"is_urgent" in the map questions ?The "state": what we give to judge
The state is the raw material: a message, a passage, or the current state of your application. The documentation advises to structure it, to put only the necessary, and to point the questions to its parts by paths.
- Three formats: string, object, array, and when to use each.
- Separate the content (state) from the judgements (questions).
- Reference a specific field with a path between serious accents:
`ticket.messages[0].text`.
Chain, object or table
The simplest state is a chain: "My card was charged twice.". But it can also be a JSON object or table containing context, examples, linked records. The image proposed by the page State : think of the status as the file you would present to a panel of experts before asking them for a judgment.
| Format | Useful for | Example (documentation) |
|---|---|---|
| Chain | A message, an article, a passage | "My card was charged twice." |
| Object | Named fields, linked records, status of application | {"message": "My card was charged twice.", "order_id": "A-104"} |
| Array | A sequence of messages or recordings | ["Hi", "My customer number is TS1337.", "My card was charged twice."] |
The Documentation Board: use an object for most queries, so that each part of the state has a descriptive name and its relationships remain clear. The chain is suitable when the case is simple and requires only one text. A query evaluates one against one or more questions; all questions see the same state and are assessed independently.
Separate the content of the questions
The state contains the content and the supporting facts; the questions define the judgements to be carried on. The example of the doc: keep the claim for reimbursement and the refund policy in the state, then ask on one side whether the client requests a refund, on the other side if the policy allows. Two Nols, only one state.
Refer to a specific field
When the state is a multi-party object, a question often relates to one of them. Primitives request to appoint her in the instructions with a "point and index" path to its key, including backticks : `ticket.messages[0].text`, `order.charges`. The model then knows what part of the state to judge.
questions = {
"refund_requested": {
"type": "noul",
"instructions": "Does `ticket.messages[0].text` request a refund?",
},
"policy_supports_refund": {
"type": "noul",
"instructions": (
"Does `refund_policy` support the refund requested "
"in `ticket.messages[0].text`, given `order.charges`?"
),
},
}
Unpack the entrance : include only context relevant to the questions being asked. This prevents distractions and what the documentation calls context rot, the loss of precision when the state grows with unrelated content. Do not rely on the memory of the model : when an up-to-date information exists in your knowledge base, put it in the state rather than hope it is in the weight.
Choice : choose an option from a set
The first primitive. You give the list of possible options; Jev returns the one he retains, a probability for each, and a confidence.
- When to use a Choice: an answer among a fixed set, without order between options.
- The form of the question (
criteria= map option → description) and the answer. - Read a shared distribution between two teams, and what the code does with it.
When
A Choice serves when the answer is one of a fixed set of options : which team processes a ticket, which category of a product belongs to, in which language is written a code extract. If the answer is a position on a spectrum, it is a Score; if it is yes or no, a Noul. Examples of questions given by the page:
- « What programming language is this code written in » →
python, javascript, typescript, go, rust, other - « What type of meeting is this based on the title and description » →
standup, planning, retrospective, one on one, brainstorm, none of the above - « Which product category does this item belong to » →
electronics, clothing, home garden, food and beverage
Note the options other and none of the above The doc recommends adding one when the list may not cover all entries, so that the model can say that none is appropriate.
The form
A Choice has three fields: type (always "choice"), instructions (the question) and criteria, a map whose each key is an option name and each value a description. Both names and descriptions are sent to the model : Write descriptions that separate the options from each other. A description may be null when the name is sufficient (example of the doc: {"calm": None, "frustrated": None, "angry": None}) A Choice accepts up to 255 options.
from typesafe_sdk import Choice, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state="My running shoes arrived in the wrong size. Can I swap them for a size 10?",
questions={
"department": Choice(
instructions="Which team should handle this?",
criteria={
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems",
},
),
},
)
print(response.answers["department"].choice)
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
questions: {
department: choice("Which team should handle this?", {
returns: "Exchanges, wrong or damaged items",
shipping: "Delivery status, delays, lost packages",
billing: "Charges, invoices, payment problems",
}),
},
});
console.log(response.answers.department.choice);
{
"state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
"model": "jev-latest",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"returns": "Exchanges, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems"
}
}
}
}
Read a shared answer
In the five-question example, department returns returns at 0.61, but billing a 0.35 due to a double rate mentioned in the ticket. The ticket belongs to two teams, and the confidence of 0.42 reflects this sharing. requested_resolution is even more indecisive: refund 0.40, replacement 0.34, exchange 0.24, confidence 0.20; the client does not say what he wants. The code of the doc has three consequences:
- under 0.3 confidence over
department, we do not assign : a person sorted ; - a second team that has more than 0.25 probability receives a copy;
- under 0.5 confidence over
requested_resolution, on asks the customer Instead of guessing.
def triage(ticket: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
state=ticket,
questions=TRIAGE_QUESTIONS,
)
answers = response.answers
department = answers["department"]
if department.confidence < 0.3:
# Not clear which team to send to. Let a person decide.
send_to_manual_triage(ticket)
return
if department.choice == "returns":
# return_reason answer is only used here
assign(ticket, team="returns", issue=answers["return_reason"].choice)
elif department.choice == "shipping":
# shipping_issue answer is only used here
assign(ticket, team="shipping", issue=answers["shipping_issue"].choice)
else:
assign(ticket, team="billing")
# A second team with a real share of the probability gets a copy
for team, probability in department.probabilities.items():
if team != department.choice and probability > 0.25:
notify(ticket, team=team)
resolution = answers["requested_resolution"]
if resolution.confidence < 0.5:
# The customer hasn't said what they want. Ask, don't guess.
ask_customer_what_they_want(ticket)
elif resolution.choice == "refund":
flag_for_refund_approval(ticket)
if answers["tone"].choice == "angry":
flag_for_senior_agent(ticket)
if ordinaryTwo of the five questions are: Speculative : return_reason only if the service is returns, shipping_issue that if he is shipping. They are asked anyway, because the questions are evaluated in parallel and the code does not know what it does not need. If tomorrow you need the language of the client or the product concerned, you add an Choice: the number of requests remains at one. It's the pattern Speculative fan-out of chapter 12.
Structured descriptions
Start with one line of description per option. When two options are similar and the model confuses them, the doc advises to describe each one by one. object rather than a string: a field for what the option covers, one for what belongs to the next option, some examples. Field names (what, not_for, examples, question, focus...) are not part of the API and are not reserved: you choose them, the model sees them with values, so prefer short names that label the following. return_policy and return_status, two options that both talk about returns; with contrasting objects, the answer is return_status 1.0%.
criteria of a Choice, what does the model see?Score : situate the state on ordered levels
The second primitive answers "what level?". You describe bearings, from the lowest to the highest; Jev returns a position, which may fall between two, plus one probability per level.
- Levels: an orderly array of descriptions, numbered 0, 1, 2... by their position.
score= average of the level numbers weighted by their probability.- Write good levels, and cut a composite judgment into several Scores.
When
A Score serves when the answer is a position on a spectrum that you can describe in steps : the severity of a bug, the satisfaction of a client, the Python experience of a candidate. criteria is a level, described in words. The number of a level is its position in the table from 0: three entries make levels 0, 1 and 2. It takes at least two levels; the API accepts up to 10.
Important detail given by the page: the model receives descriptions and nothing else, and each level is judged separately He sees neither the level number nor his neighbors. "Worse than the previous level" means nothing to him.
from typesafe_sdk import Score, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state="The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.",
questions={
"bug_severity": Score(
instructions="How severe is the reported issue?",
criteria=[
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists",
],
),
},
)
print(response.answers["bug_severity"].score)
import { score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: "The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.",
questions: {
bug_severity: score("How severe is the reported issue?", [
"Cosmetic; no impact to functionality",
"Broken or degraded feature, but workaround exists",
"Blocking issue; no workaround exists",
]),
},
});
console.log(response.answers.bug_severity.score);
{
"model": "jev-1.13.0",
"answers": {
"bug_severity": {
"type": "score",
"score": 1.43,
"confidence": 0.35,
"legend": {
"0": "Cosmetic; no impact to functionality",
"1": "Broken or degraded feature, but workaround exists",
"2": "Blocking issue; no workaround exists"
},
"probabilities": { "0": 0.0, "1": 0.57, "2": 0.43 }
}
},
"usage": { "input_tokens": 332, "output_tokens": 18 }
}
In the third and fourth example, the probability is divided between levels 1 and 2. More weight on the 2 makes the score rise, but it does not measure the fraction of customers without circumventionAnd different distributions can give the same score: 1.0 can mean "all on Level 1" or "half on the 0, half on the 2". Read probabilities and confidence next to the score to distinguish them. Jaggedness adds: do not use the score to reconstruct an exact size between two levels; a threshold, yes, an interpolation, no.
Write good levels
- Describe situations, not degrees. "Function broken or degraded, but a bypass exists" gives the model something to compare to the state. "Moderately serious" does not give anything.
- No numbers. With
criteria: ["0", "1", "2"]and the "Rate severity from 0 to 2" statement, the report of the misaligned button gets 0.55 at confidence 0.33 (shared probability between 0 and 1). With the three descriptive levels, it gets 0.0 at confidence 1.0. - As many levels as you can describe separately, up to 10. Three is good. Do not add a level that you can't distinguish.
- A dimension by Score. "Punctual and brilliant and experienced" measures three things; a high entry on one and bass on the other cannot be placed, confidence falls, the score loses meaning. Separate and combine in the code.
- Give your own level to a rare extreme case On which you have to act differently: a feeling scale that ends up "very angry" can add "undue or threatening".
- Test your data. Two formulations on the same scale can behave differently. Higher confidence alone does not prove that a description is better.
Cut a complex judgment into multiple Scores
A judgment that depends on several things breaks down into one Score per thing, sent in the same request (they are evaluated in parallel, it costs a few tokens of question). The code then combines the scores with weights that belong to it. The example of the doc: priority of a ticket from three Scores, gravity (3 levels), frustration (3 levels) and quality of the ratio (4 levels). normalizes each score by dividing it by its maximum level number, len(criteria) - 1, to bring everything back from 0 to 1.
def normalized(answers, question_id: str) -> float:
"""Put a score on 0 to 1 by dividing by its top level number."""
top_level = len(TRIAGE_QUESTIONS[question_id].criteria) - 1
return answers[question_id].score / top_level
def priority(ticket: str) -> float:
with TypeSafeClient() as client:
response = client.system_one(
state=ticket,
questions=TRIAGE_QUESTIONS,
)
answers = response.answers
severity = normalized(answers, "severity")
frustration = normalized(answers, "frustration")
report_quality = normalized(answers, "report_quality")
# A detailed report helps an engineer investigate, so it raises priority a little.
return 0.6 * severity + 0.3 * frustration + 0.1 * report_quality
Structured levels
When the model continues to place between two levels adjacent to the entries that you think are clear, give each level one object: one field for what the level covers, another with some example situations, the same field names on all levels. The doc's picture on the Safari report is eloquent:
| Description of levels | score | confidence |
|---|---|---|
| Single strings, not applicable or examples | 1.43 | 0.35 |
| Objects with a useful example: "export fails in one browser but works in another" | 1.03 | 0.96 |
| Objects with an unrelated example: « search fails, but browsing categories still works » | 1.43 | 0.35 |
The example that looks like real entries concentrates almost all probability on a level; the unconnected example does not change anything. And the caution of the page: a higher confidence does not establish what answer is correct. Choose examples of which you know the expected level, then test the revised descriptions on other entries before keeping them.
criteria: ["0", "1", "2"] Is there a bad idea for a Score?Noul : the probability that the answer is yes
The third primitive is the simplest in appearance: a closed question, a number between 0 and 1. Its subtlety lies in what this number measures, and in the threshold that your code chooses.
- A Noul returns
noul, the probability of yes.confidenceseparated, and why. - The threshold depends on the cost of the error; the values of the medium can go to a person.
- A Noul is not a scale: "Is the candidate strong in Python?" does not measure the experience.
When
A Noul serves when the answer is yes or no: does this message require a refund, does this CV mention distributed systems, does this comment contain personal data. Fields: type ("noul"), instructions (the question, or a statement to be judged) and, as an option, criteria, an object with descriptions true and false what a yes and a no mean.
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state="I have asked three times now. Can I please just talk to a real person?",
questions={
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
},
)
print(response.answers["is_human_escalation"].noul)
print(response.answers["is_repeat_contact"].noul)
import { noul, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: "I have asked three times now. Can I please just talk to a real person?",
questions: {
is_human_escalation: noul("Is the customer asking for a human agent?"),
is_repeat_contact: noul("Has the customer contacted support about this before?", {
true: "Mentions a prior attempt, ticket, or that they have asked before",
false: "No sign of any previous contact",
}),
},
});
console.log(response.answers.is_human_escalation.noul);
console.log(response.answers.is_repeat_contact.noul);
{
"model": "jev-1.13.0",
"answers": {
"is_human_escalation": { "type": "noul", "noul": 0.99 },
"is_repeat_contact": { "type": "noul", "noul": 0.93 }
},
"usage": { "input_tokens": 360, "output_tokens": 39 }
}
Read a Noul: the answer and the certainty in one number
Nearly 1, a frank yes. Nearly 0, a non-france. Nearly 0.5, the model gives the yes and the no a similar probability. no confidence separated, unlike Choice and Score : the distribution of a Noul has only two issues, so the only value noul The page gives six saved messages for the question "Is the customer asking for a human agent?"; the module below reproduces them.
YES = 0.8
NO = 0.2
def route(message: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state=message,
questions=SUPPORT_QUESTIONS,
)
answers = response.answers
wants_human = answers["is_human_escalation"].noul
repeat = answers["is_repeat_contact"].noul
if NO < wants_human < YES or NO < repeat < YES:
# The model isn't sure either way. Let a person decide.
send_to_review(message)
return
priority = "high" if repeat > YES else "normal"
if wants_human > YES:
route_to_agent(message, priority=priority)
else:
route_to_bot(message, priority=priority)
The rule on the page: it depends on the cost of the error. 0.5 when yes and no are as easy to assume each other. Higher when acting on a false yes is expensive (call someone on demand, pay back). Lower when missing a real yes is expensive (do not report a security problem). And the values of the environment can go to one person rather than to one of the two ways of code. If the readers see too many messages, narrow the gap between NO and YES ; if too many bad routings pass, it is enlarged.
A Noul is not a ladder
The value ranges from 0 to 1, but this is not a measure of the requested thing: it is the probability that the answer is yes. If the question is actually a question of degree, the value does not measure the degree. The doc compares, out of four candidates, the Noul "Is the candidate strong in Python?" and a Four Level Score (no experience, some familiarity, regular use at work, deep expertise):
One could, in the code, cut the 0–1 interval into slices ("0.3 to 0.7 = some experience"), but the model would not see them: nothing in its answer was judged against them. A median value may mean "average experience" or "miscellaneous case", and the spacing between candidates is not something you have chosen. With the Score, each candidate lands on or near a level you have written, and if you do not agree, you reformulate a level and restart.
Write a question Noul
- A question by Noul. "Is the client angry? and Does he ask for a refund?" forces the model to judge two things at a time. Two Nouls, combined in the code.
- A high value must mean yes. "Does the message contain personal data?" is clear. "Is the message free of personal data?" reverses the meaning and code that reads it will be wrong.
- A statement works as well. For "The customer is requesting a refund", a value close to 1 means that the statement is true. Try both on your data.
- Make the border clear. « Does this candidate have any Python experience?" does not leave a middle. When the border is subtle, add
criteriawithtrueandfalse; otherwise, the instruction alone is often enough.
Structured instructions, code-generated questions
The instructions can be an object: the question in one field, reference data in the others. The example of the page compares a CV that has just arrived at records of a candidate database, one question Noul per sheet, all in a single request, with the card identifier in the key to the question. Answers reported: sheet 18 (name spelled differently, same city, same employer) gets 0.74; sheet 42 (same name, other city, other employer) 0.09; sheet 77 (near name, same place, other employer) 0.08.
SAME_PERSON = "Is the resume for the same person as `potential_duplicate`?"
def duplicate_questions(candidates: list[dict]) -> dict[str, Noul]:
"""One Noul per candidate record, all asking the same question."""
return {
f"same_as_record_{candidate['id']}": Noul(
instructions={
"potential_duplicate": {
"name": candidate["name"],
"location": candidate["location"],
"last_employer": candidate["last_employer"],
},
"question": SAME_PERSON,
},
)
for candidate in candidates
}
def find_duplicates(resume: dict, candidates: list[dict]) -> list[str]:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state={"resume": resume},
questions=duplicate_questions(candidates),
)
return [
question_id
for question_id, answer in response.answers.items()
if answer.noul > 0.7
]
confidence ?Choose the right type, and structure when needed
Three primitives, so three forms of response. The rule of documentation is in one sentence: take the one whose answer is directly actionable by your code. Then, when a string is not enough, put the JSON structure in the questions.
The rule
- Choice when the answer is an option of a known set, without order between them. Give the complete list, more
otherif she can't cover it all. - Score when the answer falls on a spectrum that you can describe each point.
- Noul for a net yes/no where the probability itself is the useful signal.
If two types seem to be suitable, prefer the one whose answer is used directly: a Choice between refund, rebook and information connects on three paths of code; a frustration score compares to a threshold; a Noul plugs in on a if.
Where the structure is accepted
Page Advanced: structure says System One models are trained to understand the structure, and lists the fields that accept it. EntryType : string, object, array or null.
| Field | Applies to | Form accepted |
|---|---|---|
instructions | Choice, Score, Noul | string, object, array or null |
values of criteria (option descriptions) | Choice | same |
entries of criteria (level descriptions) | Score | same |
criteria.true and criteria.false | Noul | same |
When to use structure
Page How to build gives three situations: the question needs context or examples (a long sentence of context or a list of examples goes into fields named next to the question, which the code can modify without rewriting the question); part of the question comes from the code (a value read in the base goes into its own field rather than in a string template); several questions have neighbouring instructions (additional data make them separate). remains a chain.
A table also works, when the instruction is a list of things to check or compare:
"instructions": {
"question": "Does the claimed sender identity conflict with the sending domain?",
"compare": ["ticket.sender.display_name", "ticket.sender.email"],
"focus": "Compare the named organization with the email domain."
}
Browse a taxonomy with Choices
To classify in a deep taxonomy, the doc proposes One Choice by Level, by browsing the tree in the code : at each step, the options are the children of the current node, and the value of each option is the subtree of the child. The model thus sees what lives under a branch before committing itself to it, which counts when the article belongs to a leaf whose name does not guess from the branch. The example : a gourd that can go under Sporting Goods > Cycling > Bike Bottles & Cages or Home & Kitchen > Drinkware > Water Bottles ; the probabilities If a subtree is too large, it is reduced to its direct children and to a sample of leaves.
"Confidence": the form of distribution, in a number
Any Choice or Score answer contains probabilities. The form of this distribution says how safe the model is; confidence sum it up in a number of 0 to 1 so that the code can set a threshold without doing the calculation.
- Focus on an exit = confident; spread = uncertain.
confidenceis derived fromprobabilities; the doc does not publish the formula and leaves you free to calculate another one.- "I don't know" is a useful signal: that's what makes a system reliable.
Derivation of probabilities
Page Confidence Says it straight away: confidence is a statistics calculated from the distribution TypeSafe calculates it and returns it on each Choice and each Score, so the current case does not require anything more. For a Choice, the distribution is about your options; for a Score, about your levels. In both cases, the more flat it is, the lower the confidence: on a Choice, no option clearly outweighs it; on a Score, the levels are ambiguous, the question measures several things, or the state does not say enough.
Documentation confidence as a "solid defect" that is suitable for most cases, but specifies that you are never locked in its definition : depending on what you assess, another measure can serve better, and that is why the answer contains the probabilities The advantages and disadvantages of the different calculations are referred to a future cookbook. The module below does not invent a formula: it shows the couples (distribution, confidence) as the doc reports them, and a measure of pedagogical concentration clearly indicated as such.
"I don't know" is a useful signal
The sentence on the page is worth mentioning: if an intelligent system, human or machine, cannot express an honest uncertainty, it cannot be trusted. confidence is the integrated mechanism by which the model says "not safe for that one". This is what allows the code to have different behaviours depending on the degree of certainty, and it is, according to TypeSafe, the foundation of the systems on which we can really rely.
Keep the limit: a confidence of 1.0 means that the returned distribution puts all the probability on an exit. It describes the model's response, not a guarantee that it is fair.
confidence a Choice answer?Act, confirm, climb: thresholds follow the risk
The answer says: what ; confidence says if action is needed. A starting point: three beaches. Then a rule: a threshold is not a single number, it depends on the consequences of the error.
Three paths
- High confidence: act automatically. The model has a clear reading.
- Medium confidence: proceed with caution. Request confirmation from the user, report for rereading, or collect more information before taking action.
- Low confidence: do not act. Route to a person, ask for clarification, or switch to another system. The model says that he lacks information or that the question is not right for him.
Where to trace these borders depends on the stakes. And in the same system, different actions must be kept at different levels. The page Confidence-gated routing the watch with a voice banking interface: consult a balance of 0.6 confidence, it is acceptable (at worst, the user hears a balance he has not requested); approve a transfer requests more than 0.85, otherwise we have confirmed.
action = response.answers["intent"]
# Below 0.6 confidence on any action, route to a human
if action.confidence < 0.6:
route_to_support_agent(account_id)
elif action.choice == "check_balance":
# Low stakes. 0.6 confidence is sufficient.
show_balance(account_id)
elif action.choice == "approve_transfer":
if action.confidence > 0.85:
# High stakes, but high confidence. Safe to act automatically.
approve_transfer(account_id)
else:
# High stakes, moderate confidence. Verify intent first.
ask_user_to_confirm("Just to confirm: you would like to approve this transfer, is that correct?")
else:
route_to_support_agent(account_id)
The right values depend on your domain and the performance of the model on your case. start careful, test on your data, adjust by observing the results, and checking the thresholds by tracing confidence against accuracy on your own examples. Agent skill adds a caution: if all you care about is taking the best option, simply take the most likely option, without a confidence threshold; and if you have a precise statistical algorithm in mind, work instead on probabilities.
Building with System One: the code keeps your hand
Page How to build with TypeSafe is the design guide. Its summary: Build a normal software stream and insert System One only where AI is needed. Here are its eight steps, then the complete example that ends them.
- This makes System One composing: structured, parallel, comparable, fast, calibrated, constant.
- The eight steps of designing a stream, from "use code when you can" to "route on uncertainty".
- The complete ticket sorting of the doc, with its weighted spam score.
Typed by construction: decisions and probabilities respect the types and JSON schema that the code is waiting for. It never has to recover a value in prose.
The questions are evaluated independently and in parallel. The result of one primitive does not become a hidden context that changes the result of another.
The exits are sorted out and feed if Smart, thresholds, comparisons.
Most queries end in about 100 ms: enough for a real-time query path or interface.
The LCDN communicates uncertainty by calibrated probabilities, rather than tending to excess confidence.
Designed to return stable responses from one evaluation to another (see the cookbook) Self-consistency, chapter 19).
The page adds a target: a report of intelligence / (speed and cost) greater than 100×, with the bet that cheaper intelligence will create much more demand.
Designing an eight-step flow
Unpack the questions. Ask the most explicit, narrow, specific and atomic questions possible. A broad question hides several judgments behind an answer; atomic questions expose them, so that they can be inspected, settled and combined in the code. And decomposition does not cost going back and forth: questions about the same state run in parallel.
The complete example: sort a ticket
The flow triage_ticket.py of the page keeps determinism in the code (a closed ticket comes out immediately, without a model), only sends the useful structured context (message, sender, links, customer plan, open orders, list of sensitive identifiers), asks seven atomic questions in a single query (a Subject Choice, five Nouls, a Frustration Score), then composes the answers with explicit trusted doors. The following module isolates the part "Spam Risk": three Nouls weighted in the code.
with TypeSafeClient() as client:
response = client.system_one(
state=state,
questions=questions,
)
# Compose independent spam signals with weights controlled by code.
answers = response.answers
spam_risk = (
0.45 * answers["requests_credentials"].noul
+ 0.30 * answers["sender_identity_mismatch"].noul
+ 0.25 * answers["unexpected_reward"].noul
)
# Escalate uncertain judgments instead of guessing.
spam_is_uncertain = 0.4 < spam_risk < 0.6
if spam_is_uncertain or answers["topic"].confidence < 0.75:
return route_to_human_review(ticket)
if spam_risk >= 0.6:
return quarantine_as_spam(ticket)
# Let code decide which speculative answers matter on this path.
if answers["topic"].choice == "billing":
return route_to_billing(
ticket,
refund_requested=answers["refund_requested"].noul >= 0.7,
)
if answers["topic"].choice == "orders":
return route_to_orders(
ticket,
mentions_open_order=answers["mentions_open_order"].noul >= 0.7,
)
priority = (
"high"
if answers["frustration"].confidence >= 0.7
and answers["frustration"].score >= 1.5
else "normal"
)
return route_to_account_support(ticket, priority=priority)
Two details of the same file deserve attention. The questions are all there Structured : each Noul has an object instructions with question, compare or inspect, and focusand the criteria including true and false are objects with what, not_for, examples. And the paths to the state are quoted between serious accents (`ticket.message`, `policy.sensitive_credentials`), as in Chapter 4.
The four architectural patterns
TypeSafe is designed to live within a larger system. Thinking about atomic decisions that consist of complex behavior is, says the documentation, the key skill. Four patterns named summarize it.
| Pattern | What he does | Profits (doc) |
|---|---|---|
| Speculative fan-out | Send a lot of questions in one appeal, including speculative ones, and let the code decide what counts | Cost, speed |
| Confidence-gated routing | Use confidence as a second axis of decision-making to build safer systems | Reliability, security |
| Composite scoring | Combine multiple analysis dimensions into a single score | Cost, reliability, speed |
| Intent routing | Sort a user's intention and route to the right manager | Cost, speed |
1 · Speculative fan-out
Because a request accepts a lot of questions, the doc recommends putting it all those that the system might need, then sort in the code. All are evaluated in parallel, so add little changes in response time. The example: sort a support ticket. Instead of asking the category, then gravity in a second call if it's a bug, you ask both at a time; if it's not a bug, you ignore gravity.
category = response.answers["category"]
bug_severity = response.answers["bug_severity"]
bug_repro = response.answers["has_reproducible_steps"]
refund = response.answers["refund_requested"]
frustration = response.answers["frustration"]
if category.choice == "bug_report":
if bug_severity.score > 1.5 and bug_repro.noul > 0.6:
escalate_to_engineering(ticket_id, severity="high")
else:
add_to_bug_backlog(ticket_id)
elif category.choice == "billing":
if refund.noul > 0.7:
route_to_billing_with_flag(ticket_id, refund_likely=True)
else:
route_to_billing(ticket_id)
elif category.choice == "feature_request":
log_feature_request(ticket_id)
# Frustration is useful regardless of category
if frustration.score > 1.5:
flag_for_priority_response(ticket_id)
All that is needed for the full decision tree comes from a single call. Speculative questions are ignored when they are not serving, and save a round-trip when they are serving. Primitives returns to the cookbook Parallel questions : thirteen questions in one appeal against thirteen calls, about ten times cheaper and ten times faster, without changing the answers (both pages give slightly different multipliers, 11.5×/9.6× and 12.2×/10.0×, probably two executions).
2 · Confidence-gated routing
It's chapter 10: the answer says what, confidence says if you have to act, and every action has its threshold. Nothing to add here, if not the place of this pattern in the list: it's the one that brings reliability and security, not cost or speed.
3 · Composite scoring
We often want to classify elements according to several criteria at the same time. The pattern: to cut the judgment into independent dimensions, to note each separately (one Score per dimension, in a single query), to normalize between 0 and 1, then combine with weights that the control code. The example of the page: CVs of engineers rated on four dimensions (Python depth, leadership, system design, versatility), with two sets of weights depending on the position.
py = response.answers["python_depth"].score / 4
lead = response.answers["team_leadership"].score / 4
arch = response.answers["system_design"].score / 4
general = response.answers["generalist"].score / 4
# Senior IC
ic_score = (0.40 * py) + (0.10 * lead) + (0.40 * arch) + (0.10 * general)
# Engineering Manager
em_score = (0.15 * py) + (0.40 * lead) + (0.20 * arch) + (0.25 * general)
What the page points out: beyond the rankings, we see exactly how the final score is made. If the top ranked ones do not meet expectations, we adjust the weights, without losing the shade of individual scores.
4 · Intent routing
Not all requests deserve the same manager: some are settled by a basic request, others by a specialized LLM with its context, others by a human. TypeSafe stands ahead, as a fast and inexpensive classifier who decides which handler to invoke, instead of passing every message through an expensive LLM just to know what it's talking about.
def route_ticket(ticket_id, response):
intent = response.answers["intent"]
complexity = response.answers["complexity"]
if intent.confidence < 0.5:
# If we don't have enough confidence to classify, route to a human agent
return route_to_human_agent(ticket_id)
if intent.choice == "order_status":
handle_order_status(ticket_id)
elif intent.choice == "product_question":
handle_with_llm(ticket_id, PRODUCT_SPECIALIST)
elif intent.choice == "return_exchange":
handle_with_llm(ticket_id, RETURNS_SPECIALIST)
elif intent.choice == "complaint":
low_confidence = complexity.confidence < 0.5
# A higher complexity.score leans toward the "escalation needed" end of the scale.
if complexity.score > 1 or low_confidence:
# Too complex for safe automation, or we're not sure about the complexity; route to a human.
route_to_human_agent(ticket_id)
else:
handle_with_llm(ticket_id, COMPLAINT_RESOLUTION)
The questions of the same request are independent; one answer does not become the context of another. If a subsequent judgment depends on an earlier answer, one makes one second query in codeBut the doc insists: this is the exception. Dependence is real only if the code cannot build the second query without the first answer (it needs to go to get more data, to decide what the state is made of, or to choose the options of the following question). Three cookbooks do so for real reasons: Skill suggestion (classify 182 skills and then reread the top three in full text), Structure recovery (paste the lines, then classify blocks that did not exist before), Hierarchical classification (Each Choice decides on the following options).
Demo: the home automation assistant
The only demo listed by the documentation is a connected home assistant. It shows the speculative fan-out to its maximum, and how TypeSafe associates with a LLM when you still need to generate text.
Take the "Turn off all of the lights in the house" request. The code only needs four answers: the category of the request (domotic control), the target domain (the whole house), the type of device (lights), the action (off). The last question is written. assuming that the user is controlling lights, and it is asked before we know whether that is the case. It is a speculative question: it is evaluated in parallel with the others, and the code filters the results afterward. Each user request is thus evaluated against a long list of issues, many of which will not be applicable.
The wrong way, says the page, would be to cut into successive calls: the category first; then, once sure that it is a command, the domain and the device; then, once sure that they are lights, the action. This minimizes the number of questions, but it is much slower and more expensive than a single grouped call.
TypeSafe + LLM
The demo also shows two ways to associate Jev with a generic model, for a system that sometimes needs a text generation step:
- Cut a composite request. A Noul asks if the request requires more than one separate action. If so, an LLM cuts the sentence into a list of atomic commands, which TypeSafe then evaluates one by one.
- Switch to a conversational LLM. When TypeSafe determines that the request is a general information request or a conversation request, the system calls a LLM to generate a free response. Known deterministic behaviors are processed quickly and at low cost; the flexibility of a LLM remains available when needed. TypeSafe's initial response is so fast compared to that of the LLM that it adds negligible latency.
The page indicates that the demo is a Quick/React application whose full source code "will be available on GitHub at the output"; on the date of playback, only a video recording is offered. The course therefore does not describe its code.
Where to place Jev: the use case map
Page Example use cases is made to brainstorm: open the nearest sector, browse the decisions of example, adapt them to its own documents and actions. It starts with five large families.
Interlacing AI with reliable software, so that it can run one million times in the background without human co-driver. The code has the control stream, TypeSafe semantic decisions.
A state-of-the-art intelligence at real-time speed (the page says 150 ms): fast enough and smart enough to be programmed in a game or integrated into an interface.
One hundred times cheaper, so able to process giant data sets: searching in huge corpus, classifying trace agents, extracting characteristics.
Check for promptness, extractions, traces of reasoning, tool calls from any other AI: jailbreaks, citation errors, hallucinations, at a fraction of the cost of the LLM call.
Make the harness of an agent smarter: model routing, semantic context recovery, error detection and safeguards, classification of traces of reasoning.
By sector
Nineteen accords in the page. Each list of typical decisions; here is the content, condensed but faithful.
Research and recovery
- Replace or supplement the embeddings of a RAG pipeline with semantic research, scoring and grading.
- Note the relevance of the request → candidate; reclassify by comparison; cross-encoder for more precision.
- Select the context that is useful for downstream AI streams.
Scientific discovery
- Filter articles according to inclusion and exclusion criteria for a systematic review.
- Label transcripts, open answers, field notes.
- Verify that a cited passage supports an assertion; identify missing methodological details; link entities between articles.
Model Routing
- Build a router that chooses which LLM receives each prompt; rank intention and domain; estimate difficulty and risk; climb to a more expensive model when necessary.
LLM guardrails
- Place semantic checks on each LLM input, output and tool call at a fraction of the cost of the call.
- Detect yellowbreaks and prompt injections, policy violations, exposure of sensitive data, errors in tool calls; log structured results and probabilities.
Code semantic lint
- Add automated semantic lints to the code and text, according to the team's conventions, executed in IC.
Extraction of characteristics for prediction
- Extract probabilistic characteristics from texts, combine them with structured data, train models on known results; loops autoresearch propose and evaluate the definitions of characteristics.
Recruitment
- Evaluate CV, applications and interview returns against explicit job-related criteria; identify relevant experience; record skills; route and climb uncertain cases.
Lead generation
- Compare company profiles, executive biographies and incoming messages to an ideal customer profile; rate suitability; detect purchase intent and pain points; prioritize.
Customer support
- Sort tickets by problem, product, intention; extract problems and commitments from call transcripts; detect urgency, frustration, risk of departure, claims for reimbursement; route; check responses against policies.
Insurance claims
- File declarations, expert notes and exhibits; detect complexity, missing information, fraud clues; prioritize for direct processing or specialized review.
Financial crime
- Evaluate transaction stories, KYC documents, alert histories; bring entities with inconsistent names closer together; prioritize alerts; route ambiguous cases.
Legal and compliance
- Classify contracts, policies, regulatory repositories, marketing claims; detect missing clauses and prohibited claims; climb to lawyers.
e-commerce market places
- Normalize heterogeneous product sheets; extract attributes; detect counterfeits, abuse of opinions, policy violations; classify and route for human review.
Moderation, confidence and security
- Apply company-specific criteria; detect toxicity, harassment, spam, fraud, dangerous advice, personal data, unsubscribe requests; combine gravity and confidence to authorize, warn, review or block.
Advertising
- Evaluate creations, campaign texts, landing pages and placement context; classify brand security and audience suitability; verify compliance and alignment ad / page.
Video game
- Evaluate alerts, chat at stake, opinions and support conversations; moderate; annotate frustration and engagement; detect starting signals.
Risk assessment
- Convert incident reports, claims notes, transaction descriptions and vendor evaluations into probabilistic indicators; classify types of risk; note severity; feed into broader models.
Forecast of demand
- Enrich models for predicting semantic signals from customer requests, sales notes, notices, tickets and market reports; extract intent, urgency, product interest; detect supply tensions and competitive pressure.
Knowledge Graphs
- Annotate and verify graphs with typical semantic decisions; classify relationships and types of entities; detect contradictions; probabilistic path and hierarchical classification.
By form of decision
| Form | When to use it | Examples |
|---|---|---|
| Classification | A known category must prevail | Intention, subject, service, type of risk, type of entity |
| Detection | It takes the probability that a property is present. | Spam, fraud, emergency, jailbreaks, sensitive data |
| Scoring | The answer belongs to an ordered grid | Gravity, relevance, quality, frustration, adequacy |
| Routing | A category chooses the next code path | Use of tools, climbing, model routing, support lines |
| Search | Find the elements that respond to a natural language request | Semantic research, discovery of documents, generation of candidates |
| Recovery | A stream needs the most relevant context or records | Background RAG, Evidence, Knowledge Consultation |
| Ranking | Order by relevance or semantic quality | Search results, recommendations, prioritisation of candidates |
| Verification | Control an artifact against specific failure modes | Citation support, policy violations, tool call errors |
| Extraction of ML characteristics | A classic ML model needs semantic signals | Intention to buy, product interest, competitive pressure, departure |
| Extraction of structured data | Known fields must be recovered from an unstructured input | Attributes of candidates, order fields, document labels |
Practice: the HTTP API
An entry point, a JSON body, standard error codes. Here is the condensed reference, such as the page API reference I don't know.
The point of entry
POST https://api.typesafe.ai/v1/systemone
Authorization: Bearer <API_KEY>
Content-Type: application/json
The API key is created in the console (console.typesafe.ai/keys) The body has the three fields seen in Chapter 3, all required: state (string | object | array), model (string) and questions (map<string, Question>). Question is one of the three types; all share type and instructions, each has its own criteria.
| Type | instructions | criteria | Constraints |
|---|---|---|---|
| noul | required, string | object | array | optional: object {true, false} | — |
| choice | required | required: map<option, string | object | array | null> | not more than 255 options |
| score | required | required: array<string | object | array>, ordered from the bottom up | at least 2 levels, not more than 10 |
{
"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"
}
}
}
}
{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
"sales": "Pricing, upgrades, new accounts"
}
}
}
}
{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Frustrated", "Very angry"]
}
}
}
{
"model": "jev-1.13.0",
"answers": {
"is_urgent": { "type": "noul", "noul": 0.95 }
},
"usage": { "input_tokens": 307, "output_tokens": 20 }
}
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.88, "technical": 0.12, "sales": 0.0 },
"confidence": 0.81
}
},
"usage": { "input_tokens": 318, "output_tokens": 34 }
}
{
"model": "jev-1.13.0",
"answers": {
"frustration": {
"type": "score",
"score": 1.05,
"legend": { "0": "Calm", "1": "Frustrated", "2": "Very angry" },
"probabilities": { "0": 0.0, "1": 0.95, "2": 0.05 },
"confidence": 0.92
}
},
"usage": { "input_tokens": 304, "output_tokens": 18 }
}
Errors and flow limits
| Status | Meaning |
|---|---|
401 Unauthorized | API key absent or invalid. Check the header Authorization. |
422 Unprocessable Entity | The body failed to validate: missing required field, poorly formed question. The body of the answer refers to the wrongdoing field. |
429 Too Many Requests | Exceeded flow limit. Wait and try again. |
529 Overloaded | TypeSafe is temporarily overloaded. Try again after a short time. |
On 429 and 529, the instruction is to try again with a exponential backoff, not immediately. SDKs do it by default and respect the header retry-after when it is present. One final API detail: GET /v1/models returns the list of names your account can send to model, with description and date of release; it currently lists aliases, but versioned identifiers as jev-1.13.0 are accepted whether or not they appear.
422What to look at first?Practical: Python and JavaScript SDKs
Two official clients, both very recent at the read date: Python typesafe-sdk (first public version v0.5.7 on September 14, 2026, v0.7.0 on 18) and JavaScript @typesafe-ai/sdk (v0.5.7 on 11 September, v0.6.0 on 15).
- Install, configure by environment variables, call
system_one/systemOne. - Python : sync and async clients, answers typed by
response_model, retrieving, logging, ascending compatibility. - JavaScript : helpers
choice(),score(),noul(), inference of the types of response, retry policy.
Python
Request Python 3.10 or more. The client reads TYPESAFE_API_KEY in the environment and calls for jev-latest by default. Two customers: TypeSafeClient (synchronous) and AsyncTypeSafeClient. The answer shows answers (all responses, by ID) and three typed views, nouls, choices, scores.
pip install typesafe-sdk
# ou
uv add typesafe-sdk
export TYPESAFE_API_KEY="..."
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state={"document": "I was charged twice. Please fix this ASAP."},
questions={
"billing": Noul(instructions="Is this ticket about billing?"),
"tone": Choice(
instructions="What is the customer's tone?",
criteria={"calm": None, "frustrated": None, "angry": None},
),
"urgency": Score(
instructions="How urgent is this ticket?",
criteria=["can wait", "this week", "today"],
),
},
)
print(response.nouls["billing"].noul)
print(response.choices["tone"].choice)
print(response.scores["urgency"].score)
from typesafe_sdk import AsyncTypeSafeClient, Choice, Noul, Score
async def main() -> None:
async with AsyncTypeSafeClient() as client:
response = await client.system_one(
state={"document": "I was charged twice. Please fix this ASAP."},
questions={
"billing": Noul(instructions="Is this ticket about billing?"),
"tone": Choice(
instructions="What is the customer's tone?",
criteria={"calm": None, "frustrated": None, "angry": None},
),
"urgency": Score(
instructions="How urgent is this ticket?",
criteria=["can wait", "this week", "today"],
),
},
)
print(response.nouls["billing"].noul)
print(response.choices["tone"].choice)
print(response.scores["urgency"].score)
Typed answers with response_model
Since v0.7.0 (which replaced msgspec by pydantic), system_one accept one response_model : a subclass of SystemOneResponse which declares the expected answers, or even a pydantic model entirely to you. The result then exposes each answer as a typed attribute, and the query identifier via request_id.
from typesafe_sdk import Noul, NoulAnswer, SystemOneResponse, TypeSafeClient
class BillingResponse(SystemOneResponse):
billing: NoulAnswer
with TypeSafeClient() as client:
result = client.system_one(
"I was charged twice.",
{"billing": Noul(instructions="Is this about billing?")},
response_model=BillingResponse,
)
assert 0 <= result.billing.noul <= 1
assert result.billing == result.nouls["billing"]
print(result.request_id)
Retries, errors, logging, environment
One RetryPolicy is done to the customer or by call (RetryPolicy(max_retries=3, backoff_max=0.2, timeout=1.0) in the example). API errors remove TypeSafeAPIError, status and request_id. The SDK logs on the logger typesafe_sdk ; TYPESAFE_LOG_LEVEL accepts debug, info, warning, error or off. In debug, headers and bodies are journaled; secret headers are masked, bodies are not..
| Variable | Configure | Default |
|---|---|---|
TYPESAFE_API_KEY | API key (required) | — |
TYPESAFE_BASE_URL | API Root | https://api.typesafe.ai |
TYPESAFE_DEFAULT_MODEL | Default model | jev-latest |
TYPESAFE_LOG_LEVEL | Logger level, applied to import | Undefined (Python) · warn (JS) |
Upward compatibility
The SDK continues to work when the API evolves: extra_body send query fields that the version of the SDK does not know yet; dictionaries of raw questions pass as they are; unknown types of answer are ignored with a warning, and raw_http_response The doc states that these are emergency exits and that it is better to update the SDK.
JavaScript / TypeScript
Request Node.js 20 or more. The package provides ESM, CommonJS and TypeScript declarations. Three helpers build the questions, choice(instructions, criteria), score(instructions, criteria) and noul(instructions?, criteria?), and the types of answers are inferred from the questions : answers.department.choice is typed as one of the keys to your options.
npm install @typesafe-ai/sdk
export TYPESAFE_API_KEY="..."
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: { document: "I was charged twice. Please fix this ASAP." },
questions: {
category: choice("What is this ticket about?", {
billing: null,
technical: null,
other: null,
}),
},
});
console.log(response.answers.category.choice);
// Dérivé de la référence du SDK JS : les trois helpers dans une requête.
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient({ timeout: 10000, retry: { maxRetries: 2 } });
const { answers, model, usage } = await client.systemOne({
state: "I was charged twice. Please help ASAP.",
questions: {
billing: noul("Is this about billing?"),
tone: choice("What is the tone?", { calm: null, angry: null }),
urgency: score("How urgent is this?", ["low", "medium", "high"]),
},
});
console.log(answers.billing.noul, answers.tone.choice, answers.urgency.score);
console.log(model, usage.input_tokens);
The manufacturer TypeSafeClient(config) accepts apiKey, baseURL, defaultModel, timeout (by default, 10 000 ms), retry, logLevel, logger, defaultHeaders, fetch, and dangerouslyAllowBrowser (false by default: using the SDK in a browser exposes the key to visitors). Explicit options override environment variables, which override SDK defaults. The default retry policy allows 2 additional attempts, with an initial 500 ms backoff doubling up to 5,000 ms and 25% jitter, for status codes 408, 429, and 500–599, respecting Retry-After up to 60 seconds. Errors are dedicated classes (AuthenticationError, RateLimitError, UnprocessableEntityError, APITimeoutError...), all derived from TypeSafeError.
In both SDKs, versions 0.6.0 (15 September 2026) have changed Score.criteria : one ordered table instead of an integer indexed dictionary. Examples of this course use the current form. If you come across code with {0: "...", 1: "..."}, he's from before.
dangerouslyAllowBrowser ?Models, prices and limits
Only one current model, two aliases, one price per input token, context and flow limits. Everything fits in a table, taken from the page Models.
| Jev 1.13 | jev-1.13.0 |
|---|---|
| Price (per Btok / per Mtok) | $42 / $0.042, billed on entrance tokens; tokens are free of charge |
| Flow limits | 250,000 tokens per second / 1,200 queries per minute; beyond, 429 |
| Context length | 64k tokens per request; 32k tokens for the state the longer question |
| Input | Text only: channel, JSON object or text array. No image, no audio, no video |
Page prevents flow limits change without notice while TypeSafe absorbs a high demand and allows more users to enter; more stable limits will come "once things are calmed". Higher limits exist on personalized and corporate plans.
Alias
An alias is a name that resolves to a versioned identifier. jev-latest points to jev-1.13.0 : the most recent stable version, the default of SDKs and the name used in the examples. jev-preview points to the most recent version, official or not; it passes ahead jev-latest when a preversion exists. On the read date, both point to the same model: there is no preversion available.
Practical consequence highlighted by the page: an alias moves at each exit, so the answers behind can change nothing at home. The field model of the answer gives the versioned identifier that responded, to log. And if you have set confidence thresholds on a specific version, pin his ID rather than the alias, and migrate at your own pace.
Customizing Jev
Jev is neither fine-tuned nor adapted by LoRA with customer data; the same weights serve all accounts. by the request : owner content, records and references in the state ; business rules and limit cases in instructions and criteria ; large judgments decomposed into atomic questions combined in the code ; and, to go further, a classical model trained on Jev probabilities (cookbook) AutoResearch).
Languages and data
English is the main language of training, the language where precision is the best. Other languages, including CJK scripts, are processed but less well: test your content before you rely on it, and monitor the confidence in routing. Jev is not trained on customer requests and answers; the page Legal refers to the data processing agreement, to the privacy policy, and offers zero retention (ZDR) to business customers.
model send?Write integration by an agent: the skill TypeSafe
TypeSafe publishes a skill for Claude Code, Codex and other code agents: the three types of questions, patterns and good practices, so that the agent knows the forms of request and answer instead of inventing them.
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai
# mise à jour
claude plugin marketplace update typesafe-ai
claude plugin update typesafe@typesafe-ai
npx skills add typesafe-ai/skills --skill typesafe-ai
# -g pour une installation globale ; npx skills update pour mettre à jour
Choose a single installation method to avoid duplicates. SKILL.md is readable on GitHub (typesafe-ai/skills) In a hurry, name the skill ("use the TypeSafe skill") works with any agent; with the Claude Code plugin, one can also invoke /typesafe:typesafe-ai.
Three prompts suggested
- Explorer : « Using the TypeSafe skill, explore the project and find opportunities for using intelligent judgement to stand in for complex parsing or other fragile code. »
- Experiment with a key exported in
TYPESAFE_API_KEY"run some experiments... Proposes changes based on the most promising results." - Refactor from the cookbooks: « analyze my code and see if there are any applicable cookbooks... »
Principles of vibe coding according to page
- Discuss with the agent, starting from the above prompts.
- Read the plan before implementing it.
- Put constants (questions and thresholds) in one place, easy to read: agents are not very good at writing questions, expect to edit them with them.
- Do not take statements for cash; push the agent to validate his assumptions.
Common problems
- The agent does not use the skill : explicitly invoke it, check that the installation targeted this agent well, restart.
- Routing does not behave as planned Check questions and thresholds. Too high, false negatives; too low, false positives.
- Confidence thresholds everywhere : if one wants only the best option, take the one of higher probability, without threshold; for a precise statistical algorithm, use the probabilities.
- TypeSafe code is hard to read What humans need to read again are the questions and threshold constants; group them into a single file.
- The agent invents fields of request or response : an outdated skill; update and try again.
Page Primitives adds one more reason to install the skill: code agents fall more than humans in the habit of "a question by call." The skill tells them to group a lot of questions by call, including those that only matter for some entries.
Cookbooks: eighteen complete applications
The cookbooks are executed notebooks, with their numbers. They show Jev in real pipelines: extraction, classification, reclassification, verification, safeguards, self-coherence. Here is the map, filterable by primitive and technique, with the result announced by each.
What the cookbooks teach, in five ideas
- Find, then choose. Jev does not generate: when the answer is a text value, a regex or another model finds the candidates and a Choice chooses (Pre-parsed value extraction, Date extraction, Function callingThe returned value is copied as it is, never invented.
- One request per document, as many questions as necessary. Parallel questions measures group gain: about ten times cheaper and faster on a 54,000-character document, with the same answers.
- confidence separates easy cases from hard cases. Classification using confidence class 60 annual reports in 75 industrial groups: at the threshold 0.9, the confident half is just 90 %, the other 40 %; up one step in the hierarchy, this half increases to 70 %, without second call.
- Check, cheaper than producing. SDE cascade extract with a small model, check each field with Nouls TypeSafe, and climb to the big model of reasoning only if a signal lights up. Citation check and Guardrails apply the same idea to quotes and messages from an LLM.
- Stable answers. The two cookbooks Self-consistency play 15 times the same rubric and compare the dispersion of Jev's responses with those of LLM, including at temperature 0.

/cookbooks/pre_parsed_value_extraction_cookbook) : the regex finds the candidate values, TypeSafe chooses one, the code normalizes it and acts.
/cookbooks/date_extraction_cookbook) : TypeSafe reads how the date is written and which parts the text names; the code assembles the date, counts from today if it is relative, and accepts it or sends it to reread.
/cookbooks/sde_cascade) : the quality / cost compromise over 100 prompts between the small model, the reasoning model, and the cascade verified by TypeSafe.
/cookbooks/rerank_typesafe) : Quick search (BM25) for the short list, then reclassification by a TypeSafe question per request-candidate pair.Several cookbooks (Classification using confidence, Line-by-line search, among others) deliver a JSON cache of their API calls (json_cache.json, or class JsonCache of the package cooksafe), so that the notebook replays without key or expense. Delete the cache restarts all live. The installation type: pip install "typesafe-sdk>=0.5.7" cooksafe --extra-index-url https://pypi.typesafe.ai/.
The known limits: Jev 1.13's jaggedness
The documentation keeps an honest page of what jev-1.13 does poorly, reviewed on September 17, 2026. Fast, calibrated, and good at common-sense judgment; but literal, poor at arithmetic, and fragile with indirection. Nine failure modes, with an alternative for each.
| # | Mode of failure | Doing more |
|---|---|---|
| 1 | Literal reading | Write the exact condition, criteria for each option |
| 2 | Mathematics and Numbers | Keep arithmetic in code |
| 3 | Comparison of dates and hours | Extract components; compare in code |
| 4 | Indirection | Reduce jumps; point the relevant state |
| 5 | Great state full of unnecessary details | Filter first; send only what the question needs |
| 6 | Adversarial content | Write accurate prompts, test limit cases before deploying |
| 7 | Contradictory instructions and criteria | Align criteria and instruction |
| 8 | Common sense structural invariants | Set each decision in one way; impose identities in the code |
| 9 | Generation | Use a generic model |
1 · Literal reading
jev-1.13 answers the written question, not the one you meant. Words of scope, negations, implicit conditions are read at the foot of the letter, where a person would have read the intention.
Instead : write the exact condition in instructions, put the limit cases in the criteria. When, in the face of a bad answer, you discover to explain "what I really wanted to say", this explanation is the missing half of the instruction. If the interpretation is inevitable, divide it into two literal questions combined in the code.
2 · Mathematics and numbers
Jev is not a calculator. Three documented cases:
- Counting : characters of a word, occurrences of a term, elements of a long list. The model recognizes the form of an answer rather than it counts, and the error grows with the size. Instead: iterate in the code on the candidates, place a Noul per element, add yourself (example of the page: "
items[i]Is it a fruit name?" for each element). - Digital representations : hexadecimal colors work less well than color names; Jev can't judge whether two RGB triplets are close; the assembler or binary less well than a high-level language. Instead: convert in code, pass a calculated number or a named category, keep the model for judgment ("does this color read as a warning?").
- Calculation from score : do not use a score to reconstruct the exact size between two levels; a threshold, yes, an interpolation, no.
3 · Comparison of dates and hours
Jev reads the dates as text, not as ordered quantities: which comes first, what deviation, in which window. Worse with mixed formats, relative references and domain terminals (quarters, settlement windows).
Instead The extraction is a judgment, the model takes care of it; the arithmetic remains in the code. Each part of a date is a small closed set (twelve months, thirty-one days, a range of years): the extraction becomes a Choice on listed options, with an explicit option "not indicated". Date extraction Makes the full version.
4 · Indirection
Double denials, complex indirection, question about property ownership, several leaps of reasoning: so much lost precision. Instead : instructions as direct as possible, and name the parts of the state concerned.
5 · Great state full of unnecessary details
Accuracy declines when the state grows with content unrelated to the decision: unnecessary detail distracts, and a large state makes it difficult to know which part produced a bad answer. Instead : retrieve and filter in the code, only send the necessary fields; when you can't filter, a relevant Noul can do so (cookbook Classifying RAG passages) Reminder of the limits: 64k tokens per request, 32k for the longest question.
6 · Adversarial content
The state is data, and Jev does not treat it as hostile by default. A written content to guide the model (injected instruction, misleading framing, text that pleads for its own classification) can move the answer. TypeSafe says it expects to improve on this point. Instead : explicit criteria, and in-depth tests before exposing integration to many users.
7 · Contradictory instructions and criteria
When instructions and criteria ask for different things, the model can be lost. true corresponds to "no" and false to "yes" will work less well. Instead : treat the criteria as an extension of instruction, with a language that the average person reads effortlessly.
8 · Common sense structural invariants
jev-1.13 is very constant: semantically close entries give quantitatively close outputs. But many of the invariants that one would imagine holding are not guaranteed. Two measurements of the page:
| "Is the customer asking for a refund?" on "I'm not happy with the fit. What are my options here?" | Noul noul | Choice yes | Choice no | Choice confidence |
|---|---|---|---|---|
| Same question, two types | 0.22 | 0.01 | 0.99 | 0.97 |
| On "I was charged twist for the same order. Can someone look inside this?" | refund | not_refund | Sum |
|---|---|---|---|
| One question and its denial, two Nouls | 0.72 | 0.47 | 1.19 |
Instead : do not rely on an expected invariance ; formulate the questions so that they say directly what one wants ; do not transpose a threshold set on a Noul to a Choice ; do not require the model of arithmetic identities between separate questions. A Choice is relative (it slices out) which one), a Noul is absolute (it can be low for all options); the cookbook Skill suggestion Use both on the same list.
9 · Generation
Jev is not trained to generate text. We can force it by chaining Choices, but it works badly and very slowly. Instead When the response space is limited, turn extraction into Choice on options rather than asking for the value itself; and to really generate text, "there are other models for that."
- Ask the model something the code can calculate exactly.
- cache several judgments in one question.
- "System 2" tasks: more indirection layers.
- Give more context in the
statethat the question needs it: Jev suffers from the context rot, the unrelated material costs precision.
Summary, glossary and final quiz
What to keep from the course, in one page. Then five questions that go through all the chapters.
| System One | A class of models for fast and structured decisions, consumable by software. Jev is the first. No text generated; typed decisions and calibrated probabilities (RLCD). |
| Request | state (string, object or table) + model + questions (map of identifiers to typed questions) One entry point: POST /v1/systemone. |
| Choice | An option from a fixed set (up to 255). choice, probabilities, confidence. Add other if the list may not cover everything. |
| Score | A position on ordered levels (2 to 10), described as situations. score (weighted average of level numbers), legend, probabilities, confidence. |
| Noul | The probability of a yes, from 0 to 1. No separate confidence. The threshold depends on the cost of the error; the environment can go to one person. |
| Confidence | Derived from the form of probabilities Three ranges: act, confirm, climb. Thresholds follow the risk of each action and live in the code. |
| Build | Code when one can ; decomposed and structured state ; atomic questions, structured if necessary ; many questions per request ; composition in the code ; routing on uncertainty. |
| Patterns | Speculative fan-out, Confidence-gated routing, Composite scoring, Intent routing. A second query only when the first answer is necessary to build the second. |
| Model | jev-1.13.0 ; alias jev-latest and jev-preview (same as the read date). $0.042 per Mtok in entry, free output; 64k / 32k tokens; text only; English first. |
| Limitations | Literal, no calculator, text dates, fragile to indirection and noise in the state, sensitive to adversarial content, no invariants between types, does not generate. |
Glossary
- state : the content to be evaluated, sent in the field
state. - question : a typical judgment requested of the model; answer : the typed value returned under the same identifier.
- instructions : the question asked; criteria : options (Choice), levels (Score) or definition of yes and no (Noul).
- probabilities : distribution on options or levels, sum 1 ; legend : the levels of a Score recalled by number.
- confidence : summary of the concentration of the distribution (Choice and Score) from 0 to 1.
- speculative question : placed in advance, in the same call, before knowing if it will serve.
- context rot : loss of precision when the state contains matter unrelated to the question.
- RLCD : Reinforcement Learning for Calibrated Decisions, TypeSafe post-training.
- calibration - on a batch of predictions, a predicted probability of p is determined in a proportion close to p.
- jaggedness : "irregular edges", tasks on which the model is much less good than on others.
probabilities {0: 0, 1: 1, 2: 0}, the second {0: 0.5, 1: 0, 2: 0.5}.To go further
- The Playground : paste a text, add questions, see answers. This is the starting point recommended by the Quick start.
- The documentation, whose index llms.txt ; each page exists in Markdown by adding
.mdto his URL. - The manifest TypeSafe, and the community Discord where the page Jaggedness calls for new methods of failure to be reported.
Course generated on September 20, 2026 from the official documentation of TypeSafe AI (docs.typesafe.ai, index llms.txt, pages read in Markdown ; dates of updating the pages according to the sitemap : from 26 August to 20 September 2026). assets/origin/ No value has been invented: the interactive modules reproduce recorded answers cited by the documentation, or are marked as illustrations. Nothing is sent on the network to the execution.