Use case · Customer support
Support ticket triage and routing
Ask every routing question in one call (team, urgency, refund, frustration), then route in code and send uncertain tickets to a human.
The problem
Manual triage is slow and inconsistent, and a single "which queue?" model can't explain itself. Splitting triage into small questions gives you a team, a priority and the signals behind them, in about the time of one request, so tickets land in the right place on arrival.
How to frame it
Use the ticket (subject, body, customer tier) as the state. Ask everything you might need in one request, even questions that only matter for some tickets: which team, how urgent, whether a refund is requested, whether there are reproduction steps, how frustrated the customer is. This "fan-out" costs nearly nothing extra because questions run in parallel, and your code simply ignores answers it doesn't need.
Give each team option a short description of what belongs there. Then route on confidence as well as the answer: a confident "technical" goes straight to engineering, a 51/49 call goes to a human dispatcher.
The questions
teamchoice Which team should handle this ticket?urgencyscore How urgent is this ticket?refund_requestedyes_no Does the customer ask for a refund or money back?has_repro_stepsyes_no Does the ticket include steps to reproduce the problem?frustrationscore How frustrated is the customer?
Example request
{
"model": "ora1",
"state": {
"subject": "Checkout broken since this morning",
"customer_tier": "enterprise",
"body": "Since about 9am every checkout attempt fails with a 500 error. We can't take any orders. Steps: add any item to the cart, click Pay, error page. This is costing us money every minute."
},
"questions": {
"team": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"technical": "Bugs, errors, outages, integration problems",
"billing": "Charges, invoices, refunds, payment methods",
"account": "Login, passwords, user access",
"sales": "Pricing, upgrades, new contracts",
"other": "Anything else"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": [
"Can wait",
"This week",
"Today",
"Emergency: business is blocked right now"
]
},
"refund_requested": {
"type": "yes_no",
"instructions": "Does the customer ask for a refund or money back?"
},
"has_repro_steps": {
"type": "yes_no",
"instructions": "Does the ticket include steps to reproduce the problem?"
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": [
"Calm",
"Frustrated but civil",
"Very angry"
]
}
}
}Turn answers into actions
a = response["answers"]
team = a["team"]
if team["confidence"] < 0.5:
queue = "dispatch" # unsure: a person decides
else:
queue = team["choice"]
priority = "high" if a["urgency"]["score"] >= 2 or a["frustration"]["score"] >= 1.5 else "normal"
if queue == "billing" and a["refund_requested"]["yes_no"] >= 0.7:
tags.append("refund")
if queue == "technical" and a["has_repro_steps"]["yes_no"] >= 0.6:
tags.append("ready-for-engineering")
Tips
- Describe each team in plain words; descriptions matter more than names.
- Add an explicit "other" option so odd tickets don't get forced into a team.
- Track how often humans overrule the routing, per team, to find options that need better descriptions.