DEV Community

shashank ms
shashank ms

Posted on

Practical Guide to Using LLM for Text Classification and Clustering in Business Applications

Most businesses collect thousands of unstructured support tickets, NPS verbatims, and survey responses that never get read. In this guide, I will build a lightweight Python pipeline that classifies individual tickets by category and urgency, then clusters them into thematic groups so product and support teams can spot patterns without manual tagging. The pipeline hits Oxlo.ai's OpenAI-compatible API, and because Oxlo.ai charges one flat cost per request regardless of prompt length, feeding long customer transcripts into a clustering prompt does not scale costs with input length (see https://oxlo.ai/pricing).

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
  • No vector database or cloud storage. We keep everything in memory with the standard library.

Step 1: Set up the client and sample data

I start by initializing the Oxlo.ai client and creating a small batch of realistic support tickets. In production this list would come from a CSV export or your help desk API.

from openai import OpenAI
import json

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")

feedback_batch = [
    {"id": 101, "text": "The checkout button is completely unresponsive on mobile Safari. I cannot complete my purchase."},
    {"id": 102, "text": "I was charged twice for my subscription this month. Please refund the duplicate charge immediately."},
    {"id": 103, "text": "Would love a dark mode option for the dashboard. It is hard to use at night."},
    {"id": 104, "text": "The new reporting dashboard loads very slowly on Chrome and often times out before the data appears."},
    {"id": 105, "text": "I cannot figure out how to filter reports by date range. The UI feels unintuitive."},
    {"id": 106, "text": "Need an export to CSV button on the analytics tab so I can share with my team."},
    {"id": 107, "text": "The mobile app crashes every time I tap the notifications tab. This started after the last update."},
    {"id": 108, "text": "I thought I canceled my plan but I was billed again. The cancellation flow is not clear."},
]

Step 2: Classify tickets by category and urgency

Next I define a classifier that labels every ticket. I use Llama 3.3 70B on Oxlo.ai because it is reliable for structured JSON following a tight system prompt.

CLASSIFY_SYSTEM_PROMPT = """You are a support operations analyst. Given a customer feedback message, output a JSON object with exactly two keys:
- "category": one of ["Bug", "Feature Request", "Billing", "Usability", "Other"]
- "urgency": one of ["Low", "Medium", "High", "Critical"]
Respond with only the JSON object. Do not wrap it in markdown."""
def classify_ticket(text: str) -> dict:
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": CLASSIFY_SYSTEM_PROMPT},
            {"role": "user", "content": text},
        ],
    )
    raw = response.choices[0].message.content.strip()
    raw = raw.replace("

```json", "").replace("```

", "")
    return json.loads(raw)

for item in feedback_batch:
    result = classify_ticket(item["text"])
    item.update(result)
    print(f"Ticket {item['id']}: {result}")

print("\nClassification complete.")

Step 3: Cluster tickets into thematic groups

Now I group the batch by meaning. Instead of running embeddings and k-means, I send the entire batch to DeepSeek V3.2 on Oxlo.ai and let the model reason over the full text. This keeps the stack simple and avoids token-based surcharges on large inputs.

CLUSTER_SYSTEM_PROMPT = """You are a data analyst. You will receive a JSON list of feedback tickets, each with an id and text.
Group the tickets into thematic clusters based on semantic similarity.
Output a JSON object with:
- "clusters": a list of objects, each with "cluster_name" (a short descriptive label) and "ticket_ids" (a list of integers)
- "unclustered": a list of ids that fit no clear group
Every ticket must appear in exactly one cluster or in unclustered. Respond with JSON only."""
def cluster_tickets(tickets: list) -> dict:
    payload = json.dumps([{"id": t["id"], "text": t["text"]} for t in tickets])
    response = client.chat.completions.create(
        model="deepseek-v3.2",
        messages=[
            {"role": "system", "content": CLUSTER_SYSTEM_PROMPT},
            {"role": "user", "content": payload},
        ],
    )
    raw = response.choices[0].message.content.strip()
    raw = raw.replace("

```json", "").replace("```

", "")
    return json.loads(raw)

cluster_result = cluster_tickets(feedback_batch)
print(json.dumps(cluster_result, indent=2))

Step 4: Summarize findings for stakeholders

Finally, I turn the structured data into a short executive summary. I use Kimi K2.6 on Oxlo.ai because it handles advanced reasoning and produces concise business language.

SUMMARIZE_SYSTEM_PROMPT = """You are a product operations lead. Given JSON data containing classified customer feedback and thematic clusters, write a concise 3-bullet executive summary for leadership.
Each bullet should state the theme, the dominant category, and one concrete action item.
Use plain language and keep each bullet to two sentences maximum."""
def summarize(report_data: dict) -> str:
    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": SUMMARIZE_SYSTEM_PROMPT},
            {"role": "user", "content": json.dumps(report_data, indent=2)},
        ],
    )
    return response.choices[0].message.content

