How to Use Jev for Content Moderation (Consistent Decisions)

Updated Applies to Jev 1.13

TL;DR: Moderation is not one hard problem — it is many small decisions. Run a choice call over a flat category list, fall back to a judgment call on the specific policy rule when confidence is low, and store the rationale as an audit record for every removal. Consistency comes from the fixed question format; explainability comes from the rationale. Flow, thresholds, and appeal path below.

Category design first

Two levels, kept deliberately simple:

{"answer":"yes","confidence":0.97,"rationale":"The message threatens physical harm contingent on a payment."}

This mirrors how human moderation teams work: triage first, rule-by-rule review for the hard cases. It also keeps each question small, which is exactly what a judgment model wants.

Moderation flow

Content submitted
   |
   v
Jev choice call (category: spam / harassment / adult / violence / none)
   |
   +--> category = none and confidence high --> publish
   |
   +--> confidence low or category uncertain
   |        |
   |        v
   |    Jev judgment call on the specific policy rule (yes / no / unclear)
   |        |
   |        +--> clear yes / no --> act, store rationale
   |        |
   |        +--> still unclear --> human review queue
   |
   +--> clear violation --> remove, store rationale, notify with appeal link

Every removal stores: content id, category, answer, confidence, rationale, and model version. That tuple is your audit trail.

Minimal implementation

import os, json, requests

URL = "https://openrouter.ai/api/v1/chat/completions"
# Confirm the exact model slug on the OpenRouter model page.
MODEL = "typesafe/jev-1.13"
CATEGORIES = ["spam", "harassment", "adult", "violence", "none"]

def call(system: str, text: str) -> dict:
    resp = requests.post(
        URL,
        headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"},
        json={"model": MODEL, "temperature": 0, "messages": [
            {"role": "system", "content": system},
            {"role": "user", "content": text}]},
        timeout=30,
    )
    resp.raise_for_status()
    # example fixture (judgment):
    # {"answer":"yes","confidence":0.97,"rationale":"..."}
    return json.loads(resp.json()["choices"][0]["message"]["content"])

def moderate(text: str, low=0.85) -> dict:
    c = call("Pick exactly one category from: "
             + ", ".join(CATEGORIES) + ".", text)
    if c["answer"] != "none" and c["confidence"] >= low:
        return {"action": "remove", **c}
    if c["confidence"] >= low:
        return {"action": "publish", **c}
    j = call("Does this text violate the harassment policy? "
             "Answer yes, no, or unclear.", text)
    if j["answer"] == "unclear" or j["confidence"] < low:
        return {"action": "human-review", **j}
    return {"action": "remove" if j["answer"] == "yes" else "publish", **j}

Thresholds and the appeal path

StageSettingRecommendation
Level 1 choiceAuto-act thresholdconfidence >= 0.85 for both remove and publish
Level 2 judgmentBorderline rule checkFire when Level 1 is low-confidence; act only on clear yes/no
Human queueTriggerAny unclear answer, or confidence below threshold after Level 2
Audit recordAlways storedContent id, category, answer, confidence, rationale, model version
AppealsUser-facingNotification includes the rationale summary and a one-click appeal

For why the question format matters this much, see the guide on state and questions, and for threshold tuning see confidence and fallback. The multilingual review-moderation case study shows this two-level design holding up across languages.

Applies to Jev 1.13.

FAQ

Frequently asked questions

How do I structure moderation categories with Jev?

Define a flat category list for a first choice call (for example spam, harassment, adult, violence, none), and use a second-stage judgment call per policy rule for borderline or low-confidence cases instead of one giant multi-label prompt.

Why keep the rationale for every decision?

The rationale is your audit record. It explains to users, appeals reviewers, and regulators why a specific piece of content was removed, which manual moderation notes rarely capture consistently.

What happens when confidence is low?

Route the item to a human review queue with the rationale attached. A cheap second judgment call on the specific rule in question can also resolve clear borderline cases before a human ever sees them.

Keep reading