AI Support Ticket Routing: Cutting Resolution Time by 40% with Intelligent Classification
How to build ML models that automatically route, prioritize, and assign support tickets
AI Support Ticket Routing: Cutting Resolution Time by 40% with Intelligent Classification
How to build ML models that automatically route, prioritize, and assign support tickets
Learn how to implement AI-powered support ticket classification and routing systems that automatically assign tickets to the right team, set priority levels, and surface related knowledge base articles — dramatically reducing first response time.
AI Support Ticket Routing: From Chaos to Intelligent Automation
A typical mid-size SaaS company receives 5,000-20,000 support tickets monthly. Without intelligent routing, tickets sit in a generic queue until someone manually triages them — introducing delays and sending tickets to the wrong team.
The Ticket Triage Problem
Manual triage is slow and inconsistent:
AI triage can process tickets in seconds with higher consistency than manual processes.
Building an AI Ticket Classification System
python
from openai import OpenAI
import pandas as pd
from sklearn.preprocessing import MultiLabelBinarizer
from sklearn.metrics import classification_report
import jsonclient = OpenAI()
class TicketClassificationSystem:
"""
Multi-label ticket classification for routing, priority, and team assignment.
"""
def __init__(self):
self.team_taxonomy = {
'billing': 'Finance & Billing team',
'technical_bug': 'Engineering/Bug team',
'feature_request': 'Product team',
'api_integration': 'Developer Relations team',
'account_management': 'Customer Success team',
'security': 'Security team (IMMEDIATE escalation)',
'data_privacy': 'Privacy/Legal team',
'onboarding': 'Onboarding team',
'enterprise_support': 'Enterprise support (SLA-driven)'
}
self.priority_criteria = {
'P0_critical': 'Production outage, data loss, security incident',
'P1_high': 'Major feature broken, enterprise customer blocked',
'P2_medium': 'Feature degraded, workaround available',
'P3_low': 'Minor issue, cosmetic, feature request'
}
def classify_ticket(self, ticket: dict) -> dict:
"""
Classify a single support ticket using GPT-4.
Returns routing decision, priority, and suggested resources.
"""
prompt = f"""Classify this customer support ticket for routing and prioritization.
Teams available: {json.dumps(self.team_taxonomy, indent=2)}
Priority levels: {json.dumps(self.priority_criteria, indent=2)}
Ticket:
Subject: {ticket.get('subject', 'No subject')}
Body: {ticket.get('body', 'No body')[:2000]}
Customer tier: {ticket.get('customer_tier', 'Standard')}
Previous tickets this month: {ticket.get('ticket_count_30d', 0)}
Account MRR: {ticket.get('account_mrr', 0)}
Return JSON:
{{
"primary_team": "team_name",
"secondary_team": null,
"priority": "P0_critical/P1_high/P2_medium/P3_low",
"issue_category": "specific category within team",
"sentiment": "frustrated/neutral/positive",
"churn_risk": "high/medium/low",
"suggested_kb_articles": ["article title 1", "article title 2"],
"auto_resolution_possible": true/false,
"auto_response_draft": "if auto_resolution_possible, draft a response",
"context_for_agent": "key context an agent needs to resolve this quickly",
"escalation_needed": true/false,
"escalation_reason": null
}}"""
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
temperature=0.1
)
result = json.loads(response.choices[0].message.content)
result['ticket_id'] = ticket.get('id')
result['classified_at'] = pd.Timestamp.now().isoformat()
return result
def batch_classify(self, tickets: list[dict], max_concurrent: int = 5) -> list[dict]:
"""Process multiple tickets with rate limiting."""
results = []
for i, ticket in enumerate(tickets):
print(f"Classifying ticket {i+1}/{len(tickets)}: {ticket.get('id')}")
classification = self.classify_ticket(ticket)
results.append(classification)
return results
def generate_queue_report(self, classified_tickets: list[dict]) -> str:
"""Generate a summary of the current support queue."""
df = pd.DataFrame(classified_tickets)
report = f"""
Support Queue Report — {pd.Timestamp.now().strftime('%Y-%m-%d %H:%M')}
Total Tickets: {len(df)}
By Priority:
{df['priority'].value_counts().to_string()}By Team:
{df['primary_team'].value_counts().to_string()}Attention Required:
P0/P1 tickets: {len(df[df['priority'].isin(['P0_critical', 'P1_high'])])}
High churn risk: {len(df[df['churn_risk'] == 'high'])}
Escalation needed: {len(df[df['escalation_needed'] == True])}
Auto-resolvable: {len(df[df['auto_resolution_possible'] == True])} Sentiment Distribution:
{df['sentiment'].value_counts().to_string()}
"""
return reportSLA tracking
SLA_TARGETS = {
'P0_critical': {'first_response_minutes': 15, 'resolution_hours': 4},
'P1_high': {'first_response_minutes': 60, 'resolution_hours': 24},
'P2_medium': {'first_response_minutes': 240, 'resolution_hours': 72},
'P3_low': {'first_response_minutes': 1440, 'resolution_hours': 168}
}def check_sla_breach_risk(ticket: dict, classification: dict) -> dict:
"""Check if ticket is at risk of SLA breach."""
priority = classification.get('priority', 'P3_low')
sla = SLA_TARGETS.get(priority, SLA_TARGETS['P3_low'])
created_at = pd.Timestamp(ticket.get('created_at', pd.Timestamp.now()))
age_minutes = (pd.Timestamp.now() - created_at).total_seconds() / 60
first_response_target = sla['first_response_minutes']
is_at_risk = age_minutes > first_response_target * 0.75 # 75% of SLA used
is_breached = age_minutes > first_response_target
return {
'sla_target_minutes': first_response_target,
'current_age_minutes': round(age_minutes, 1),
'sla_at_risk': is_at_risk,
'sla_breached': is_breached,
'minutes_until_breach': max(0, round(first_response_target - age_minutes, 1))
}
Fine-Tuning for Your Domain
Generic models work well, but fine-tuned models on your historical tickets work better:
python
def prepare_fine_tuning_data(historical_tickets: pd.DataFrame) -> list[dict]:
"""
Prepare historical tickets for fine-tuning classification model.
Requires: tickets with human-assigned categories/priorities.
"""
training_examples = []
for _, ticket in historical_tickets.iterrows():
if pd.isna(ticket.get('resolved_team')) or pd.isna(ticket.get('final_priority')):
continue # Skip unresolved or uncategorized tickets
example = {
"messages": [
{
"role": "system",
"content": "You are a support ticket classifier. Classify tickets accurately."
},
{
"role": "user",
"content": f"Subject: {ticket['subject']}\nBody: {ticket['body'][:500]}"
},
{
"role": "assistant",
"content": json.dumps({
"primary_team": ticket['resolved_team'],
"priority": ticket['final_priority'],
"issue_category": ticket.get('category', 'general'),
"sentiment": ticket.get('sentiment', 'neutral')
})
}
]
}
training_examples.append(example)
print(f"Prepared {len(training_examples)} training examples")
return training_examplesUpload and start fine-tuning job
def create_fine_tuning_job(training_data: list[dict]) -> str:
import json
import tempfile
# Write to JSONL file
with tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl', delete=False) as f:
for example in training_data:
f.write(json.dumps(example) + '\n')
temp_path = f.name
# Upload file
with open(temp_path, 'rb') as f:
uploaded = client.files.create(file=f, purpose='fine-tune')
# Create fine-tuning job
job = client.fine_tuning.jobs.create(
training_file=uploaded.id,
model="gpt-4o-mini", # Fine-tune smaller model for cost efficiency
hyperparameters={"n_epochs": 3}
)
print(f"Fine-tuning job created: {job.id}")
return job.id
Integration with Popular Helpdesks
Most helpdesks support webhooks for AI integration:
The ROI is clear: reducing triage time from 10 minutes to seconds, eliminating misrouting, and ensuring SLA compliance is worth the engineering investment.
相关教程
How to replace frustrating phone trees with natural language voice AI that customers actually like
From recommendation algorithms to dynamic content: a technical guide to personalization at scale
How to build systems that analyze thousands of customer conversations for actionable insights