We are going to build a support ticket triage agent that reads incoming customer messages, classifies the issue, looks up account details via a mock tool, and drafts a response. This saves time if you run a help desk and want to cut first-response latency before a human takes over. I use Oxlo.ai because its OpenAI-compatible API and flat per-request pricing keep the code simple and costs predictable even when ticket histories get long. See https://oxlo.ai/pricing for details.
What you'll need
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK. Install it with
pip install openai
Step 1: Verify the connection
Before adding logic, make sure you can reach Oxlo.ai and get a completion back. I start with Llama 3.3 70B because it is a reliable general-purpose model for this workflow.
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": "How do I reset my password?"},
],
)
print(response.choices[0].message.content)
Step 2: Define the agent's system prompt
A raw model answers generically. We need a system prompt that tells it to act as a triage agent and return structured data. Here is the prompt I use. You can edit the categories or tone to match your product.
SYSTEM_PROMPT = """You are a support ticket triage agent.
Your job is to analyze the customer's message and produce a structured assessment.
Output valid JSON with these keys:
- category: one of Billing, Technical, Account, General
- urgency: Low, Medium, or High
- draft_reply: a brief, empathetic response or escalation note
Be concise. Do not ask follow-up questions."""
Step 3: Enforce JSON output
Parsing free text is fragile. Oxlo.ai supports JSON mode, so we can force valid JSON by setting response_format. This removes the need to beg the model to output JSON inside the prompt.
import json
user_message = "I was charged twice for my subscription this month. Please fix this immediately."
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
response_format={"type": "json_object"},
)
result = json.loads(response.choices[0].message.content)
print(json.dumps(result, indent=2))
Step 4: Give the model a tool
For real triage, we need data. We will define a mock function called get_account_status and let the model call it. I switch to Qwen 3 32B here because it handles agent workflows and tool use well.
tools = [
{
"type": "function",
"function": {
"name": "get_account_status",
"description": "Retrieve billing and plan details for a user.",
"parameters": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "The customer user ID.",
},
},
"required": ["user_id"],
},
},
}
]
def get_account_status(user_id: str):
# Mock lookup. In production, query your database.
return {"user_id": user_id, "plan": "Pro", "payment_issue": True}
user_message = "User u-8821 says they were double charged. Can you check?"
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
]
response = client.chat.completions.create(
model="qwen-3-32b",
messages=messages,
tools=tools,
tool_choice="auto",
)
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_account_status":
tool_result = get_account_status(**arguments)
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_result),
})
final_response = client.chat.completions.create(
model="qwen-3-32b",
messages=messages,
tools=tools,
)
print(final_response.choices[0].message.content)
else:
print(message.content)
Step 5: Wrap it in a reusable class
Now we tie everything together into a clean Python class that accepts a ticket, runs the tool loop, and returns structured JSON. I use Kimi K2.6 for this final version because its reasoning and agentic coding capabilities make the tool loop reliable.
class SupportTriageAgent:
def __init__(self, api_key: str):
self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
self.model = "kimi-k2.6"
self.system_prompt = SYSTEM_PROMPT
self.tools = [
{
"type": "function",
"function": {
"name": "get_account_status",
"description": "Retrieve billing and plan details for a user.",
"parameters": {
"type": "object",
"properties": {
"user_id": {
"type": "string",
"description": "The customer user ID.",
},
},
"required": ["user_id"],
},
},
}
]
def get_account_status(self, user_id: str):
return {"user_id": user_id, "plan": "Pro", "payment_issue": True}
def triage(self, user_message: str):
messages = [
{"role": "system", "content": self.system_prompt},
{"role": "user", "content": user_message},
]
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=self.tools,
tool_choice="auto",
response_format={"type": "json_object"},
)
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_account_status":
tool_result = self.get_account_status(**arguments)
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(tool_result),
})
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
tools=self.tools,
response_format={"type": "json_object"},
)
message = response.choices[0].message
return json.loads(message.content)
Run it
Here is how I call the finished agent against two sample tickets.
if __name__ == "__main__":
agent = SupportTriageAgent(api_key="YOUR_OXLO_API_KEY")
tickets = [
"User u-9912 cannot log in after the latest update.",
"User u-8821 was charged twice. Please check their account.",
]
for ticket in tickets:
result = agent.triage(ticket)
print(f"Ticket: {ticket}")
print(json.dumps(result, indent=2))
print()
Example output:
Ticket: User u-9912 cannot log in after the latest update.
{
"category": "Technical",
"urgency": "High",
"draft_reply": "We are sorry you are locked out. Our engineering team is investigating the update. I will escalate this to our technical team immediately and follow up within 30 minutes."
}
Ticket: User u-8821 was charged twice. Please check their account.
{
"category": "Billing",
"urgency": "High",
"draft_reply": "I have reviewed your account and confirmed a duplicate charge on your Pro plan. I have issued a refund which will appear in 3 to 5 business days."
}
Next steps
Deploy this agent as a FastAPI endpoint and wire it into your existing help desk via webhook. For tickets that require code-level debugging, swap the model to DeepSeek V3.2 or DeepSeek R1 671B to let the model reason through stack traces before replying.
Top comments (0)