JevCode / 网站地图 / 生态案例

JevCode 文档 · 模型是 clavue-jev

Content moderation API: build one from typed questions

How to build a content moderation API on TypeSafe typed questions — per-policy Noul checks, severity scoring, category routing, threshold governance you control — compared with one-size-fits-all moderation endpoints.

该文档的当前语言版本尚未完成,以下为默认语言内容。moderationcontent-moderationguardrailscookbook
内容流经审查网关后按类别分流

Why “a moderation endpoint” is the wrong unit of purchase

Most platforms sell moderation as one endpoint with fixed labels: toxicity, hate, violence, sexual, self-harm. That is convenient until the first time your product’s definition of a violation differs from the vendor’s — a marketplace that must flag off-platform payment attempts, a gaming chat where “trash talk” is allowed but doxxing is not, a health forum where dosage questions are the point.

Your moderation policy is a product decision. The API you buy should not hard-code it. What you actually need is a way to express the policy — and to change it when the policy changes, without retraining anything and without waiting for a vendor.

This page shows how to build moderation from typed questions: each policy rule is a question, each answer carries a probability, and the thresholds — the actual policy — live in your code, versioned like code.

The shape: one request per message, questions per rule

A message goes in; one request carries a Noul question per policy rule plus a Score for severity; decisions come out. There is no second call and no per-rule endpoint to fan out to.

from typesafe_sdk import AsyncTypeSafeClient, Noul, Score

MODERATION_RULES = {
    "spam": Noul(instructions="Is this message spam or bulk advertising?"),
    "scam": Noul(instructions="Does this message try to defraud or phish the reader?"),
    "pii": Noul(instructions="Does this message contain personal data such as addresses, phone numbers, or IDs?"),
    "offsite_payment": Noul(instructions="Does this message try to move the transaction off the platform (external payment, direct email)?"),
    "severity": Score(
        instructions="If a policy is violated, how much harm would acting on it do?",
        criteria=["none", "minor", "moderate", "severe"],
    ),
}

async def moderate(client: AsyncTypeSafeClient, message: str) -> dict:
    async with client:
        response = await client.system_one(state=message, questions=MODERATION_RULES)
    return {rule: response.answers[rule] for rule in MODERATION_RULES}

The policy is the dict. Adding “no medical advice” to a health forum is one line; it ships with your next deploy, not with your vendor’s next model card.

Thresholds are the policy — keep them in your code

Every Noul answer is a probability. What counts as a violation is the threshold you apply to it, and different rules can carry different thresholds:

THRESHOLDS = {"spam": 0.90, "scam": 0.85, "pii": 0.70, "offsite_payment": 0.95}
SEVERITY_ESCALATE = 2.5  # between "moderate" and "severe" on the 4-point scale

def decide(answers: dict) -> str:
    if answers["severity"].score >= SEVERITY_ESCALATE:
        return "escalate"    # legal / trust-and-safety queue
    for rule, threshold in THRESHOLDS.items():
        if answers[rule].noul >= threshold:
            return "block"
    if any(answers[rule].noul >= 0.60 for rule in THRESHOLDS):
        return "review"      # send to the human queue
    return "pass"

Four outcomes, each owned by you:

  • pass — deliver the message
  • review — human queue, cheapest way to burn down false positives
  • block — enforce
  • escalate — the severity Score pushed over its line, e.g. legal or trust-and-safety

Compare that with a moderation endpoint that returns its own category labels and a flag. You can tune exactly one thing: the flag cutoff. The labels, their definitions, and their calibration are someone else’s deploy.

Why the probabilities matter more than the labels

A boolean flag throws away the information you need to operate a moderation system:

You can measure the borderline. The share of decisions landing between 0.6 and 0.9 is your human-review workload. That number is legible before launch, not after a pile of escalations. Self-consistency testing extends this: run borderline posts through the rubric repeatedly and check whether labels hold still — label wobble is the silent killer of automated moderation.

You can gate on confidence like every other decision in the pipeline. The same confidence-routing pattern used for classification applies unchanged: high probability acts, low probability reviews, and the split is a number you chose.

You can verify the verifier. Because each rule is a separate question with a distribution, you can hold out labeled examples per rule and measure precision per rule — instead of discovering via a news cycle that one hidden category was misfiring.

Where this sits next to input guardrails

Guardrails for LLMs covers the LLM-specific case: screening prompts and completions for jailbreaks and harmful output around a model call. The moderation system described here is the broader product surface — user messages, profiles, listings, reviews — where the rules come from your terms of service rather than from model safety. Same primitive, different rule author. Many products end up running both: product moderation on user content, guardrails around the model.

Real moderation systems built this way

  • Jev-Moderation-Bot — a Discord bot where Jev checks spam and scam links, with warning and timeout escalation rules kept local
  • mastra-jev-moderation — input moderation for Mastra agents on TypeSafe Jev, in one file

Build it