Skip to content
Learn Agentic AI
Learn Agentic AI11 min read9 views

Ticket Classification with AI Agents: Auto-Routing Support Requests

Implement an AI-powered ticket classification system that automatically assigns priority, department, and SLA to incoming support requests using multi-label classification and intelligent routing rules.

The Cost of Misrouted Tickets

When a billing question lands in the engineering queue, two things happen: the engineer wastes time reading something they cannot act on, and the customer waits an extra cycle for re-routing. Studies show that misrouted tickets add an average of 4.2 hours to resolution time. At scale, this translates to millions in wasted labor and measurably lower customer satisfaction.

AI-powered ticket classification eliminates this bottleneck by analyzing the ticket content, assigning labels, priority, and department in under a second, and routing it to the right team before any human touches it.

Multi-Label Classification Model

Support tickets rarely fit a single category. A message like "My payment failed and now I can't access my account" spans both billing and technical access. The classifier must support multi-label output.

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
flowchart LR
    USER(["Customer"])
    CHANNEL{"Channel"}
    CHAT["Chat agent"]
    VOICE["Voice agent"]
    EMAIL["Email agent"]
    TRIAGE["Triage and<br/>intent detection"]
    KB[("Knowledge base<br/>RAG")]
    CRM[("CRM context")]
    AUTORES{"Auto resolvable?"}
    RESOLVE(["Resolved with<br/>cited answer"])
    HUMAN(["Tier 2 agent"])
    USER --> CHANNEL --> CHAT --> TRIAGE
    CHANNEL --> VOICE --> TRIAGE
    CHANNEL --> EMAIL --> TRIAGE
    TRIAGE --> KB
    TRIAGE --> CRM
    TRIAGE --> AUTORES
    AUTORES -->|Yes| RESOLVE
    AUTORES -->|No| HUMAN
    style TRIAGE fill:#4f46e5,stroke:#4338ca,color:#fff
    style AUTORES fill:#f59e0b,stroke:#d97706,color:#1f2937
    style RESOLVE fill:#059669,stroke:#047857,color:#fff
    style HUMAN fill:#0ea5e9,stroke:#0369a1,color:#fff
from dataclasses import dataclass
from openai import AsyncOpenAI
import json

DEPARTMENTS = [
    "billing", "technical", "shipping",
    "account", "product", "legal"
]
PRIORITIES = ["low", "medium", "high", "urgent"]

@dataclass
class TicketClassification:
    departments: list[str]
    primary_department: str
    priority: str
    sla_hours: int
    confidence: float
    reasoning: str

CLASSIFICATION_PROMPT = """Analyze this support ticket and return a JSON object:
{
  "departments": ["list of relevant departments"],
  "primary_department": "the single most relevant department",
  "priority": "low|medium|high|urgent",
  "confidence": 0.0-1.0,
  "reasoning": "brief explanation"
}

Departments: billing, technical, shipping, account, product, legal
Priority rules:
- urgent: service outage, security breach, legal threat
- high: payment failure, account locked, data loss
- medium: feature questions, general complaints
- low: feedback, feature requests, general inquiries

Ticket: {ticket_text}"""

async def classify_ticket(
    client: AsyncOpenAI, ticket_text: str
) -> TicketClassification:
    response = await client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "system",
                "content": "You are a support ticket classifier. Return valid JSON only.",
            },
            {
                "role": "user",
                "content": CLASSIFICATION_PROMPT.format(
                    ticket_text=ticket_text
                ),
            },
        ],
        response_format={"type": "json_object"},
        max_tokens=200,
    )
    data = json.loads(response.choices[0].message.content)
    sla = compute_sla(data["priority"])
    return TicketClassification(
        departments=data["departments"],
        primary_department=data["primary_department"],
        priority=data["priority"],
        sla_hours=sla,
        confidence=data["confidence"],
        reasoning=data["reasoning"],
    )

SLA Assignment Engine

SLA deadlines are computed from the priority level and department. Urgent billing tickets get a 1-hour SLA, while low-priority feedback might have a 72-hour window.

SLA_MATRIX = {
    ("urgent", "billing"): 1,
    ("urgent", "technical"): 2,
    ("urgent", "account"): 1,
    ("urgent", "shipping"): 4,
    ("urgent", "legal"): 2,
    ("high", "billing"): 4,
    ("high", "technical"): 4,
    ("high", "account"): 4,
    ("high", "shipping"): 8,
    ("medium", "billing"): 12,
    ("medium", "technical"): 12,
    ("medium", "shipping"): 24,
    ("low", "billing"): 48,
    ("low", "technical"): 48,
    ("low", "shipping"): 72,
}

