I am going to walk you through a support ticket triage agent that classifies intent, extracts order numbers, and scores urgency in a single LLM call. This is the same approach I use to introduce teams to LLMs for natural language processing. I run it on Oxlo.ai because the flat per-request pricing keeps costs predictable even when I pass in long ticket threads.
What you'll need
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
Step 1: Scaffold the script and test connectivity
I always start by verifying the environment. Create a file named triage.py, import the OpenAI client, point it at Oxlo.ai, and send a simple message to confirm there are no network or key issues.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "user", "content": "Say hello and confirm you are ready."},
],
)
print(response.choices[0].message.content)
Step 2: Write the system prompt
The system prompt is the only place where I encode the NLP task. I want the model to act like a structured classifier and entity extractor, not a chatbot. This constant forces JSON output with four fields: intent, order_id, sentiment, and urgency.
SYSTEM_PROMPT = """You are a support ticket triage engine.
Analyze the user's message and return a single JSON object with these exact keys:
- intent: one of [billing, technical, refund, general]
- order_id: extract any order number (format #ORD-12345) or null
- sentiment: one of [angry, frustrated, neutral, satisfied]
- urgency: integer 1 to 5, where 5 means the customer cannot proceed
Rules:
- Return only the JSON object, with no markdown fences and no explanation.
- If no order number is present, use null.
- Base urgency on explicit phrases like 'urgent', 'down', or 'immediately'."""
Step 3: Build the triage function
Next I wrap the API call in a reusable function. I use qwen-3-32b because it follows structured instructions reliably. The function sends the system prompt plus the raw ticket text, then parses the JSON response.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a support ticket triage engine.
Analyze the user's message and return a single JSON object with these exact keys:
- intent: one of [billing, technical, refund, general]
- order_id: extract any order number (format #ORD-12345) or null
- sentiment: one of [angry, frustrated, neutral, satisfied]
- urgency: integer 1 to 5, where 5 means the customer cannot proceed
Rules:
- Return only the JSON object, with no markdown fences and no explanation.
- If no order number is present, use null.
- Base urgency on explicit phrases like 'urgent', 'down', or 'immediately'."""
def triage_ticket(text: str) -> dict:
response = client.chat.completions.create(
model="qwen-3-32b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": text},
],
)
raw = response.choices[0].message.content.strip()
return json.loads(raw)
if __name__ == "__main__":
sample = "My account was double charged for order #ORD-99881 and I need this fixed immediately."
result = triage_ticket(sample)
print(json.dumps(result, indent=2))
Step 4: Run a batch and route tickets
In production, tickets arrive in groups. I loop over a list, call triage_ticket for each, and apply a simple routing rule. Anything with urgency greater than or equal to 4 goes to the priority queue.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a support ticket triage engine.
Analyze the user's message and return a single JSON object with these exact keys:
- intent: one of [billing, technical, refund, general]
- order_id: extract any order number (format #ORD-12345) or null
- sentiment: one of [angry, frustrated, neutral, satisfied]
- urgency: integer 1 to 5, where 5 means the customer cannot proceed
Rules:
- Return only the JSON object, with no markdown fences and no explanation.
- If no order number is present, use null.
- Base urgency on explicit phrases like 'urgent', 'down', or 'immediately'."""
def triage_ticket(text: str) -> dict:
response = client.chat.completions.create(
model="qwen-3-32b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": text},
],
)
raw = response.choices[0].message.content.strip()
return json.loads(raw)
tickets = [
"My account was double charged for order #ORD-99881 and I need this fixed immediately.",
"How do I change my notification settings?",
"The API returns a 500 error on every request since this morning. This is urgent.",
"I want a refund for order #ORD-22100. The item arrived damaged.",
]
for t in tickets:
result = triage_ticket(t)
queue = "priority" if result["urgency"] >= 4 else "standard"
print(f"[{queue.upper()}] intent={result['intent']} urgency={result['urgency']} order_id={result['order_id']} sentiment={result['sentiment']}")
print(f" original: {t[:60]}...")
print()
Run it
Save the file and run it from your terminal. You should see each ticket routed to the correct queue with the extracted fields.
$ python triage.py
[PRIORITY] intent=billing urgency=5 order_id=#ORD-99881 sentiment=frustrated
original: My account was double charged for order #ORD-99881 and I nee...
[STANDARD] intent=general urgency=2 order_id=None sentiment=neutral
original: How do I change my notification settings?...
[PRIORITY] intent=technical urgency=5 order_id=None sentiment=angry
original: The API returns a 500 error on every request since this morni...
[STANDARD] intent=refund urgency=3 order_id=#ORD-22100 sentiment=frustrated
original: I want a refund for order #ORD-22100. The item arrived damage...
Next steps
To make this production-ready, wire the triage function into a small FastAPI endpoint so tickets are classified as they arrive via webhook. You could also switch to the DeepSeek V3.2 model on Oxlo.ai for faster throughput on the free tier, or experiment with the kimi-k2.6 model if you need vision support for screenshots attached to tickets.
Top comments (0)