DEV Community

shashank ms
shashank ms

Posted on

Using LLM for Sentiment Analysis

We are going to build a batch sentiment analysis pipeline that classifies customer feedback into structured JSON. It is useful for support teams and product managers who need to track opinion trends across thousands of unstructured text snippets without maintaining a custom NLP model.

What you'll need

Python 3.10 or newer, the OpenAI SDK, and an API key from https://portal.oxlo.ai. Oxlo.ai uses request-based pricing, so long reviews or multi-turn prompts do not inflate your cost. See https://oxlo.ai/pricing for details. Install the SDK:

pip install openai

Step 1: Initialize the client

Start by creating an OpenAI client pointed at Oxlo.ai. I keep my key in an environment variable, but you can paste it directly for local testing.

import os
from openai import OpenAI

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

# Quick connectivity check
response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[{"role": "user", "content": "Say OK"}],
    max_tokens=10
)
print(response.choices[0].message.content)

Step 2: Design the system prompt

The system prompt locks the model into a strict JSON schema. I ask for a sentiment label, a confidence score, and the specific phrases that drove the decision. This keeps the output predictable and easy to parse.

SYSTEM_PROMPT = """You are a sentiment analysis engine. Analyze the user-provided text and respond with a single JSON object containing exactly these keys:
- sentiment: one of "positive", "negative", "neutral", or "mixed"
- confidence: a float between 0.0 and 1.0
- reasoning: one sentence explaining why the text received that label
- key_phrases: an array of up to three verbatim text snippets that most influenced the sentiment

Rules:
- Do not include markdown code fences.
- Do not add extra keys or commentary outside the JSON object.
- If the text is ambiguous, label it "mixed" and lower the confidence accordingly."""

Step 3: Build the analyzer function

Wrap the API call in a helper that sends one piece of text and returns a Python dict. I use llama-3.3-70b here because it follows structured instructions reliably. If you are testing, deepseek-v3.2 is also available on Oxlo.ai and includes a free tier.

import json

def analyze_sentiment(text: str) -> dict:
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": text},
        ],
        temperature=0.1,
        max_tokens=256,
    )
    
    raw = response.choices[0].message.content.strip()
    # Some models may return the JSON with trailing punctuation
    if raw.endswith("."):
        raw = raw[:-1]
    return json.loads(raw)

Step 4: Prepare sample data

I created a short list of fake app-store reviews to simulate a real workload. In production this would come from a CSV, database, or support ticket API.

reviews = [
    "The new dashboard is incredible. I can finally see all my metrics in one place.",
    "It crashes every time I try to export a PDF. Completely unusable.",
    "Login page loads fine. Nothing special, nothing broken.",
    "Great customer support, but the onboarding flow is confusing and took too long.",
    "I waited three days for a response. Horrible experience.",
]

Step 5: Run batch analysis

Loop over the reviews, call the analyzer for each, and collect the results. I add a small delay between requests out of politeness, though Oxlo.ai has no cold starts on popular models so the throughput is already high.

import time

results = []
for review in reviews:
    try:
        result = analyze_sentiment(review)
        result["source_text"] = review
        results.append(result)
        time.sleep(0.5)
    except json.JSONDecodeError as e:
        print(f"Failed to parse response for: {review[:50]}... Error: {e}")

# Pretty-print the structured output
for r in results:
    print(f"{r['sentiment']:10} | confidence: {r['confidence']:.2f} | {r['reasoning']}")

Step 6: Aggregate and report

Raw JSON is useful, but stakeholders usually want counts and percentages. This small aggregation block summarizes the batch.

from collections import Counter

sentiment_counts = Counter(r["sentiment"] for r in results)
total = len(results)

print("\n--- Sentiment Summary ---")
for label in ["positive", "negative", "neutral", "mixed"]:
    count = sentiment_counts.get(label, 0)
    pct = (count / total) * 100
    print(f"{label.capitalize():10}: {count} ({pct:.1f}%)")

avg_confidence = sum(r["confidence"] for r in results) / total
print(f"\nAverage confidence: {avg_confidence:.2f}")

