Ticket Routing and Escalation Logic
Classifying intent, routing to specialist agents, and escalation triggers.
Ticket Routing and Escalation Logic is a free AI Agents lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Agents learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Routing Problem
A customer service agent receives thousands of diverse messages daily: billing disputes, password resets, product defects, shipping delays, feature requests. Sending every message to the same handler produces slow, low-quality responses.
Ticket routing classifies each message and sends it to the team best equipped to resolve it.
Intent Classification with an LLM
The routing layer calls an LLM with a classification prompt. The model returns a structured response with an intent label and a confidence score.
import openai, json
client = openai.OpenAI(api_key='YOUR_OPENAI_KEY')
INTENTS = ['billing', 'technical_support', 'returns_refunds',
'account_access', 'shipping', 'general_inquiry']
def classify_intent(message: str) -> dict:
prompt = (
f'Classify this customer message into exactly one intent.\n'
f'Intents: {INTENTS}\n'
f'Message: "{message}"\n'
f'Respond with JSON: {{"intent": "...", "confidence": 0.0-1.0}}'
)
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
return json.loads(resp.choices[0].message.content)Routing to Specialist Queues
Once the intent is known, route the ticket to the appropriate specialist queue. Each queue has its own response templates, SLAs, and escalation rules.
ROUTING_MAP = {
'billing': 'queue_billing',
'technical_support': 'queue_tech',
'returns_refunds': 'queue_returns',
'account_access': 'queue_tech',
'shipping': 'queue_fulfillment',
'general_inquiry': 'queue_general'
}
def route_ticket(ticket: dict) -> str:
result = classify_intent(ticket['message'])
intent = result['intent']
confidence = result['confidence']
queue = ROUTING_MAP.get(intent, 'queue_general')
ticket['intent'] = intent
ticket['confidence'] = confidence
ticket['queue'] = queue
return queueConfidence Threshold for Escalation
If the classifier is uncertain (confidence below 0.7), automatic routing may be wrong. Low-confidence tickets should be escalated to a human for manual triage rather than auto-routed to a specialist.
CONFIDENCE_THRESHOLD = 0.70
def route_with_escalation(ticket: dict) -> dict:
result = classify_intent(ticket['message'])
intent = result['intent']
confidence = result['confidence']
if confidence < CONFIDENCE_THRESHOLD:
return {
'ticket_id': ticket['id'],
'action': 'escalate_to_human',
'reason': f'Low confidence: {confidence:.2f}',
'suggested_intent': intent
}
queue = ROUTING_MAP.get(intent, 'queue_general')
return {
'ticket_id': ticket['id'],
'action': 'route_to_queue',
'queue': queue,
'intent': intent,
'confidence': confidence
}SLA-Based Priority Escalation
Even auto-routed tickets can breach their SLA if not resolved in time. A background job checks ticket age against SLA targets and escalates overdue tickets to a supervisor queue.
from datetime import datetime, timezone
SLA_HOURS = {
'queue_billing': 4,
'queue_tech': 8,
'queue_returns': 24,
'queue_fulfillment': 12,
'queue_general': 48
}
def check_sla_breach(ticket: dict) -> bool:
created = datetime.fromisoformat(ticket['created_at'])
age_hours = (datetime.now(timezone.utc) - created).total_seconds() / 3600
sla = SLA_HOURS.get(ticket['queue'], 24)
if age_hours > sla and ticket['status'] == 'open':
ticket['queue'] = 'queue_supervisor_escalation'
ticket['escalation_reason'] = f'SLA breach: {age_hours:.1f}h > {sla}h'
return True
return False
if __name__ == '__main__':
from datetime import datetime, timedelta, timezone
old_ticket = {
'created_at': (datetime.now(timezone.utc) - timedelta(hours=10)).isoformat(),
'queue': 'queue_billing',
'status': 'open',
}
breached = check_sla_breach(old_ticket)
print(f'SLA breached: {breached}')
if breached:
print('Escalation reason:', old_ticket['escalation_reason'])
Multi-Label Routing for Complex Tickets
Some messages touch multiple domains: 'My order arrived broken and I was charged twice.' A multi-label classifier returns multiple intents; the ticket is duplicated into both queues, and the agents coordinate on resolution.
def classify_multi_intent(message: str) -> list[dict]:
prompt = (
f'A customer message may have multiple intents.\n'
f'Intents: {INTENTS}\n'
f'Message: "{message}"\n'
f'Return JSON array: [{{"intent": "...", "confidence": 0.0}}]\n'
f'Include only intents with confidence > 0.5'
)
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
data = json.loads(resp.choices[0].message.content)
return data.get('intents', [])
# Route to multiple queues
def route_multi(ticket: dict) -> list[str]:
intents = classify_multi_intent(ticket['message'])
return [ROUTING_MAP.get(i['intent'], 'queue_general') for i in intents]Extracting Ticket Metadata
Before routing, extract metadata from the message to help specialists: order numbers, product names, account IDs. This speeds up resolution by eliminating the first round of clarifying questions.
def extract_metadata(message: str) -> dict:
prompt = (
f'Extract structured metadata from this customer message.\n'
f'Return JSON: {{"order_id": null, "product": null, "account_email": null}}\n'
f'Use null for fields not mentioned.\n'
f'Message: "{message}"'
)
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
return json.loads(resp.choices[0].message.content)
meta = extract_metadata('My order #A12345 for the blue headphones never arrived.')
print(meta) # {'order_id': 'A12345', 'product': 'blue headphones', 'account_email': None}Sentiment-Based Priority Boost
Angry customers are more likely to churn. Detect negative sentiment and boost ticket priority so unhappy customers get faster responses, even if their SLA clock has not expired yet.
def detect_sentiment(message: str) -> str:
prompt = f'Classify sentiment as positive/neutral/negative.\nMessage: "{message}"\nReturn JSON: {{"sentiment": "..."}}'
resp = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'}
)
return json.loads(resp.choices[0].message.content)['sentiment']
def set_priority(ticket: dict) -> str:
sentiment = detect_sentiment(ticket['message'])
if sentiment == 'negative':
ticket['priority'] = 'high'
elif sentiment == 'positive':
ticket['priority'] = 'low'
else:
ticket['priority'] = 'normal'
return ticket['priority']Escalation Chain Definition
Define a clear escalation chain so every queue has a fallback. When the front-line queue fails to resolve within SLA, the ticket moves to tier 2, then tier 3 (senior specialist), then supervisor.
ESCALATION_CHAIN = {
'queue_tech': 'queue_tech_tier2',
'queue_tech_tier2': 'queue_tech_senior',
'queue_tech_senior': 'queue_supervisor_escalation',
'queue_billing': 'queue_billing_senior',
'queue_billing_senior': 'queue_supervisor_escalation',
'queue_returns': 'queue_supervisor_escalation',
'queue_fulfillment': 'queue_supervisor_escalation',
'queue_general': 'queue_supervisor_escalation',
'queue_supervisor_escalation': None # terminal — human manager
}
def escalate(ticket: dict) -> str | None:
next_queue = ESCALATION_CHAIN.get(ticket['queue'])
if next_queue:
ticket['queue'] = next_queue
return next_queue
if __name__ == '__main__':
ticket = {'queue': 'queue_tech'}
for _ in range(3):
nxt = escalate(ticket)
print(f"Escalated to: {ticket['queue']}")
if nxt is None:
break
Full Routing Pipeline
Combine classification, metadata extraction, sentiment analysis, and SLA checking into a single pipeline that runs on each incoming ticket.
def process_ticket(raw_ticket: dict) -> dict:
ticket = dict(raw_ticket)
# Step 1: Classify and route
routing = route_with_escalation(ticket)
ticket.update(routing)
# Step 2: Extract metadata
ticket['metadata'] = extract_metadata(ticket['message'])
# Step 3: Set priority from sentiment
ticket['priority'] = set_priority(ticket)
# Step 4: Check if already breaching SLA
if ticket.get('created_at'):
check_sla_breach(ticket)
return ticket
result = process_ticket({
'id': 'T001',
'message': 'I was charged twice for my subscription last month!',
'created_at': '2026-05-28T10:00:00+00:00',
'status': 'open'
})
print(result)Monitoring Routing Accuracy
Track routing accuracy by sampling a percentage of auto-routed tickets for human review. When a specialist reassigns a ticket to a different queue, that is a routing error. Feed errors back to improve the prompt or fine-tune a classifier.
import random
def log_routing_decision(ticket: dict, final_queue: str):
was_correct = ticket.get('queue') == final_queue
if not was_correct:
print(f'[ROUTING_ERROR] ticket={ticket["id"]} '
f'predicted={ticket["queue"]} actual={final_queue} '
f'confidence={ticket.get("confidence", 0):.2f}')
# Specialist reassigns ticket: log the discrepancy
def specialist_reassign(ticket: dict, new_queue: str):
log_routing_decision(ticket, new_queue)
ticket['queue'] = new_queue
return ticket
if __name__ == '__main__':
ticket = {'id': 'T-1001', 'queue': 'queue_billing', 'confidence': 0.62}
specialist_reassign(ticket, 'queue_tech')
What confidence threshold should trigger human escalation instead of auto-routing?
Choosing the right confidence threshold balances automation rate against routing accuracy. A threshold that is too high over-escalates; too low leads to mis-routes.
Ticket Routing Recap
Effective ticket routing combines LLM intent classification with a confidence threshold for human escalation, metadata extraction for faster resolution, sentiment-based priority for at-risk customers, and an SLA escalation chain to ensure no ticket falls through the cracks.
Frequently asked questions
Is the “Ticket Routing and Escalation Logic” lesson free?
Yes — the full text of “Ticket Routing and Escalation Logic” is free to read here on the web, and the AI Agents course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Agents course, upgrade to CoddyKit PRO.
What will I learn in “Ticket Routing and Escalation Logic”?
Classifying intent, routing to specialist agents, and escalation triggers. You practise AI Agents with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start AI Agents?
No prior experience is required. AI Agents on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Ticket Routing and Escalation Logic” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this AI Agents lesson?
Yes. Every AI Agents lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Ticket Routing and Escalation Logic
- CRM Integration: Salesforce and HubSpot
- Human Handoff Protocols
- Customer Context and History Management