用 Jev 做内容审核:一致、可解释的判定
TL;DR: 内容审核不是一个难题,而是很多个小决策。用
choice调用对扁平类别列表做分诊,置信度不足时对具体政策规则降级为judgment调用,每次删除都把rationale存成审计记录。一致性来自固定的问题格式,可解释性来自 rationale。下文给出流程、阈值与申诉路径。
先设计类别
分两层,刻意保持简单:
- 第一层——choice 调用:从固定列表里选恰好一个类别,例如
spam(垃圾信息)、harassment(骚扰)、adult(成人内容)、violence(暴力)、none(无违规)。 - 第二层——按规则的 judgment 调用:当第一层结果是
unclear或置信度低于阈值时,针对一条具体规则提是/否问题(“这段文字是否包含可信的威胁?”)。示例数据(example fixture):
{"answer":"yes","confidence":0.97,"rationale":"该消息以付款为条件威胁人身伤害。"}
这与人审团队的实际工作方式一致:先分诊,难例逐条规则复核。同时每个问题都很小——这正是判断模型(judgment model)最喜欢的输入形态。
审核流程图
内容提交
|
v
Jev choice 调用(类别:spam / harassment / adult / violence / none)
|
+--> 类别 = none 且置信度高 --> 发布
|
+--> 置信度低或类别不确定
| |
| v
| Jev judgment 调用(针对具体规则:yes / no / unclear)
| |
| +--> 明确 yes / no --> 执行动作,存 rationale
| |
| +--> 仍不明确 --> 人工审核队列
|
+--> 明确违规 --> 删除,存 rationale,附带申诉链接通知用户
每次删除固定存储五元组:内容 id、类别、answer、confidence、rationale、模型版本。这就是你的审计记录(audit trail)。
最小实现
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}
官方 API 的端点与字段请以 typesafe.ai 官方文档为准。
阈值与申诉路径
| 阶段 | 配置项 | 建议 |
|---|---|---|
| 第一层 choice | 自动执行阈值 | 删除与发布统一用 confidence >= 0.85 |
| 第二层 judgment | 边界规则复核 | 第一层低置信度时触发;仅对明确的 yes/no 执行动作 |
| 人工队列 | 触发条件 | 任何 unclear 结果,或第二层后置信度仍低于阈值 |
| 审计记录 | 一律存储 | 内容 id、类别、answer、confidence、rationale、模型版本 |
| 申诉 | 用户可见 | 通知里附 rationale 摘要与一键申诉入口 |
为什么问题格式这么重要,见状态与问题(state and questions)指南;阈值调优见置信度与兜底(confidence and fallback)指南。多语言审核案例展示了这套两层设计在跨语言场景下的表现。
本文适用版本:Jev 1.13。
多级处置策略:放行、复审、拦截
两层调用只给出答案,处置策略决定对这个答案做什么。把”答案”与”动作”分开,是让审核系统可维护的关键一步。
三档动作与触发条件
| 动作 | 触发条件 | 执行内容 |
|---|---|---|
| 放行(publish) | 第一层 answer=none,confidence ≥ 0.85 | 内容进入正常发布队列 |
| 拦截(remove) | 第一层 answer 为违规类别,confidence ≥ 0.85 | 隐藏内容,向用户发送附 rationale 的通知 |
| 复审(human-review) | 任何 unclear,或两层后 confidence 仍低于阈值 | 进入人工队列,附完整调用记录 |
不对称阈值的使用时机
拦截阈值和放行阈值不必对称。若平台对误删(false positive)的容忍度低,可将拦截阈值调高(如 0.92),而放行阈值保持 0.85——中间地带更多流向复审队列,而不是直接拦截。同理,成人内容与垃圾信息对用户伤害程度不同,可以为不同类别设置不同阈值。
示例数据(example fixture):
{"category":"adult","threshold_remove":0.92,"threshold_publish":0.85}
阈值体系本身要版本化存储,和模型版本一同归档——这样回溯历史判定时,能复现当时的决策逻辑。关于置信度调优的更多方法,参见置信度与兜底指南。
边界样本处理
置信度接近阈值的内容(边界样本)是审核系统最容易出错的地方。以下是两种常见的处理路径。
路径一:针对规则的二次 judgment
当第一层 choice 返回低置信度时,不要直接进人工队列——先用 judgment 调用对最可能违规的规则做一次是/否确认。示例数据(example fixture):
{"answer":"no","confidence":0.91,"rationale":"该消息措辞强硬但未包含可识别的威胁要素。"}
这一步的成本远低于人工审核,能消化掉相当一部分模糊样本。
路径二:聚合边界样本用于策略迭代
对连续进入复审队列、但人工最终判定为”发布”的样本,定期做聚合分析。如果某个类别的边界样本中人工放行率持续偏高,说明该类别的提示词措辞或阈值需要重新校准,而不是让人工反复处理同类内容。
聚合时,只记录调用元数据(类别、answer、confidence、人工结论),不存储内容原文——这样既能用于策略迭代,也便于在数据留存政策到期时单独清除内容字段。
阈值与置信度联动
置信度数值本身不等于”准确率”,它是模型对自身判断的不确定性估计。这一节讨论如何把置信度接入系统逻辑,而不是只做一个静态截断。
动态阈值触发链
第一层 choice
|
+--> confidence ≥ 0.85,answer 明确 --> 直接执行动作
|
+--> 0.70 ≤ confidence < 0.85 --> 触发第二层 judgment
|
+--> confidence < 0.70 --> 直接进人工队列(跳过第二层)
第二层 judgment 调用只有在置信度处于中间区间时才有价值。极低置信度(< 0.70)时,再做一次 judgment 往往也返回 unclear,不如直接进人工队列节省延迟。具体分界值需根据实际流量调整,这里给出的仅为参考结构,不是固定数字。
多类别置信度分布监控
若平台流量足够,建议按类别分别追踪置信度分布(直方图),而不只看总体通过率。示例数据(example fixture):
[
{"category":"spam", "p50_confidence":0.96,"p10_confidence":0.88},
{"category":"harassment", "p50_confidence":0.89,"p10_confidence":0.71},
{"category":"adult", "p50_confidence":0.94,"p10_confidence":0.82}
]
harassment 类别的 p10 置信度(0.71)明显低于其他类别,说明这个类别更容易产生边界样本,可以优先为它补充更细粒度的规则拆分或调整提示词措辞。
日志留存与审计记录
每次调用都要归档,不只是删除事件。日志是申诉流程、监管合规和策略迭代的共同基础。
最小日志结构
{
"event_id": "mod-20261019-0042",
"content_id": "post-8871",
"layer": 1,
"call_type": "choice",
"answer": "harassment",
"confidence": 0.91,
"rationale": "消息包含针对具体个人的重复性贬损语言。",
"model_version": "jev-1.13",
"action": "remove",
"ts": "2026-10-19T08:14:03Z"
}
示例数据(example fixture):上方 JSON 结构。
字段说明:layer 区分第一层 choice 与第二层 judgment,便于单独统计两层的调用量与置信度分布;model_version 与动作绑定,确保策略变更前后的判定可追溯;rationale 原文保留,申诉审核员可直接阅读,无需重新调用模型。
留存期与分级存储
日志中内容原文和 rationale 的留存期,需与平台隐私政策对齐。一种常见做法是分级存储:
- 热存储(90 天):完整日志,供申诉和实时监控使用。
- 冷存储(1 年):仅保留元数据(content_id、类别、action、model_version),内容原文和 rationale 脱敏或删除。
- 合规存档:按监管要求保留最低必要字段,周期结束后彻底删除。
数据留存策略本身应作为配置项版本化管理,而不是硬编码在业务逻辑里。关于状态与调用结构的更多讨论,参见三元基础指南。
FAQ
- 审核类别怎么组织? 第一层 choice 用扁平列表(spam、harassment、adult、violence、none),边界情况用按规则的 judgment 复核,不要写一个巨型多标签提示词。
- 为什么要保留 rationale? 它是审计记录:向用户、申诉审核员和监管方解释每次删除,比人工备注一致得多。
- 置信度低怎么办? 针对具体规则做第二次 judgment,仍不明确就进人工队列——无论哪一步都附带 rationale。