Social media sentiment analysis usually fails when it misses nuance, sarcasm, or product-specific context. In this tutorial we will build a lightweight opinion miner that takes raw posts and returns structured sentiment data using an LLM. I run it on Oxlo.ai because the flat per-request pricing keeps costs predictable even when posts are long threads or include large context windows, and you can see the exact structure at https://oxlo.ai/pricing.
What you'll need
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai - A handful of sample social posts to analyze
Step 1: Initialize the client and test the connection
I import the OpenAI SDK and point it at Oxlo.ai. A quick test request confirms the key is live before we build the rest of the 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": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Say hello"},
],
)
print(response.choices[0].message.content)
Step 2: Define the analysis schema in the system prompt
The agent needs a rigid output format so we can parse it programmatically. I instruct it to return JSON with sentiment labels, confidence scores, aspects, emotions, and a sarcasm flag.
SYSTEM_PROMPT = """You are an opinion mining engine. Analyze the user's social media post and return a single JSON object with exactly these keys:
- sentiment: one of "positive", "negative", "neutral", "mixed"
- confidence: float between 0.0 and 1.0
- aspects: list of objects, each with "topic" and "aspect_sentiment"
- emotions: list of detected emotions
- sarcastic: boolean
Be concise. If the post is ambiguous, reflect that in lower confidence and a mixed sentiment."""
Step 3: Build the analyzer function
I wrap the API call in a helper that accepts a raw post string, injects the system prompt, and parses the JSON response. I use Llama 3.3 70B for reliable instruction following.
import json
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
def analyze_post(post_text: str):
user_message = post_text
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
response_format={"type": "json_object"},
)
return json.loads(response.choices[0].message.content)
Step 4: Ingest a batch of posts and aggregate
Social listening means handling multiple posts at once. I loop over a sample list, analyze each one, and tally sentiments and aspects with a simple Counter.
import json
from collections import Counter
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
posts = [
"Just tried the new update. It crashes every time I open settings. Worst release ever.",
"Love the new dark mode! The design team nailed it. Much easier on the eyes during late night work.",
"Okay so the price went up again? Not sure how I feel. Features are solid though.",
"Yeah, great, another app that requires a subscription. Totally what everyone wanted."
]
results = []
for post in posts:
user_message = post
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
response_format={"type": "json_object"},
)
parsed = json.loads(response.choices[0].message.content)
parsed["original"] = post
results.append(parsed)
sentiment_counts = Counter([r["sentiment"] for r in results])
aspect_counts = Counter([
aspect["topic"] for r in results for aspect in r.get("aspects", [])
])
print("Raw results:")
print(json.dumps(results, indent=2))
print("\nSentiment distribution:", dict(sentiment_counts))
print("Top aspects:", dict(aspect_counts.most_common(5)))
Run it
Save the full script as sentiment_miner.py, export your key, and execute it. The output below is representative of what I see when running against the sample posts on Oxlo.ai.
$ export OXLO_API_KEY="sk-oxlo.ai-..."
$ python sentiment_miner.py
Raw results:
[
{
"sentiment": "negative",
"confidence": 0.95,
"aspects": [
{"topic": "stability", "aspect_sentiment": "negative"},
{"topic": "updates", "aspect_sentiment": "negative"}
],
"emotions": ["frustration", "anger"],
"sarcastic": false,
"original": "Just tried the new update. It crashes every time I open settings. Worst release ever."
},
{
"sentiment": "positive",
"confidence": 0.92,
"aspects": [
{"topic": "dark mode", "aspect_sentiment": "positive"},
{"topic": "design", "aspect_sentiment": "positive"}
],
"emotions": ["joy", "satisfaction"],
"sarcastic": false,
"original": "Love the new dark mode! The design team nailed it. Much easier on the eyes during late night work."
},
{
"sentiment": "mixed",
"confidence": 0.78,
"aspects": [
{"topic": "pricing", "aspect_sentiment": "negative"},
{"topic": "features", "aspect_sentiment": "positive"}
],
"emotions": ["confusion", "caution"],
"sarcastic": false,
"original": "Okay so the price went up again? Not sure how I feel. Features are solid though."
},
{
"sentiment": "negative",
"confidence": 0.88,
"aspects": [
{"topic": "subscription model", "aspect_sentiment": "negative"}
],
"emotions": ["frustration", "annoyance"],
"sarcastic": true,
"original": "Yeah, great, another app that requires a subscription. Totally what everyone wanted."
}
]
Sentiment distribution: {'negative': 2, 'positive': 1, 'mixed': 1}
Top aspects: {'stability': 1, 'updates': 1, 'dark mode': 1, 'design': 1, 'pricing': 1}
Wrap-up
The pipeline is now parsing unstructured posts into structured opinion data. Two concrete next steps: wire this into a scheduled job that pulls from the Twitter or Reddit API and appends results to a CSV, or swap in deepseek-v3.2 on Oxlo.ai to see if a coding-specialized model changes aspect extraction accuracy on technical product discussions.
Top comments (0)