← Inbox/Results

How the cascade was measured

Every ticket goes to a small fine-tuned model first. Only the ones it isn't sure about are sent to an LLM. The question is how much LLM work that saves, and what it costs in accuracy. All numbers below come from 384 held-out tickets that were never used for training or for choosing the threshold.

Handled without the LLM
57%
threshold 0.65
Category accuracy
80.7%
LLM on everything: 81.0%
Priority accuracy
75.0%
LLM on everything: 75.5%
Model latency, p50
8.4 ms
p95 16.5 ms · 1 CPU thread

Accuracy vs. LLM share

Each point is one confidence threshold. Moving right sends more tickets to gemini-3.5-flash-lite. The threshold was picked on the validation split as the lowest escalation rate that keeps 92% agreement with the teacher, then frozen.

Category
65%70%75%80%85%0%25%50%75%100%Tickets sent to the LLMLLM on every ticket · 81.0%Deployed · 43% escalated · 80.7%
Priority
65%70%75%80%0%25%50%75%100%Tickets sent to the LLMLLM on every ticket · 75.5%Deployed · 43% escalated · 75.0%
Show as a table
ThresholdEscalatedCategory acc.Category F1Priority acc.Priority F1
0.000.0%71.1%0.70968.8%0.656
0.050.0%71.1%0.70968.8%0.656
0.100.0%71.1%0.70968.8%0.656
0.150.0%71.1%0.70968.8%0.656
0.200.0%71.1%0.70968.8%0.656
0.250.0%71.1%0.70968.8%0.656
0.300.8%71.9%0.71668.5%0.654
0.351.8%71.9%0.71668.2%0.652
0.404.4%72.7%0.72368.2%0.652
0.457.6%74.0%0.73668.8%0.659
0.5017.2%77.3%0.76770.8%0.682
0.5524.5%79.2%0.78573.2%0.703
0.6034.1%79.2%0.78474.0%0.709
0.6542.7%80.7%0.80275.0%0.718
0.7054.4%80.5%0.79775.5%0.727
0.7565.4%81.5%0.80875.3%0.726
0.8074.2%81.3%0.80276.0%0.733
0.8584.1%81.5%0.80576.6%0.736
0.9089.3%81.3%0.80276.3%0.734
0.9599.7%81.0%0.80075.5%0.726
1.00100.0%81.0%0.80075.5%0.726

Models on the gold set

The student learns from the teacher's labels, so the teacher's own score is roughly its ceiling. Gold labels come from a separate, stronger LLM, independent of both Gemini models (not from a person).

ModelCategory acc.Category F1Priority acc.Priority F1Trainable params
TF-IDF + logistic regression63.0%0.62764.1%0.608–
MiniLM-L6, full fine-tune71.1%0.70968.8%0.65622,717,451
MiniLM-L6, LoRA r=871.9%0.71967.2%0.640151,691
gemini-3.5-flash-lite (teacher)81.0%0.80075.5%0.726–

Calibration

The routing rule trusts the model's confidence, so confidence has to mean something. One temperature per head, fitted on validation; ECE is measured on gold (lower is better).

HeadTemperatureECE beforeECE after
category1.190.1080.066
priority1.290.1260.077

Serving

Exported to ONNX and quantized to int8 with dynamic quantization. Agreement is the share of gold tickets where the served model picks the same label as the PyTorch model.

RuntimeSizeSame label as PyTorchp50p95
torch fp32–reference31.4 ms45.0 ms
onnx fp3290.9 MB100.0%16.6 ms32.3 ms
onnx int823.3 MB96.9%8.4 ms16.5 ms

Reply drafting: does reranking help?

For 300 held-out tickets with a known brand reply, the top retrieved past reply is compared to the real one (cosine similarity, bge-small). A proxy, not a human rating, but identical for every method. The cross-encoder was 33× slower and no better, so it's off; a small boost for replies from the same brand did more.

MethodSimilarity to real replySame brand @1Time / query
Random past reply0.6481.3%0.2 ms
Embedding search0.71466.7%21.5 ms
Embedding + same-brand boost (deployed)0.72392.7%21.2 ms
Cross-encoder rerank0.71573.3%0.69 s
Rerank + same-brand boost0.71576.3%0.70 s

Load test

Open-loop load (requests fired on a fixed schedule) against the classify endpoint with LLM escalation off, on a 2-core laptop CPU, load generator on the same machine. Capacity tops out near 90 requests a second; past that, requests queue and latency climbs into seconds, which is what the BullMQ queue in front of the API absorbs. Dynamic micro-batching was tried and made it worse here (batches averaged 1.3 tickets and only one ran at a time), so it ships switched off.

ServerTargetAchieved req/sFailedp50p95p99
One request per inference (deployed) · steady20/s20011.3 ms19.6 ms26.5 ms
One request per inference (deployed) · busy50/s50016.0 ms33.5 ms43.9 ms
One request per inference (deployed) · outage spike100/s100019.9 ms41.6 ms57.1 ms
One request per inference (deployed) · overload200/s8408.80 s26.36 s29.91 s
Micro-batching · steady20/s20025.6 ms36.7 ms42.3 ms
Micro-batching · busy50/s50015.8 ms25.5 ms29.6 ms
Micro-batching · outage spike100/s100021.2 ms50.5 ms66.1 ms
Micro-batching · overload200/s62020.50 s40.85 s44.07 s

Data

  • 125,463 customer opening tweets that got a brand reply, from 101 support accounts in the public Customer Support on Twitter dataset (2017). Capped at 1,500 per brand.
  • Phone numbers, emails, links and long account numbers masked before anything else touched the text.
  • 20,000 tickets labelled by gemini-3.5-flash-lite; 384 held out and labelled independently for evaluation (non-English tickets skipped).
  • The teacher agrees with the gold labels about 4 times in 5, so every number here inherits that noise.