def compute_sla(priority: str, department: str = "technical") -> int:
    return SLA_MATRIX.get(
        (priority, department),
        {"urgent": 2, "high": 8, "medium": 24, "low": 72}[priority],
    )

Routing Engine

The routing engine maps classifications to specific teams and agents. It considers agent availability, current workload, and skill matching.

from typing import Optional

@dataclass
class Agent:
    id: str
    name: str
    department: str
    skills: list[str]
    current_load: int
    max_load: int

@dataclass
class RoutingDecision:
    assigned_agent: Optional[Agent]
    queue: str
    sla_hours: int
    priority: str
    tags: list[str]

class TicketRouter:
    def __init__(self, agents: list[Agent]):
        self.agents = agents

    def find_best_agent(
        self, department: str, required_skills: list[str]
    ) -> Optional[Agent]:
        candidates = [
            a for a in self.agents
            if a.department == department
            and a.current_load < a.max_load
        ]
        if required_skills:
            skilled = [
                a for a in candidates
                if any(s in a.skills for s in required_skills)
            ]
            if skilled:
                candidates = skilled

        if not candidates:
            return None
        # Assign to agent with lowest current load
        return min(candidates, key=lambda a: a.current_load)

    def route(self, classification: TicketClassification) -> RoutingDecision:
        agent = self.find_best_agent(
            classification.primary_department,
            classification.departments,
        )
        queue = (
            f"{classification.primary_department}-"
            f"{classification.priority}"
        )
        return RoutingDecision(
            assigned_agent=agent,
            queue=queue,
            sla_hours=classification.sla_hours,
            priority=classification.priority,
            tags=classification.departments,
        )

Putting It All Together

The complete pipeline classifies, assigns SLA, and routes in a single async call.

async def process_new_ticket(
    client: AsyncOpenAI,
    router: TicketRouter,
    ticket_text: str,
    ticket_id: str,
) -> dict:
    classification = await classify_ticket(client, ticket_text)
    routing = router.route(classification)

    return {
        "ticket_id": ticket_id,
        "classification": classification,
        "routing": routing,
        "auto_routed": routing.assigned_agent is not None,
    }

FAQ

How accurate does ticket classification need to be before deploying?

Aim for 90%+ accuracy on your top five ticket categories before going live. Below that, misrouting causes more frustration than manual triage. Start by running the classifier in shadow mode — it classifies every ticket but a human still routes. Compare results for two weeks before switching to auto-routing.

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

How do I handle tickets that span multiple departments?

Assign the ticket to the primary department but tag all relevant departments. The primary department agent resolves their portion and can transfer to secondary departments. This avoids the ticket sitting in limbo between teams.

What happens when the classifier has low confidence?

Route low-confidence tickets (below 0.7) to a triage queue where a human reviews and classifies them. Log these cases as training data — they represent the boundary cases your classifier needs to improve on.


#TicketClassification #AutoRouting #SLAManagement #SupportAutomation #AIAgents #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Strategy

AI Agent M&A Activity 2026: Aircall–Vogent, Meta–PlayAI, OpenAI's Six Deals

Q1 2026 saw a record acquisition wave: Aircall bought Vogent (May), Meta acquired Manus and PlayAI, OpenAI closed six deals. The voice AI consolidation phase has begun.

Agentic AI

LangGraph State-Machine Architecture: A Principal-Engineer Deep Dive (2026)

How LangGraph's StateGraph, channels, and reducers actually work — with a working multi-step agent, eval hooks at every node, and the patterns that survive production.

Agentic AI

LangGraph Checkpointers in Production: Durable, Resumable Agents with Eval Replay

Use LangGraph's checkpointer to make agents resumable across crashes and human-in-the-loop pauses, then replay any checkpoint into your eval pipeline.

Agentic AI

Multi-Agent Handoffs with the OpenAI Agents SDK: The Pattern That Actually Scales (2026)

Handoffs done right — when one agent should hand control to another, how to preserve context, and how to evaluate the handoff decision itself.

Agentic AI

Building Your First Agent with the OpenAI Agents SDK in 2026: A Hands-On Walkthrough

Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.

Agentic AI

LangGraph Supervisor Pattern: Orchestrating Multi-Agent Teams in 2026

The supervisor pattern in LangGraph for coordinating specialist agents, with full code, an eval pipeline that scores routing accuracy, and the failure modes to watch for.