One support ticket, four AI models
Most AI support pipelines have one model in them: a large language model, prompted to do everything. It reads the ticket, guesses the category, decides if it's urgent, writes the reply and, if anyone thought about it, grades its own reply.
It works in the demo. In production you end up parsing free text for a category name, retrying when the format drifts, paying for tokens on every label, and trusting the model that wrote the reply to tell you whether the reply is correct.
There's a better split. Four kinds of model, each doing the one job it's built for:
- A decision model judges the ticket: which team, what tone, how urgent.
- A trained ML model predicts what your history says will happen: will this escalate?
- An LLM writes the reply.
- An NLI model checks the reply against your policy before it goes out.
This post runs one ticket through all four, in one database. If you want the "when do I pick which" guide first, read LLM, NLI, ML or a Decision Model: Which One Should You Use?. This post is the worked example.
The ticket
"I was charged twice for order 4471 and I want my money back today." — Dana, customer for 3 months, 4 tickets in the last 90 days, order value $820, waiting 30 hours.
The refund policy has two lines that matter:
- If a customer is charged twice for the same order, the duplicate charge is refunded.
- Refunds go back to the card that was charged.
Step 1: Triage with a decision model (d1)
The first question is a judgment, not a writing task: where does this go, how upset is the customer, how fast do we need to move?
That's what d1, Liquid AI's decision model, is for. You send it the ticket as the state and ask typed questions. It answers each one with a probability per option, in a single forward pass, without generating any text. SynapCores serves it at /v1/systemone:
curl -s http://localhost:8080/v1/systemone \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"model": "d1:latest",
"state": "Customer message: I was charged twice for order 4471 and I want my money back today.",
"questions": {
"route": { "type": "choice", "instructions": "Which team should handle this?",
"criteria": { "billing": "payments, charges and refunds",
"shipping": "delivery and tracking",
"technical": "bugs and login problems" } },
"angry": { "type": "noul", "instructions": "Is the customer angry?" },
"urgency": { "type": "score", "instructions": "How urgent is this?",
"criteria": ["can wait", "this week", "today", "right now"] }
}
}'
"route": { "choice": "billing", "probabilities": { "billing": 0.90, "shipping": 0.03, "technical": 0.07 } },
"angry": { "noul": 0.82 },
"urgency": { "score": 2.28 }
Billing, at 0.90. Probably angry. Urgency 2.28 on a 0–3 scale, between "today" and "right now". And output_tokens: 0: nothing was generated, so there's nothing to parse and no format to drift.
The probabilities are the point. Route automatically when confidence is high, and send the borderline tickets to a person. Add a new team tomorrow by adding one line to criteria. There's no model to retrain.
Step 2: Predict escalation with AutoML
d1 judged the message. But some of the strongest signals about this ticket aren't in the message at all. They're in the account: a 3-month-old customer, four tickets in 90 days, a large order, a 30-hour wait.
That's a job for a model trained on your own history. We have 600 past tickets with an escalated column, so we train a classifier in SQL:
CREATE EXPERIMENT escalation_model AS
SELECT tenure_months, prior_tickets_90d, order_value, hours_waiting,
escalated AS target
FROM ticket_history
WITH (
task_type = 'binary_classification', target_column = 'target',
algorithms = ['logistic_regression', 'xgboost'], max_trials = 6,
validation_strategy = 'holdout', validation_split = 0.2,
optimization_metric = 'roc_auc', random_seed = 42
);
-- best_score 0.873 (ROC AUC on the holdout)
And score the open queue:
SELECT id, customer, tenure_months, prior_tickets_90d, order_value, hours_waiting,
AUTOML.PREDICT('escalation_model',
tenure_months, prior_tickets_90d, order_value, hours_waiting) AS escalation_risk
FROM open_tickets
ORDER BY escalation_risk DESC;
| id | customer | escalation_risk |
|---|---|---|
| 4471 | Dana | 0.986 |
| 4472 | Sam | 0.019 |
Dana's ticket is near-certain to escalate if it's handled like a normal one. No pre-trained model could know that, because it comes from your customers' history. Training took under a second, and the prediction is a function call inside the query that reads the queue.