We are going to build a support ticket triage agent that sits in front of an existing CRM API. It ingests raw customer emails, classifies the issue type, pulls relevant articles from an internal knowledge base, and drafts a first response. This pattern works for any legacy stack that speaks HTTP and stores unstructured text.
What you'll need
- Python 3.10 or newer
pip install openai requests- An Oxlo.ai API key from https://portal.oxlo.ai. Review request-based pricing at https://oxlo.ai/pricing.
I assume you already have a CRM or help desk with a REST endpoint. If not, the mock server in Step 1 is fully runnable.
Step 1: Mock the legacy system
Before we touch the LLM, we need an existing application to integrate with. I created a tiny fake CRM module that exposes customer history and help articles. Save this as legacy_crm.py.
# legacy_crm.py
"""Mock existing application. In production, replace these with real HTTP calls."""
CUSTOMERS = {
"alex@example.com": {"tier": "premium", "account_age_months": 14},
"sam@example.com": {"tier": "free", "account_age_months": 2},
}
KB_ARTICLES = [
{"id": "kb-001", "title": "Reset your password", "category": "account", "excerpt": "Go to settings..."},
{"id": "kb-002", "title": "Refund policy details", "category": "billing", "excerpt": "Refunds within 30 days..."},
{"id": "kb-003", "title": "API rate limits explained", "category": "technical", "excerpt": "Free tier: 100 req/min..."},
]
def get_customer_profile(email):
return CUSTOMERS.get(email, {"tier": "unknown", "account_age_months": 0})
def search_knowledge_base(category):
return [a for a in KB_ARTICLES if a["category"] == category]
def create_ticket(email, subject, body, priority, suggested_response=None):
ticket_id = f"TICK-{abs(hash(email + subject)) % 10000:04d}"
return {
"ticket_id": ticket_id,
"email": email,
"subject": subject,
"priority": priority,
"suggested_response": suggested_response,
"status": "open",
}
Step 2: Create the LLM client
Oxlo.ai exposes an OpenAI-compatible endpoint, so the official SDK drops in with just a base URL change. Because Oxlo.ai charges per request instead of per token, injecting long customer histories or bulky knowledge base snippets will not inflate costs. Save this as llm_client.py.
# llm_client.py
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=os.environ.get("OXLO_API_KEY", "YOUR_OXLO_API_KEY"))
def chat(model, system_prompt, user_message, temperature=0.2):
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message},
],
temperature=temperature,
)
return response.choices[0].message.content
Step 3: Write the system prompt
I keep the prompt in a dedicated file so it can be edited without touching business logic. This is the only place where behavior is defined. Save this as prompts.py.
# prompts.py
SYSTEM_PROMPT = """You are a support triage agent integrated with an existing CRM.
Your job has two phases:
1. Classify the incoming message into exactly one category: billing, technical, or account.
2. Draft a concise, helpful first reply. If you lack information, say you are escalating to a human.
Rules:
- Use the customer's tier and account age to personalize tone. Premium users get a warmer, more apologetic tone.
- Reference specific knowledge base article titles when relevant.
- Output strictly valid JSON with keys: category, reasoning, reply, escalate (boolean)."""
Step 4: Build the triage agent
This module fetches the customer profile, calls Oxlo.ai to classify and draft a reply, searches the knowledge base, and persists the result to the legacy CRM. Save this as agent.py.
# agent.py
import json
from legacy_crm import get_customer_profile, search_knowledge_base, create_ticket
from llm_client import chat
from prompts import SYSTEM_PROMPT
def process_ticket(email, subject, body):
profile = get_customer_profile(email)
user_message = f"""Customer: {email}
Tier: {profile['tier']}
Account age: {profile['account_age_months']} months
Subject: {subject}
Body: {body}
Classify and draft a reply. Return JSON only."""
raw = chat(
model="llama-3.3-70b",
system_prompt=SYSTEM_PROMPT,
user_message=user_message,
temperature=0.2,
)
# Strip markdown fences if the model adds them
cleaned = raw.strip()
if cleaned.startswith("
```json"):
cleaned = cleaned.split("```
json", 1)[1]
if cleaned.startswith("
```"):
cleaned = cleaned.split("```
", 1)[1]
if cleaned.endswith("
```"):
cleaned = cleaned.rsplit("```
", 1)[0]
cleaned = cleaned.strip()
parsed = json.loads(cleaned)
category = parsed.get("category", "technical")
articles = search_knowledge_base(category)
ticket = create_ticket(
email=email,
subject=subject,
body=body,
priority="high" if parsed.get("escalate") else "normal",
suggested_response=parsed.get("reply"),
)
return {
"ticket": ticket,
"category": category,
"articles": articles,
"reply": parsed.get("reply"),
"escalate": parsed.get("escalate"),
}
Run it
Create a small runner script, set your API key, and execute the pipeline.
# run.py
import os
import json
from agent import process_ticket
os.environ.setdefault("OXLO_API_KEY", "YOUR_OXLO_API_KEY")
if __name__ == "__main__":
result = process_ticket(
email="alex@example.com",
subject="Unexpected charge on my card",
body="I was charged $49 twice this month. Can you fix this?",
)
print(json.dumps(result, indent=2))
Example output:
{
"ticket": {
"ticket_id": "TICK-4829",
"email": "alex@example.com",
"subject": "Unexpected charge on my card",
"priority": "normal",
"suggested_response": "Hi Alex, I sincerely apologize for the duplicate charge on your account. As a premium customer, your experience matters to us, and I have immediately flagged this with our billing team to reverse the extra $49 charge. You should see the refund within 3 business days. For full details, please see our Refund policy details article.",
"status": "open"
},
"category": "billing",
"articles": [
{
"id": "kb-002",
"title": "Refund policy details",
"category": "billing",
"excerpt": "Refunds within 30 days..."
}
],
"reply": "Hi Alex, I sincerely apologize...",
"escalate": false
}
Wrap-up
You now have a working integration pattern that bridges an existing CRM with an LLM through Oxlo.ai. The next concrete step is to replace the mock CRM functions with real HTTP calls to your internal API. After that, add a confidence threshold so any classification below a certain score automatically escalates to a human.
If your tickets start carrying long conversation threads, remember that Oxlo.ai's per-request pricing means longer context does not increase cost. You can swap in kimi-k2.6 or deepseek-v3.2 for heavier reasoning without worrying about token counts.
Top comments (0)