Blog · October 15, 2026

How to Classify and Route Support Tickets Without GPT

Moving support classification and routing off GPT: your real options, why teams switch, and a step-by-step migration with shadow-mode validation and rollback.

Saina · October 15, 2026 · 3 min read

This guide is for teams that want to classify and route support tickets without GPT. The usual reasons:

Below are the options that don't involve OpenAI, then a migration you can run one workflow at a time, with shadow mode before any cutover.

One note first: "off GPT" is not "GPT can't do this." OpenAI shipped its own decision model, GPT-6 Luna Decisions, on 2026-10-06, and it's a fair option (see the GPT-6 Luna Decisions alternatives guide). If you move off, do it for data control and vendor choice, not capability.

Ways to classify and route support tickets without GPT

The automated ticket routing guide compares the general approaches (rules, chat prompts, hosted and self-hosted decision models) in depth. Here's the shortlist when the constraint is "not OpenAI":

The migration, step by step

Step 1: Inventory. List every GPT call that returns a label, tag, yes/no or score. For each: prompt, model, volume/month, and what consumes the answer.

Step 2: Freeze the contract. Write down, per node: the option list (IDs + descriptions), the output shape, and the fallback behavior. This is what the replacement must honor. Then run the replacement against your labeled test set; the routing guide has the test-set and shadow-mode steps.

Step 3: Build the shadow. Run the replacement alongside GPT on live traffic, logging both answers, no routing action from the new one yet.

For Helm, the frozen contract becomes request data. An online store's support inbox, for example (illustrative):

from saina import Saina

client = Saina('https://helm.internal.example', api_key='YOUR_KEY')

# The option lists you froze in Step 2, reused as-is.
ROUTES = {'orders':   'Order status, changes, cancellations',
          'returns':  'Returns, exchanges, refunds for purchased items',
          'product':  'Questions about a product before or after buying',
          'other':    'Anything that does not fit the queues above'}
TAGS   = {'damaged':  'Item arrived broken or defective',
          'late':     'Delivery is late or tracking has stalled',
          'discount': 'Promo code or price-match question'}

result = client.ask(
    model='saina-helm-0.8b',
    state=ticket_text,
    mode='decision',
    questions={
        'queue': {'type': 'single_choice',
                  'question': 'Which queue should handle this message?',
                  'options': ROUTES},
        'tags':  {'type': 'multi_choice',
                  'question': 'Which of these apply?',
                  'options': TAGS,
                  'threshold': 0.5},   # per tag; an example to tune
    },
)

log_shadow(ticket_id,
           old=gpt_label,
           new=result['answers']['queue']['selection'],   # None if rejected
           new_reason=result['answers']['queue']['reason'],
           new_tags=result['answers']['tags']['selections'])

A chat model with structured outputs can return a route and a tag list in one call too. What the decision model adds here is an independent membership score per tag and a reason code (below_threshold, below_margin, tie) on every rejected answer, which is what your review branch keys on.

Step 4: Review the disagreements. A high overall agreement rate can hide the cases that matter. Review every disagreement sample; classify each as new-system-wrong, old-system-wrong, or genuinely ambiguous. You're building the case file for cutover.

Step 5: Cut over per node, not wholesale. Switch one workflow to the new decision step with a fast rollback (feature flag or duplicate branch). Watch the fallback/review rate for a week before migrating the next node.

Step 6: Keep GPT where GPT earns it. Reply drafting, summarization and open-ended extraction stay on a chat model. The migration is about decision nodes.

Checklist