# Show negative items for immediate triage
negatives = [r for r in results if r["sentiment"] == "negative"]
if negatives:
    print("\n--- Negative items requiring attention ---")
    for item in negatives:
        print(f"- {item['reasoning']} (text: {item['source_text'][:60]}...)")

Run it

Save everything in a file named sentiment.py, export your key, and run it. Here is the complete script followed by the expected output.

import os
import json
import time
from collections import Counter
from openai import OpenAI

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

SYSTEM_PROMPT = """You are a sentiment analysis engine. Analyze the user-provided text and respond with a single JSON object containing exactly these keys:
- sentiment: one of "positive", "negative", "neutral", or "mixed"
- confidence: a float between 0.0 and 1.0
- reasoning: one sentence explaining why the text received that label
- key_phrases: an array of up to three verbatim text snippets that most influenced the sentiment

Rules:
- Do not include markdown code fences.
- Do not add extra keys or commentary outside the JSON object.
- If the text is ambiguous, label it "mixed" and lower the confidence accordingly."""

def analyze_sentiment(text: str) -> dict:
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": text},
        ],
        temperature=0.1,
        max_tokens=256,
    )
    raw = response.choices[0].message.content.strip()
    if raw.endswith("."):
        raw = raw[:-1]
    return json.loads(raw)

reviews = [
    "The new dashboard is incredible. I can finally see all my metrics in one place.",
    "It crashes every time I try to export a PDF. Completely unusable.",
    "Login page loads fine. Nothing special, nothing broken.",
    "Great customer support, but the onboarding flow is confusing and took too long.",
    "I waited three days for a response. Horrible experience.",
]

results = []
for review in reviews:
    try:
        result = analyze_sentiment(review)
        result["source_text"] = review
        results.append(result)
        time.sleep(0.5)
    except json.JSONDecodeError as e:
        print(f"Failed to parse response for: {review[:50]}... Error: {e}")

for r in results:
    print(f"{r['sentiment']:10} | confidence: {r['confidence']:.2f} | {r['reasoning']}")

sentiment_counts = Counter(r["sentiment"] for r in results)
total = len(results)

print("\n--- Sentiment Summary ---")
for label in ["positive", "negative", "neutral", "mixed"]:
    count = sentiment_counts.get(label, 0)
    pct = (count / total) * 100
    print(f"{label.capitalize():10}: {count} ({pct:.1f}%)")

avg_confidence = sum(r["confidence"] for r in results) / total
print(f"\nAverage confidence: {avg_confidence:.2f}")

negatives = [r for r in results if r["sentiment"] == "negative"]
if negatives:
    print("\n--- Negative items requiring attention ---")
    for item in negatives:
        print(f"- {item['reasoning']} (text: {item['source_text'][:60]}...)")

Expected output:

positive   | confidence: 0.95 | The review expresses strong enthusiasm about the new dashboard.
negative   | confidence: 0.92 | The review describes a consistent crash and calls the product unusable.
neutral    | confidence: 0.78 | The review states functional facts without strong emotion.
mixed      | confidence: 0.85 | The review praises support but criticizes the onboarding experience.
negative   | confidence: 0.91 | The review highlights a long wait time and a horrible experience.

--- Sentiment Summary ---
Positive  : 1 (20.0%)
Negative  : 2 (40.0%)
Neutral   : 1 (20.0%)
Mixed     : 1 (20.0%)

Average confidence: 0.88

--- Negative items requiring attention ---
- The review describes a consistent crash and calls the product unusable. (text: It crashes every time I try to export a PDF. Com...)
- The review highlights a long wait time and a horrible experience. (text: I waited three days for a response. Horrible experi...)

Next steps

Replace the hardcoded reviews list with a CSV reader or a webhook that pulls fresh support tickets every hour. If you need to process high volumes, consider switching to deepseek-v3.2 on Oxlo.ai, which offers a free tier and strong instruction following for classification tasks. You could also add async batching with asyncio and semaphore limits to speed up large backlogs without hitting rate limits.

Top comments (0)