I built this pipeline to process a backlog of product reviews and support tickets without training a custom model. It classifies overall sentiment, extracts aspect-level opinions, and returns structured JSON you can dump into a database or a spreadsheet. You can wire it into an ETL job or a FastAPI endpoint in about an hour.
What you'll need
- An Oxlo.ai API key from https://portal.oxlo.ai
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai
Step 1: Set up the Oxlo.ai client
I load the API key from an environment variable and point the OpenAI SDK at Oxlo.ai. A quick ping confirms the endpoint is live.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ["OXLO_API_KEY"]
)
resp = client.chat.completions.create(
model="llama-3.3-70b",
messages=[{"role": "user", "content": "Say OK"}]
)
print(resp.choices[0].message.content)
Step 2: Lock down the system prompt
The prompt is the contract. It forces the model to return raw JSON with sentiment, confidence, and a list of aspects and opinions. I keep it in a constant so I can version it easily.
SYSTEM_PROMPT = """You are a sentiment analysis and opinion mining engine.
Analyze the user text and return ONLY a JSON object with this exact schema:
{
"sentiment": "positive" | "neutral" | "negative" | "mixed",
"confidence": 0.0 to 1.0,
"aspects": [
{
"aspect": "string naming the product feature or topic",
"opinion": "string describing the user opinion",
"polarity": "positive" | "neutral" | "negative"
}
]
}
Rules:
- Do not wrap the JSON in markdown code fences.
- If no specific aspect is mentioned, return an empty aspects array.
- Confidence should reflect how explicit the sentiment is in the text."""
Step 3: Build the analyzer function
This wrapper calls the model, strips any accidental markdown fences, and parses the result. I set temperature to 0.1 because sentiment scoring should be deterministic.
import json
def analyze_text(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,
)
raw = response.choices[0].message.content.strip()
# Guard against models that occasionally wrap JSON in fences
if raw.startswith("
```"):
lines = raw.splitlines()
if lines[0].startswith("```
"):
lines = lines[1:]
if lines and lines[-1].startswith("
```"):
lines = lines[:-1]
raw = "\n".join(lines).strip()
try:
return json.loads(raw)
except json.JSONDecodeError:
return {
"sentiment": "error",
"confidence": 0.0,
"aspects": [],
"raw_response": raw
}
Step 4: Prepare a batch of reviews
In production you will stream these from a CSV or a database. For now I hardcode four realistic reviews so the script is self-contained.
reviews = [
"The battery life on this laptop is incredible, but the fan noise is unbearable during video calls.",
"Shipping was fast. The box was damaged, though the product inside seems fine.",
"I expected better customer support. Waited three days for a generic reply.",
"Absolutely love the new camera module. Portrait mode is crisp and the low light performance shocked me.",
]
Step 5: Run the batch and print a summary
We loop through the reviews, call the analyzer for each, and print a quick text table. You can replace the print statements with a pandas DataFrame or a database insert.
results = []
for review in reviews:
parsed = analyze_text(review)
results.append({"text": review, "analysis": parsed})
aspects = ", ".join([a["aspect"] for a in parsed.get("aspects", [])])
print(f"{parsed['sentiment']:10} | conf={parsed['confidence']:.2f} | aspects: {aspects}")
Run it
Here is the complete script. Save it as sentiment_pipeline.py, export your OXLO_API_KEY, and run it.
import os
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key=os.environ["OXLO_API_KEY"]
)
SYSTEM_PROMPT = """You are a sentiment analysis and opinion mining engine.
Analyze the user text and return ONLY a JSON object with this exact schema:
{
"sentiment": "positive" | "neutral" | "negative" | "mixed",
"confidence": 0.0 to 1.0,
"aspects": [
{
"aspect": "string naming the product feature or topic",
"opinion": "string describing the user opinion",
"polarity": "positive" | "neutral" | "negative"
}
]
}
Rules:
- Do not wrap the JSON in markdown code fences.
- If no specific aspect is mentioned, return an empty aspects array.
- Confidence should reflect how explicit the sentiment is in the text."""
def analyze_text(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,
)
raw = response.choices[0].message.content.strip()
if raw.startswith("```
"):
lines = raw.splitlines()
if lines[0].startswith("
```"):
lines = lines[1:]
if lines and lines[-1].startswith("```
"):
lines = lines[:-1]
raw = "\n".join(lines).strip()
try:
return json.loads(raw)
except json.JSONDecodeError:
return {"sentiment": "error", "confidence": 0.0, "aspects": [], "raw_response": raw}
reviews = [
"The battery life on this laptop is incredible, but the fan noise is unbearable during video calls.",
"Shipping was fast. The box was damaged, though the product inside seems fine.",
"I expected better customer support. Waited three days for a generic reply.",
"Absolutely love the new camera module. Portrait mode is crisp and the low light performance shocked me.",
]
if __name__ == "__main__":
for review in reviews:
parsed = analyze_text(review)
aspects = ", ".join([a["aspect"] for a in parsed.get("aspects", [])])
print(f"{parsed['sentiment']:10} | conf={parsed['confidence']:.2f} | aspects: {aspects}")
Expected output:
$ python sentiment_pipeline.py
mixed | conf=0.85 | aspects: battery life, fan noise
mixed | conf=0.78 | aspects: shipping, packaging, product condition
negative | conf=0.92 | aspects: customer support
positive | conf=0.95 | aspects: camera module, portrait mode, low light performance
Wrap-up and next steps
If your data includes multilingual reviews, swap llama-3.3-70b for qwen-3-32b in the model string. For coding-related feedback, deepseek-v3.2 handles technical jargon well.
Because Oxlo.ai uses flat per-request pricing, a long, rambling customer ticket costs the same as a short sentence. That makes a big difference when you are batch-processing thousands of reviews or support conversations. See https://oxlo.ai/pricing for plan details.
Two concrete ways to extend this:
- Wrap the analyzer in a FastAPI endpoint and stream results with Server-Sent Events.
- Store the JSON output in Postgres with the
jsonbtype so you can query aspects with SQL.
Top comments (0)