Using LLM for Intent Detection
I needed a simple way to route incoming support tickets without maintaining a pile of regex rules. An LLM-based intent detector works well for this because it handles varied phrasing and new terminology out of the box. I
I needed a simple way to route incoming support tickets without maintaining a pile of regex rules. An LLM-based intent detector works well for this because it handles varied phrasing and new terminology out of the box. In this tutorial I will walk through the 30-line classifier I shipped using Oxlo.ai.
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: Define the taxonomy and initialize the client
First I define the five intents my support team actually cares about. Then I point the OpenAI SDK at Oxlo.ai. I use llama-3.3-70b here because it sticks to structured output formats reliably.
from openai import OpenAI
INTENTS = [
"billing",
"technical_support",
"account_management",
"sales_inquiry",
"general_other"
]
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key="YOUR_OXLO_API_KEY"
)
Step 2: Lock down the system prompt
The system prompt is the entire contract. I list the exact intents, give one-line definitions, and mandate a JSON schema so I never have to parse free text.
SYSTEM_PROMPT = """You are an intent classification engine.
Analyze the user message and classify it into exactly one of these intents:
- billing: questions about invoices, payments, refunds, or charges
- technical_support: bugs, errors, integrations, or feature malfunctions
- account_management: password resets, plan changes, user access, or cancellations
- sales_inquiry: pricing questions, demo requests, or upgrade interest
- general_other: anything that does not fit the above
Respond ONLY with a JSON object in this format:
{"intent": "", "confidence": "high|medium|low", "reason": ""}
Do not include markdown formatting or explanation outside the JSON."""
Step 3: Build the classifier function
I wrap the API call in a small function that parses the output and validates the intent against my taxonomy. If the model hallucinates an intent or returns malformed JSON, I fall back to general_other.
import json
def detect_intent(user_message: str, model: str = "llama-3.3-70b"):
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
)
raw = response.choices[0].message.content
try:
parsed = json.loads(raw)
if parsed.get("intent") not in INTENTS:
return {
"intent": "general_other",
"confidence": "low",
"reason": "Intent returned by model was outside defined taxonomy"
}
return parsed
except json.JSONDecodeError:
return {
"intent": "general_other",
"confidence": "low",
"reason": "Malformed JSON returned by model"
}
Step 4: Add a confidence gate and batch test
In production I do not act on medium or low confidence predictions. I route them to a human review queue. I run a batch of representative messages through the detector to verify it behaves correctly.
test_messages = [
"My invoice last month was double what I expected and I need a refund.",
"The API returns a 500 error every time I send a list longer than 100 items.",
"Can I talk to someone about upgrading to the enterprise plan?",
"I forgot my password and the reset email never arrives.",
"What is the weather like today?"
]
for msg in test_messages:
result = detect_intent(msg)
route = result["intent"] if result["confidence"] == "high" else "human_review_queue"
print(f"Message: {msg[:50]}...")
print(f" Detected: {result['intent']} ({result['confidence']})")
print(f" Route: {route}")
print(f" Reason: {result['reason']}\n")
Run it
Running the script produces deterministic routing decisions. Here is the output I see:
Message: My invoice last month was double what I expected...
Detected: billing (high)
Route: billing
Reason: User explicitly mentions an invoice and a refund request.
Message: The API returns a 500 error every time I send...
Detected: technical_support (high)
Route: technical_support
Reason: User describes a reproducible server error in the API.
Message: Can I talk to someone about upgrading to...
Detected: sales_inquiry (high)
Route: sales_inquiry
Reason: User expresses interest in an enterprise plan upgrade.
Message: I forgot my password and the reset email...
Detected: account_management (high)
Route: account_management
Reason: User reports a password reset issue and missing email.
Message: What is the weather like today?...
Detected: general_other (high)
Route: general_other
Reason: Message is unrelated to product, billing, or support.
Next steps
To put this into production, wrap the detect_intent function in an async FastAPI endpoint so it can handle concurrent support tickets without blocking. If you start passing long conversation threads or full knowledge-base articles as context to disambiguate intent, switch to a model like kimi-k2.6 or deepseek-v3.2 on Oxlo.ai. Because Oxlo.ai charges per request rather than per token, those long-context classification jobs will not inflate your bill the way they would on token-based providers. See https://oxlo.ai/pricing for plan details.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.