Comment moderation with AI: Python example
Spam, insults, ads, genuine comments — the model labels them and sends unclear cases to a moderator. Here's the code and what it costs.
3 min · · development · бизнес
Moderating comments by hand is tedious: every comment needs a read and a decision — fine, spam, insult, or ad. With thousands of comments a day the queue grows faster than anyone can clear it. Below is an API-based auto-moderation setup with a review queue for borderline cases, batch processing, and honest cost math.
How moderation works
Each comment gets a label:
- ok: normal comment, publish.
- spam: obvious spam, delete.
- insult: insult, hide.
- ads: advertising, delete.
Plus a confidence value. If the model is unsure (confidence below a threshold), the comment goes to a moderator queue. Automation takes the obvious cases, a human decides the borderline ones.
Fast moderation with glm-5.3-flash
Start with Zukko's cheapest model — glm-5.3-flash ($0.04/$0.04 per 1M tokens). Whether it fits your comments, we'll check below on a labeled sample.
import json
from openai import OpenAI
client = OpenAI(
base_url="https://api.zukko.pro/v1",
api_key="fetch_your_key",
)
LABELS = ["ok", "spam", "insult", "ads"]
SYSTEM_PROMPT = """You are a comment moderator. Judge the comment and pick a label.
Labels:
- ok: normal comment, publishRouting: automatic or to a human
Not every case is clear. If confidence is below the threshold, the comment goes to a moderator:
ACTIONS = {"ok": "publish", "spam": "delete", "insult": "hide", "ads": "delete"}
def route_comment(text: str, threshold: float = 0.8) -> dict:
"""Decide: publish, delete, or send for review"""
result = moderate_comment(text)
confidence = float(result.get("confidence", 0))
if confidence < threshold or result["label"] not in ACTIONS:
return {
"action": "manual_review",
"suggested_label": result["label"],
"confidence": confidence,
"reason": result.get("reasoning", ""),
}
return {"action": ACTIONS[result["label"]], "label": result["label"], "confidence": confidence}
if __name__ == "__main__":
for comment in ["Great article!", "Maybe this is an ad, maybe not", "SPAM!!!!"]:
print(comment, "->", route_comment(comment)["action"])The model sets confidence itself; it is not a calibrated probability. A threshold of 0.8 is a starting point; tune your own on a sample where people have already assigned the correct labels.
Batch processing: dozens of comments per request
For an overnight pass or an archive cleanup, send comments in batches of 20–50. Ask the model to return the comment number so answers don't get mixed up:
def moderate_batch(comments: list[str], model: str = "glm-5.3-flash") -> dict[int, dict]:
"""Moderate a batch of comments, return {number: result}"""
batch_text = "\n---\n".join(f"Comment {i}:\n{c}" for i, c in enumerate(comments, 1))
system_for_batch = SYSTEM_PROMPT + """
You received several comments. For EACH one output a single line of JSON:
{"id": <comment number>, "label": "...", "confidence": 0.9}
One line per object, no extra text.
"""
response = client.chat.completions.create(
model=model,
max_tokens=60 * len(comments) + 100,
messages=[
{"role": "system", "content": system_for_batch},
{"role": "user", "content": batch_text},Moderator queue
Low-confidence comments pile up in a separate queue. A moderator confirms or corrects the decision:
class ModerationQueue:
def __init__(self):
self.items = [] # in a real project, a database table
def add(self, comment_id: str, text: str, suggestion: dict):
self.items.append({
"id": comment_id,
"text": text,
"suggestion": suggestion, # what the model proposed
"status": "pending",
})
def resolve(self, comment_id: str, final_label: str):
"""The moderator made a decision"""Store the moderator's final decision together with the model's suggestion: these pairs become a labeled sample for re-measuring accuracy and tuning the threshold.
Cost
Estimate for 10,000 comments per month on glm-5.3-flash, averaging 200 input and 50 output tokens per comment:
(10,000 × 200 × 0.04 / 1M) + (10,000 × 50 × 0.04 / 1M)
= $0.08 + $0.02 = $0.10 per month
If you re-check borderline comments on claude-sonnet-5 ($0.30/$0.30), every thousand re-checks adds 1,000 × 250 × 0.30 / 1M = $0.075. What share ends up borderline depends on your site and threshold — measure it on your own data.
Current prices and models: GET https://api.zukko.pro/v1/models or the Models page in your dashboard.
Set up the key
Create a key in the Zukko dashboard, allow only the models you need, and set a monthly limit — $1 comfortably covers 10,000 comments. See also the quick start and Python examples.
FAQ
How often should the queue be reviewed?
It depends on the site. Where comments appear immediately and reaction time matters (news, social), the queue is cleared during the day. For forums and blogs, once or twice a day is enough.
Can the model learn from moderator decisions?
Not by itself. But you can add moderator decisions to the prompt as examples ("a comment like this is ads") and periodically re-measure accuracy. If accuracy drops, route borderline cases to another model and compare results on the same sample.
What about comments in several languages?
Say in the prompt that comments may be in different languages, and add examples in each language to your test sample. Compare the accuracy of glm-5.3-flash and claude-sonnet-5 on those examples — that tells you whether you need the pricier model.
Should I moderate old comments?
If the archive was never checked, batch processing lets you go through it cheaply: 10,000 comments on glm-5.3-flash cost about $0.10.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Prompt Injection: What It Is and How to Protect a Bot and an AgentHow prompt injection works through user input and third-party documents, and which measures limit the damage: data isolation, tool permissions, key limits.
- Your Own Neural Network Proxy on Node.js: Keep the API Key Off the FrontendA small Express server between your frontend and the model API: the key stays on the server, and models, request size and rate are capped.
- DeepSeek API in Python: Requests, Streaming, JSON and Error HandlingReady code for DeepSeek V4.1 Flash and V4 Pro on the OpenAI SDK: a request, streaming, JSON parsing and careful error handling.
- Prompt Engineering Examples: 12 Ready-Made Templates for WorkTwelve prompt templates for everyday tasks: what each one says, why it is built that way, and what a single API call costs.