A support router that asks a chat model to “reply with JSON” still has to parse a string. Jev is built for the other shape. You send the ticket as state, name the questions, and the client returns a choice or a probability. The application, not the model, decides whether to file the ticket, hold it, or page someone.
This host does not have a TypeSafe key, so the Jev call stopped before the network. The OpenAI platform key on the same host can list models, including gpt-6-luna, and then refuses inference because the credit balance is exhausted. The code below is the call that was constructed, the errors that came back, and the gate that still runs on a fixture of the same answer shape.
What you are asking each model to do
Jev is TypeSafe’s System One model. The docs describe it as a model that evaluates a state and returns typed answers, not a reply. A Choice question picks one label you supplied. A Noul question returns a probability for a yes-or-no statement. A Score question rates ordered levels. The current model page lists jev-1.14.0 and jev-1.13.0, with the SDK default alias jev-latest still pointing at 1.13.0 as of that page. Output tokens are not billed. Input is listed at $0.042 per million tokens.
OpenAI does not publish a separate System One endpoint. The practical substitute on an ordinary API key is Structured Outputs on a small model. gpt-6-luna is the cheap high-volume model in the GPT-6 family: $0.10 per million input tokens and $0.50 per million output tokens on the model page, with structured outputs supported. It still generates text that the SDK then parses into your schema. That is not the same object as a Jev answer. It is the call you can make when TYPESAFE_API_KEY is absent and an OpenAI key is the credential you already have.
The ticket used for both calls was the same short report: CSV export broken since Friday, fine in Chrome, broken for the Safari half of the team, board meeting on Thursday. No refund is mentioned. The queues are bug_triage, incident_response, and customer_success.
Build the Jev request with the installed SDK
typesafe-sdk 0.7.5 is what imported on this machine. Choice requires a criteria map. Noul takes the yes-or-no instructions. TypeSafeClient() reads TYPESAFE_API_KEY unless you pass api_key. Do not put the key in the source file.
from typesafe_sdk import Choice, Noul, TypeSafeClient
ticket = (
"Export to CSV has been broken since Friday. It works in Chrome, "
"but half the team is on Safari and they cannot pull reports. "
"We have a board meeting Thursday."
)
questions = {
"queue": Choice(
instructions="Which queue should own this ticket?",
criteria={
"bug_triage": "A reproducible defect in one feature.",
"incident_response": "A live breakage affecting many users today.",
"customer_success": "The account needs managing, not the code.",
},
),
"refund_requested": Noul(
instructions="Does this message request a refund?",
),
}
with TypeSafeClient() as client:
response = client.system_one(state=ticket, questions=questions)
Serializing those question objects, without sending them, produced this body. That is the shape system_one is wrapping. The type field is set by the class, not by the caller.
{
"queue": {
"type": "choice",
"instructions": "Which queue should own this ticket?",
"criteria": {
"bug_triage": "A reproducible defect in one feature.",
"incident_response": "A live breakage affecting many users today.",
"customer_success": "The account needs managing, not the code."
}
},
"refund_requested": {
"type": "noul",
"instructions": "Does this message request a refund?"
}
}
Running the with TypeSafeClient() block on this host raised TypeSafeError and the message No API key was provided. Pass api_key or set the TYPESAFE_API_KEY environment variable. No HTTP status came back, because the SDK refused to build a client. That is the check you want in a deploy: missing vendor configuration should fail before a ticket is filed by guesswork.
When a key is present, read response.answers["queue"].choice and the confidence or probability field documented for that primitive. Do not treat a high probability as permission to refund. The docs are explicit that calibration is a property of a set of predictions, not a promise that one answer is correct. A wrong-but-valid queue is still possible. The label can only be one of the criteria keys, which is the property JSON-mode chat calls do not give you.
Ask GPT-6 Luna for the same decision
The OpenAI path uses the Responses API and a Pydantic model. gpt-6-luna accepts reasoning.effort of none, which is the setting to use for a routing call. A reasoning model that writes a paragraph and then a JSON object is the wrong tool for this job, even if the schema parse succeeds.
from openai import OpenAI
from pydantic import BaseModel, Field
class Route(BaseModel):
queue: str = Field(description="bug_triage, incident_response, or customer_success")
refund_requested: bool
client = OpenAI()
response = client.responses.parse(
model="gpt-6-luna",
reasoning={"effort": "none"},
input=[
{
"role": "system",
"content": (
"Return only the structured decision. "
"Do not invent a queue outside bug_triage, "
"incident_response, or customer_success."
),
},
{"role": "user", "content": ticket},
],
text_format=Route,
)
decision = response.output_parsed
client.models.list() with the configured platform key succeeded and included gpt-6-luna. The responses.parse call then failed with HTTP 429 and code credit_balance_exhausted: “You have no credits remaining.” Listing models did not prove that inference would run. A deploy check has to send one small parse, not only list the catalog.
Codex on this host was not logged in, so it was not a second path around that 429. The competitor the key can select is still gpt-6-luna. The key has to have credits before the example returns a Route.
Luna’s answer is a parsed object, not a Jev choice plus a calibrated probability. If you need a confidence number from Luna, put it in the schema yourself and treat it as the model’s stated score, not as a TypeSafe calibration. The gate below does not trust that number past the threshold you set.
Keep the action in ordinary code
Neither client should open a ticket. A function that only sees queue, confidence, and refund_requested can be tested without either vendor. These four fixtures were run locally. An unknown queue returns review even when the confidence is 0.99. A known queue under 0.8 returns review. incident_response at 0.95 returns escalate. A confident bug_triage with no refund request returns auto.
def route(queue, confidence, refund_requested):
if queue not in {"bug_triage", "incident_response", "customer_success"}:
return "review"
if confidence < 0.8:
return "review"
if refund_requested and queue != "customer_success":
return "review"
if queue == "incident_response" and confidence >= 0.9:
return "escalate"
return "auto"
Wire Jev into that function by reading the choice and the confidence field from response.answers. Wire Luna into it by reading decision.queue and, if you asked for one, decision.confidence. If either call raises, return review. The TypeSafe missing-key error and the OpenAI 429 are both that case. A router that crashes into an uncaught exception will drop the ticket. A router that catches the exception and holds the ticket for a person will not.
The threshold 0.8 is a policy choice, not a value either vendor published for this ticket. Change it in the function, and keep the fixture next to the change. Do not hide the threshold in a prompt.
What this does not show
There is no live Jev probability in this article, and no live Luna parse. The installed SDK built the question payload. The TypeSafe client stopped on a missing key. The OpenAI client listed gpt-6-luna and then refused the parse for lack of credits. Those three results are enough to wire the calls and the failure path. They are not a measurement of either model’s accuracy on support mail.
When a TypeSafe key is added, rerun the same system_one call and record response.model, the queue, and the confidence. When the OpenAI balance is restored, rerun the same responses.parse call and record response.model, token counts, and the parsed queue. Do not copy the vendor marketing latency into that note unless you measured it on the same ticket.
Sources
- TypeSafe, System One: https://docs.typesafe.ai/concepts/system-one
- TypeSafe, Models (
jev-1.14.0,jev-1.13.0, aliases): https://docs.typesafe.ai/models typesafe-sdk0.7.5, imported and serialized on this host- OpenAI, Structured Outputs: https://platform.openai.com/docs/guides/structured-outputs
- OpenAI, GPT-6 Luna: https://platform.openai.com/docs/models/gpt-6-luna