Skip to content

Support ticket routing

expilu edited this page Oct 3, 2026 · 3 revisions

Support ticket routing

The decision every support tool has somewhere in the middle: which department takes this ticket. This example uses [[choice]] over a fixed set of departments, with a move to a human when the answer comes back too unsure to trust.

The departments

The option set the decision runs on:

const criteria = {
  billing: "Customer asks about invoices, payments, refunds or charges",
  technical: "Customer reports a bug, error or product malfunction",
  sales: "Customer asks about pricing, plans or upgrading",
  account: "Customer needs help with login or account access",
};

The keys are names your code will route on. The values are written for the model, not for customers: each description lists the words and situations that trigger this department, so the model matches the message against these lines rather than against a bare name like "billing". Keep the descriptions concrete and self standing; overlapping departments split the vote and show up later as low confidence.

The code

import { choice } from "smart-decisions";

const model = {
  apiBaseUrl: "http://localhost:8000/v1",
  apiKey: "a-super-secret-api-key",
  model: "/models/Qwen3.5-4B-Q4_K_M.gguf",
};

const criteria = {
  billing: "Customer asks about invoices, payments, refunds or charges",
  technical: "Customer reports a bug, error or product malfunction",
  sales: "Customer asks about pricing, plans or upgrading",
  account: "Customer needs help with login or account access",
};

const answer = await choice({
  model,
  mode: "system1",
  state:
    'Customer support ticket:\n"Hi, I was charged twice this month. Can you refund the extra payment?"',
  instructions: "Classify the ticket into the right department",
  criteria,
});

if (answer.confidence < 0.5) {
  forwardToHuman(answer); // too unsure to act autonomously
  return;
}

routeTo(answer.choice); // 'billing'

forwardToHuman and routeTo are your code, not the library's. The library's contract ends at the numbers: the winning option, the distribution and the confidence. What your software does with them is the actual use case.

What comes back

With the ticket above, billing carries nearly all the probability mass: the message talks about charges and refunds, which match the billing description almost word for word. The distribution stays tight (look at how little weight the others hold) and the confidence comes back well over 0.9, so no escalation fires and the ticket arrives in billing.

The interesting tickets are the ambiguous ones. "I can't get in, and by the way you keep charging me" reads like both account and billing. The model splits the probability mass between departments the ticket genuinely touches, the confidence drops, and the confidence check keeps a hardcoded reading out of your pipeline. Instead of a misroute that a customer has to dig out of, the ticket goes to a person, who decides.

Tuning the threshold

Where 0.5 falls for you is a business decision. A misroute that wastes minutes of engineering time on a billing question tolerates a higher threshold than a misroute of one department into her neighbors. You will see the number that works for your traffic only in production, so the boring job is watching the low confidence rate and adjusting the threshold: raise it when escalations stay clear of annoyance and lower it when the human queue drowns.

Variations

  • Add a fifth other department with a deliberately vague description ("Does not clearly fit the above"). On junky or out of scope tickets it soaks the answer, keeps other from competing with the real departments, and produces a cleaner signal than forcing a bad fit somewhere legitimate.
  • Run small noul calls alongside the choice answer for concerns that sit across departments: an outage, an angry customer, a legal mention. Each judges one condition, and their numbers combine in your code with the routing answer.
  • Log the full probabilities map, not only the winner. Over time the distributions tell you which department descriptions were drawn too wide for real traffic long before the low confidence alert does.
  • Let the escalation-to-a-human step be handled by the model itself: mode: 'auto' (Auto mode) escalates low-confidence System 1 verdicts to a deliberate System 2 pass (Under the hood) that may well reach the right department without a person; a human only sees tickets a confident System 2 cannot place either.

Clone this wiki locally