Social media generates endless unstructured opinion data. We will build a lightweight Python pipeline that ingests raw posts and extracts structured sentiment, emotion, and key opinion targets using an LLM. The finished tool fits into any brand analytics stack and runs on Oxlo.ai's flat per-request pricing, so analyzing long threads or large batches stays predictable.
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
Step 1: Set up the Oxlo.ai client
Initialize the OpenAI-compatible client pointing at Oxlo.ai. I keep my key in an environment variable, but a hardcoded string works 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")
)
Step 2: Define the system prompt
The system prompt forces the model to return strict JSON and tells it what to extract: sentiment label, confidence score, primary emotion, targeted entities, and a brief reasoning string.
SYSTEM_PROMPT = """You are a social media sentiment analysis engine.
Analyze the user-provided post and return a single JSON object with these exact keys:
- sentiment: one of ["positive", "neutral", "negative"]
- confidence: float between 0.0 and 1.0
- emotion: one of ["joy", "anger", "sadness", "fear", "surprise", "disgust", "neutral"]
- targets: list of strings, the brands, products, or people mentioned
- reasoning: one sentence explaining why
Rules:
- Output ONLY valid JSON.
- Do not wrap the JSON in markdown code fences.
- If the post is ambiguous, use "neutral" and set confidence below 0.6.
"""
Step 3: Build the analysis function
This helper takes a raw post string, calls Llama 3.3 70B with JSON mode enabled, parses the response, and returns a Python dict. If your social data is multilingual, swap the model ID to qwen-3-32b.
import json
def analyze_post(post_text: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": post_text},
],
response_format={"type": "json_object"},
temperature=0.1,
max_tokens=512,
)
raw = response.choices[0].message.content
return json.loads(raw)
Step 4: Ingest a batch of posts
Real social listening means processing multiple posts. This loop iterates over a list, calls analyze_post for each, and collects the results. Because Oxlo.ai charges per request rather than per token, running this on verbose posts or long comment threads costs the same as short ones.
raw_posts = [
"Just spent 4 hours on hold with @AcmeCorp support. Absolutely furious. Never again.",
"The new Oxlo.ai pricing model is a lifesaver for our agentic workflows. Huge fan.",
"Meh, the update is okay I guess. Nothing special but it works.",
"Why does every AI tool claim to be 'revolutionary'? Starting to feel like marketing fluff.",
]
results = []
for post in raw_posts:
try:
result = analyze_post(post)
result["original"] = post
results.append(result)
except Exception as e:
print(f"Failed on post: {post[:50]}... Error: {e}")
Step 5: Aggregate and report
Turn the structured outputs into actionable metrics. This simple aggregation counts sentiment distribution and surfaces the most frequently mentioned targets.
from collections import Counter
sentiment_counts = Counter([r["sentiment"] for r in results])
target_counts = Counter()
for r in results:
for t in r.get("targets", []):
target_counts[t.lower()] += 1
print("Sentiment Distribution:", dict(sentiment_counts))
print("Top Mentioned Targets:", target_counts.most_common(5))
for r in results:
print(f"[{r['sentiment'].upper()}] {r['emotion']} | targets: {r['targets']}")
print(f" Reasoning: {r['reasoning']}")
print()
Run it
Save the script as sentiment_pipeline.py and run it. Here is the complete runnable file followed by the output I see on my machine.
import os
import json
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 social media sentiment analysis engine.
Analyze the user-provided post and return a single JSON object with these exact keys:
- sentiment: one of ["positive", "neutral", "negative"]
- confidence: float between 0.0 and 1.0
- emotion: one of ["joy", "anger", "sadness", "fear", "surprise", "disgust", "neutral"]
- targets: list of strings, the brands, products, or people mentioned
- reasoning: one sentence explaining why
Rules:
- Output ONLY valid JSON.
- Do not wrap the JSON in markdown code fences.
- If the post is ambiguous, use "neutral" and set confidence below 0.6.
"""
def analyze_post(post_text: str) -> dict:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": post_text},
],
response_format={"type": "json_object"},
temperature=0.1,
max_tokens=512,
)
raw = response.choices[0].message.content
return json.loads(raw)
if __name__ == "__main__":
raw_posts = [
"Just spent 4 hours on hold with @AcmeCorp support. Absolutely furious. Never again.",
"The new Oxlo.ai pricing model is a lifesaver for our agentic workflows. Huge fan.",
"Meh, the update is okay I guess. Nothing special but it works.",
"Why does every AI tool claim to be 'revolutionary'? Starting to feel like marketing fluff.",
]
results = []
for post in raw_posts:
try:
result = analyze_post(post)
result["original"] = post
results.append(result)
except Exception as e:
print(f"Failed on post: {post[:50]}... Error: {e}")
sentiment_counts = Counter([r["sentiment"] for r in results])
target_counts = Counter()
for r in results:
for t in r.get("targets", []):
target_counts[t.lower()] += 1
print("Sentiment Distribution:", dict(sentiment_counts))
print("Top Mentioned Targets:", target_counts.most_common(5))
print()
for r in results:
print(f"[{r['sentiment'].upper()}] {r['emotion']} | targets: {r['targets']}")
print(f" Reasoning: {r['reasoning']}")
print()
Example output:
Sentiment Distribution: {'negative': 2, 'positive': 1, 'neutral': 1}
Top Mentioned Targets: [('acmecorp support', 1), ('oxlo.ai pricing model', 1), ('ai tool', 1)]
[NEGATIVE] anger | targets: ['AcmeCorp support']
Reasoning: The user expresses extreme frustration after a long wait time and states they will never use the service again.
[POSITIVE] joy | targets: ['Oxlo.ai pricing model']
Reasoning: The user explicitly praises the pricing model and calls themselves a huge fan.
[NEUTRAL] neutral | targets: ['update']
Reasoning: The post is lukewarm, acknowledging functionality without enthusiasm or criticism.
[NEGATIVE] disgust | targets: ['AI tool']
Reasoning: The user criticizes repetitive marketing language and expresses skepticism.
Next steps
Wire the pipeline to a real-time source by replacing the static raw_posts list with a webhook listener that ingests live platform feeds. For deeper insight, add a second pass with kimi-k2.6 or deepseek-v3.2 to generate summary reports from the aggregated JSON instead of reading rows manually.
Top comments (0)