DEV Community

shashank ms
shashank ms

Posted on

Using LLM for Topic Modeling: A Comprehensive Guide

Topic modeling with an LLM replaces weeks of clustering work with a few API calls. In this tutorial I will build a complete pipeline that reads raw support tickets, extracts a clean topic taxonomy, and labels every ticket. It is useful for product teams and researchers who need thematic analysis without training a custom model.

What you'll need

  • Python 3.10 or newer
  • pip install openai pandas
  • An Oxlo.ai API key from https://portal.oxlo.ai
  • A dozen or more text snippets to analyze

Step 1: Configure the Oxlo.ai client

Oxlo.ai is fully OpenAI SDK compatible, so the switch is only a base URL and an API key. I use llama-3.3-70b as the general-purpose workhorse for this pipeline.

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": "Reply with exactly: Connection OK"}],
)
print(response.choices[0].message.content)

Step 2: Prepare the documents

I will use a hardcoded list of support snippets. In production you would read from a CSV or your data warehouse.

documents = [
    "PDF export button spins forever and never downloads.",
    "Please add dark mode to the dashboard for night usage.",
    "CSV export works, but PDF fails on datasets over 50 rows.",
    "Dark mode would reduce eye strain during long sessions.",
    "Team invite link returns a 403 error when clicked.",
    "Billing page displays the wrong VAT number and won't update.",
    "Invite links expire too fast, should be valid for 24 hours.",
    "PDF exports are blank if the report contains large charts.",
    "Mobile app needs dark mode too.",
    "Cannot save the VAT number in billing settings.",
]

Step 3: Define the extraction prompt

The system prompt is the most important part of the pipeline. It forces JSON output and constrains the model to only the themes present in the text.

SYSTEM_PROMPT = """You are a precise topic modeling assistant.
Your job is to read a list of user feedback texts and extract the main themes.

Rules:
- Identify up to 5 distinct topics.
- Each topic must have a short label (2 to 4 words) and a one-sentence description.
- Base topics only on the provided texts. Do not hallucinate.
- Output strictly as JSON in this format:
{
  "topics": [
    {"id": "topic_1", "label": "Label", "description": "Description."}
  ]
}
"""

Step 4: Generate the topic taxonomy

I send the full document list in one request. Because Oxlo.ai uses flat per-request pricing, stuffing the prompt with every ticket does not inflate cost the way token-based billing would.

from openai import OpenAI
import json

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

numbered_docs = "\n".join([f"{i}. {d}" for i, d in enumerate(documents)])

user_message = f"Analyze the following feedback texts and return the JSON taxonomy:\n\n{numbered_docs}"

response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_message},
    ],
)

taxonomy = json.loads(response.choices[0].message.content)
print(json.dumps(taxonomy, indent=2))

Step 5: Classify every document

Now I ask the model to assign each document to one of the topics from Step 4. I keep the taxonomy and the documents in the same prompt so the model has full context.

CLASSIFY_PROMPT = """You are a document classification assistant.
Given a topic taxonomy and a list of user feedback texts, assign each text to exactly one topic from the taxonomy.

Rules:
- Use only the topic IDs provided.
- If a text fits multiple topics, choose the best match.
- Output strictly as JSON in this format:
{
  "assignments": [
    {"doc_index": 0, "topic_id": "topic_1", "reason": "One sentence explaining why."}
  ]
}
"""

taxonomy_text = json.dumps(taxonomy, indent=2)
classify_user_msg = f"Taxonomy:\n{taxonomy_text}\n\nDocuments:\n{numbered_docs}\n\nReturn the JSON assignments."

response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[
        {"role": "system", "content": CLASSIFY_PROMPT},
        {"role": "user", "content": classify_user_msg},
    ],
)

assignments = json.loads(response.choices[0].message.content)
print(json.dumps(assignments, indent=2))

Step 6: Export the results

I join the assignments back to the original texts and write a CSV.

import pandas as pd

topic_lookup = {t["id"]: t["label"] for t in taxonomy["topics"]}

rows = []
for a in assignments["assignments"]:
    idx = a["doc_index"]
    rows.append({
        "document": documents[idx],
        "topic_id": a["topic_id"],
        "topic_label": topic_lookup.get(a["topic_id"], "Unknown"),
        "reason": a["reason"],
    })

df = pd.DataFrame(rows)
df.to_csv("topic_modeling_results.csv", index=False)
print(df.to_string(index=False))

Run it

Save the script as topic_model.py, export your key, and run it.

export OXLO_API_KEY="sk-..."
python topic_model.py

The first call produces a taxonomy like this:

{
  "topics": [
    {
      "id": "topic_1",
      "label": "PDF Export Failure",
      "description": "Users report that PDF exports hang, fail, or produce blank files."
    },
    {
      "id": "topic_2",
      "label": "Dark Mode Request",
      "description": "Users want a dark theme for the dashboard and mobile app."
    },
    {
      "id": "topic_3",
      "label": "Team Invite Errors",
      "description": "Invitation links return 403 errors or expire too quickly."
    },
    {
      "id": "topic_4",
      "label": "Billing VAT Issues",
      "description": "The VAT number field displays wrong values or will not save."
    }
  ]
}

The second call produces assignments, and the final CSV looks like this:

document                                           topic_id   topic_label           reason
PDF export button spins forever and never...       topic_1    PDF Export Failure    Directly describes a PDF export hang.
Please add dark mode to the dashboard...           topic_2    Dark Mode Request     Explicitly requests dark mode.
CSV export works, but PDF fails on datasets...     topic_1    PDF Export Failure    Reports PDF failure on large datasets.
Dark mode would reduce eye strain...               topic_2    Dark Mode Request     Requests dark mode for eye strain.
Team invite link returns a 403 error...            topic_3    Team Invite Errors    Describes a 403 error on invite link.
Billing page displays the wrong VAT number...      topic_4    Billing VAT Issues    Reports wrong VAT and update failure.
Invite links expire too fast...                    topic_3    Team Invite Errors    Complains about short invite link expiry.
PDF exports are blank if the report...             topic_1    PDF Export Failure    PDF blank with large charts.
Mobile app needs dark mode too.                    topic_2    Dark Mode Request     Requests dark mode on mobile.
Cannot save the VAT number in billing settings.    topic_4    Billing VAT Issues    Cannot save VAT in billing settings.

Wrap-up and next steps

If your documents are highly technical or ambiguous, swap llama-3.3-70b for deepseek-v3.2 or kimi-k2.6 to get stronger reasoning on edge cases. Both are available on Oxlo.ai with the same client and request-based pricing.

A concrete next step is to wrap this pipeline in a FastAPI endpoint so other services can POST text batches and receive labeled JSON back. Another is to schedule the script daily against your support ticket export and append results to a data warehouse table for trending.

Top comments (0)