Some AI tasks end with a sentence. Others end with a branch in your code.
Which queue should receive this support ticket? Does the customer request a refund? How urgent is the issue? For those questions, I want a value my application can inspect—not a paragraph I need to parse.
That is what brought me to Jev, TypeSafe’s decision model. I built a small client around it, then used the same interface to compare it with a locally running alternative.
Three ways to ask
Jev accepts a state object and a set of typed questions. TypeSafe’s documentation describes three primitives:
| Type | What I use it for | Result |
|---|---|---|
choice |
Routing to one of several teams | A selected option, probabilities, and confidence |
score |
Evaluating an ordered rubric | A score, probabilities, and confidence |
noul |
Testing one specific statement | A value between zero and one |
The distinction is useful because it makes the contract explicit. A refund flag and an urgency rubric are different questions, even if they concern the same message.
A small support example
Here is the fictional input in my starter project:
state = {
"subject": "Duplicate charge",
"body": "I was charged twice. Please refund one payment."
}
A focused question can be written as:
questions = {
"refund": {
"type": "noul",
"instructions": "The customer explicitly requests a refund."
}
}
result = jev_client.decide(state, questions)
probability = jev_client.noul(result, "refund")
The complete example also includes a team choice and an urgency score. It submits the questions together, while keeping each one narrow enough to review independently.
I do not hard-code an expected answer into the example. It calls the real API, so running it requires your own account credentials and can incur usage charges.
Keep credentials out of the project
The client reads TYPESAFE_API_KEY from the environment. It does not contain a bundled key, ask for one in the browser, or require editing the Python source to add a credential.
The starter exits with a clear message when no key is configured. The optional cost estimate is also explicit: set JEV_INPUT_USD_PER_MILLION only after checking the provider’s current rate. An absent rate is shown as unavailable, not as a free call.
Structured does not mean automatically correct
A typed response solves an integration problem. It does not remove the need to evaluate the answer.
During my experiments, changing a question’s wording changed its behavior substantially. Asking about what an “image shows” when I supplied only a filename was a different task from asking what that filename described.
I also encountered inconsistent probabilities for nested time windows. If several questions describe related events, the application should check the relationships between their outputs rather than assuming independent scores form a coherent whole.
Before using a score to route or block anything important, I would build a small labeled set, inspect the mistakes, and choose a threshold for that particular workflow.
Where the starter leads
My next step was to compare the same request shape across Jev and Laya. That made it easier to separate client integration from model behavior—and to discover that good results on easy examples did not necessarily transfer to harder ones.
The starter is intentionally small: a standard-library Python client and one fictional workflow. It gives me a concrete place to begin testing, without hiding the question or the response behind a large framework.
