Routing · Mixed
Route Customer Support Tickets with Jev
Use Jev Choice, Noul, and Score together to route support tickets, flag urgency, and keep assignment logic in code.
- Published
- Sep 20, 2026
- Updated
- Sep 20, 2026
- Last verified
- Sep 20, 2026
Quick answer
Assign a ticket to billing, technical, account, or other, and score urgency in the same Jev call.
Problem
A support inbox mixes refunds, outages, and login problems. A single LLM prompt that returns free text is slow to parse and easy to break. You need a closed set of queues plus a separate urgency signal before auto-assignment.
Why Jev fits this task
Department is an unordered closed set — a Choice. Same-day risk is a yes/no judgment — a Noul. Frustration is an ordered rubric — a Score. Official docs say these can share one state and run in parallel, so you pay for the ticket text once.
Input state
Send only the fields the questions name. Official docs warn that extra unrelated state costs accuracy.
{
"ticket": {
"subject": "Charged twice for order A-104",
"message": "I was billed twice for order A-104 this morning. Please refund the duplicate today — I need the cash back before payroll."
},
"customer": {
"plan": "pro",
"tenure_months": 14
}
}Question
Which team owns this ticket, does it need same-day action, and how frustrated is the customer?
Question type: Mixed.
Jev schema
{
"model": "jev-latest",
"state": {
"ticket": {
"subject": "Charged twice for order A-104",
"message": "I was billed twice for order A-104 this morning. Please refund the duplicate today — I need the cash back before payroll."
},
"customer": {
"plan": "pro",
"tenure_months": 14
}
},
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle `ticket.subject` and `ticket.message`?",
"criteria": {
"billing": "Charges, invoices, refunds, subscriptions, payouts",
"technical": "Bugs, outages, integrations, product behavior",
"account": "Login, permissions, profile, email change",
"other": "None of the above"
}
},
"urgent": {
"type": "noul",
"instructions": "Does `ticket.message` ask for same-day action or mention money, outage, or blocked work?",
"criteria": {
"true": "A deadline today, lost money, or a hard stop is stated",
"false": "No deadline and no consequence is named"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated does the writer sound in `ticket.message`?",
"criteria": [
"Calm, stating facts only",
"Frustrated but civil",
"Angry, threats, or all-caps abuse"
]
}
}
}Python example
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
state = {
"ticket": {
"subject": "Charged twice for order A-104",
"message": "I was billed twice for order A-104 this morning. Please refund the duplicate today — I need the cash back before payroll.",
},
"customer": {
"plan": "pro",
"tenure_months": 14,
},
}
with TypeSafeClient() as client:
response = client.system_one(
state=state,
questions={
"department": Choice(
instructions="Which team should handle `ticket.subject` and `ticket.message`?",
criteria={
"billing": "Charges, invoices, refunds, subscriptions, payouts",
"technical": "Bugs, outages, integrations, product behavior",
"account": "Login, permissions, profile, email change",
"other": "None of the above",
},
),
"urgent": Noul(
instructions="Does `ticket.message` ask for same-day action or mention money, outage, or blocked work?",
criteria={
"true": "A deadline today, lost money, or a hard stop is stated",
"false": "No deadline and no consequence is named",
},
),
"frustration": Score(
instructions="How frustrated does the writer sound in `ticket.message`?",
criteria=[
"Calm, stating facts only",
"Frustrated but civil",
"Angry, threats, or all-caps abuse",
],
),
},
)
print(response.answers["department"].choice)
print(response.model)TypeScript example
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: {
"ticket": {
"subject": "Charged twice for order A-104",
"message": "I was billed twice for order A-104 this morning. Please refund the duplicate today — I need the cash back before payroll."
},
"customer": {
"plan": "pro",
"tenure_months": 14
}
},
questions: {
department: choice("Which team should handle `ticket.subject` and `ticket.message`?", {
billing: "Charges, invoices, refunds, subscriptions, payouts",
technical: "Bugs, outages, integrations, product behavior",
account: "Login, permissions, profile, email change",
other: "None of the above",
}),
urgent: {
type: "noul",
instructions: "Does `ticket.message` ask for same-day action or mention money, outage, or blocked work?",
criteria: {"true":"A deadline today, lost money, or a hard stop is stated","false":"No deadline and no consequence is named"},
},
frustration: score("How frustrated does the writer sound in `ticket.message`?", [
"Calm, stating facts only",
"Frustrated but civil",
"Angry, threats, or all-caps abuse"
]),
},
});
console.log(response.answers.department.choice);
console.log(response.model);JavaScript example
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: {
"ticket": {
"subject": "Charged twice for order A-104",
"message": "I was billed twice for order A-104 this morning. Please refund the duplicate today — I need the cash back before payroll."
},
"customer": {
"plan": "pro",
"tenure_months": 14
}
},
questions: {
department: choice("Which team should handle `ticket.subject` and `ticket.message`?", {
billing: "Charges, invoices, refunds, subscriptions, payouts",
technical: "Bugs, outages, integrations, product behavior",
account: "Login, permissions, profile, email change",
other: "None of the above",
}),
urgent: {
type: "noul",
instructions: "Does `ticket.message` ask for same-day action or mention money, outage, or blocked work?",
criteria: {"true":"A deadline today, lost money, or a hard stop is stated","false":"No deadline and no consequence is named"},
},
frustration: score("How frustrated does the writer sound in `ticket.message`?", [
"Calm, stating facts only",
"Frustrated but civil",
"Angry, threats, or all-caps abuse"
]),
},
});
console.log(response.answers.department.choice);
console.log(response.model);cURL example
curl -s https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"model": "jev-latest",
"state": {
"ticket": {
"subject": "Charged twice for order A-104",
"message": "I was billed twice for order A-104 this morning. Please refund the duplicate today — I need the cash back before payroll."
},
"customer": {
"plan": "pro",
"tenure_months": 14
}
},
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle `ticket.subject` and `ticket.message`?",
"criteria": {
"billing": "Charges, invoices, refunds, subscriptions, payouts",
"technical": "Bugs, outages, integrations, product behavior",
"account": "Login, permissions, profile, email change",
"other": "None of the above"
}
},
"urgent": {
"type": "noul",
"instructions": "Does `ticket.message` ask for same-day action or mention money, outage, or blocked work?",
"criteria": {
"true": "A deadline today, lost money, or a hard stop is stated",
"false": "No deadline and no consequence is named"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated does the writer sound in `ticket.message`?",
"criteria": [
"Calm, stating facts only",
"Frustrated but civil",
"Angry, threats, or all-caps abuse"
]
}
}
}
EOFExpected output
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice",
"choice": "billing",
"probabilities": {
"billing": 0.91,
"technical": 0.05,
"account": 0.02,
"other": 0.02
},
"confidence": 0.88
},
"urgent": {
"type": "noul",
"noul": 0.94
},
"frustration": {
"type": "score",
"score": 1.1,
"legend": {
"0": "Calm, stating facts only",
"1": "Frustrated but civil",
"2": "Angry, threats, or all-caps abuse"
},
"probabilities": {
"0": 0.05,
"1": 0.8,
"2": 0.15
},
"confidence": 0.7
}
},
"usage": {
"input_tokens": 340,
"output_tokens": 42
}
}Confidence handling
If department.confidence is below your review floor, skip auto-assign and send to triage. Official confidence docs show example floors of 0.5 for uncertainty and 0.9 before a high-stakes action — those are examples, not defaults. Urgent.noul has no separate confidence field; threshold the probability itself.
Production considerations
Log response.model and the raw probabilities. Pin a versioned model ID after you tune thresholds. Add an other option so unknown tickets are not forced into technical. Do not send the customer's entire history — official notes say unrelated state hurts accuracy.
Helpdesk routers, shared inboxes, and first-line Slack bots that assign a queue before a human opens the ticket.
When to use Jev
You already know the legal queues and you want milliseconds plus a typed label.
When not to use Jev
The next step is writing a customer email, diagnosing a unique outage, or deciding refund eligibility from ledger math.
Jev will not write the reply, check whether a charge actually duplicated, or compare timestamps. Official jaggedness notes: keep arithmetic and date windows in code.
Common mistakes
- Omitting other, then watching every weird ticket land in technical.
- Hiding routing, urgency, and tone in one Choice.
- Treating frustration.score 1.0 as exact without reading probabilities.
FAQ
Why not one Choice that includes urgent_billing?
Urgency and department are independent. Official docs recommend atomic questions, then compose in code.
Can I add more queues later?
Yes. Choice allows up to 255 options. Re-test boundaries when you add a neighbor queue.
Sources
- API referenceTypeSafe · accessed 2026-09-20 · documentation
- Primitives (Questions)TypeSafe · accessed 2026-09-20 · documentation
- ConfidenceTypeSafe · accessed 2026-09-20 · documentation
- Intent routingTypeSafe · accessed 2026-09-20 · documentation