Run it

The main block below runs the full pipeline end to end. It classifies every ticket, clusters the batch, and prints the executive summary.

if __name__ == "__main__":
    print("=== Step 1: Data loaded ===")
    print(f"Tickets: {len(feedback_batch)}")

    print("\n=== Step 2: Classifying ===")
    for item in feedback_batch:
        result = classify_ticket(item["text"])
        item.update(result)
        print(f"  {item['id']}: {item['category']} / {item['urgency']}")

    print("\n=== Step 3: Clustering ===")
    cluster_result = cluster_tickets(feedback_batch)
    for c in cluster_result.get("clusters", []):
        print(f"  Cluster '{c['cluster_name']}': tickets {c['ticket_ids']}")
    if cluster_result.get("unclustered"):
        print(f"  Unclustered: {cluster_result['unclustered']}")

    print("\n=== Step 4: Executive Summary ===")
    report_payload = {
        "total_tickets": len(feedback_batch),
        "category_counts": {},
        "clusters": cluster_result.get("clusters", []),
        "unclustered": cluster_result.get("unclustered", []),
    }
    for t in feedback_batch:
        cat = t["category"]
        report_payload["category_counts"][cat] = report_payload["category_counts"].get(cat, 0) + 1

    print(summarize(report_payload))

Example output:

=== Step 1: Data loaded ===
Tickets: 8

=== Step 2: Classifying ===
  101: Bug / Critical
  102: Billing / High
  103: Feature Request / Low
  104: Bug / High
  105: Usability / Medium
  106: Feature Request / Medium
  107: Bug / High
  108: Billing / High

=== Step 3: Clustering ===
  Cluster 'Mobile Checkout and App Stability': tickets [101, 107]
  Cluster 'Billing and Cancellation Confusion': tickets [102, 108]
  Cluster 'Dashboard Reporting Issues': tickets [104, 105]
  Cluster 'Feature Gaps': tickets [103, 106]

=== Step 4: Executive Summary ===
- Mobile stability is the top Critical theme, with checkout and crash reports dominating. Engineering should prioritize a hotfix for the Safari checkout button and the notifications crash.
- Billing confusion accounts for two High urgency tickets around duplicate charges and unclear cancellation flows. Finance and UX should audit the cancellation page and refund process.
- Reporting dashboard performance and filter usability are tightly clustered. Product should ship the CSV export and investigate the timeout issues on the analytics tab.

Wrap-up and next steps

This pipeline gives you a working foundation. Two concrete moves from here:

  1. Schedule the script as a daily job against your help desk API export. Because Oxlo.ai uses flat per-request pricing, increasing the batch size to 50 or 100 long transcripts does not scale your cost with input length (see https://oxlo.ai/pricing).
  2. Add a validation layer. Store previous cluster names in a local JSON file and prompt the model to maintain naming consistency across runs so trends remain comparable week over week. If your queue contains multilingual tickets, swap the classifier to qwen-3-32b on Oxlo.ai for stronger multilingual reasoning.

Top comments (0)