第 18 章

第 18 章:内容审核与信任安全——Moderation and trust and safety

第 18 章:内容审核与信任安全——Moderation and trust and safety

内容审核是所有垂直场景里判断量最大的一个:每一条用户内容都要过一遍,坏内容又在不停变换写法逃逸。用昂贵的大模型逐条审,成本扛不住;用关键词表,逃逸一戳就破。本章讲 Jev 怎么用"分级审核 + 逐条 Noul + 批量吞吐"这套组合,把审核做成既便宜又扛得住对抗的判定层。

为什么是分级,而不是一个大分类器

新手做审核,第一反应往往是训一个大分类器把所有违规类型一次判完。这条路在真实对抗下有几个硬伤:类别会持续变化(新话术层出不穷),一个多分类模型对"同时踩两类"的内容给不出正确表达,更麻烦的是它的输出没有可直接用的概率,定不了阈值的分级处置。

于是成熟的做法都是分级 + 逐条:

  • 分级:先跑便宜且确定的第一层(关键词、正则、本地格式规则),只把剩下的灰区内容往上级送。级别越高,判定越贵,送上去的量越小。
  • 逐条:违规类型写成互相独立的 Noul 问题,一条内容可能同时命中"垃圾信息"和"钓鱼链接",用多个布尔判定如实表达,而不是强迫它选一个类。

这套做法把"贵"和"准"错开了:绝大多数内容在第一层就被廉价地处理掉,只有真正模糊的才动用模型。

mastra-jev-moderation:一次请求,两级判定

CodeAlive-AI/mastra-jev-moderation 把分级审核压缩进了一次 Jev 请求。它是一个 Mastra 的输入处理器,对每条进来的消息问两个问题:一个 Boolean("这条消息是否必须被拦截?"),加一个类别 Choice,命中阈值就中止本回合。

生产数据很能说明问题:它拦截了 9/9 条敌对消息,同时 0/49 条正常消息被误拦,中位延迟约 0.4 秒,成本大约是一个 LLM 审核员的 1/4(约 4 倍便宜)。

更值得抄的是它的失败策略——fail-open,并且加超时和熔断。审核挂掉时它选择放行而不是全拦,因为全拦会让整个助手不可用。超时和熔断保证不会因为 Jev 拖慢而卡住主流程。审核场景里"坏内容漏过"和"正常服务瘫痪"是两种代价,这个项目明确选了不能瘫痪。

d = client.systemone(
    state=message_text,
    questions={
        "block": {
            "type": "noul",
            "question": "这条消息是否必须被拦截(钓鱼、垃圾、社工)?"
        },
        "category": {
            "type": "choice",
            "question": "若需拦截,属于哪一类?",
            "choices": ["phishing", "spam", "social_engineering", "none"]
        }
    },
    instructions="你是输入审核器。务必拦截钓鱼与社工消息,正常闲聊一律放行。"
)

BLOCK_THRESHOLD = 0.7          # 到达 0.7 即中止本回合
try:
    if d["block"].probability >= BLOCK_THRESHOLD:
        abort_turn(reason=d["category"].choice)
except (TimeoutError, APIError):
    pass                        # fail-open:审核不可用时不阻断主流程

把 block 和 category 放在同一次请求里,还有一个隐性收益:类别判定能吃到与拦截判定相同的上下文,两者不会互相打架。

Jev Moderation Bot:四阶段升级阶梯

Jev Moderation Bot(brainstormity/Jev-Moderation-Bot)是个 Discord 机器人,它把"分级"做成了显式的四阶段 escalation ladder:对每条进来的消息,用 Jev 打分判断钓鱼、垃圾、社工风险,再按风险级别逐级升级处置——从静默记录,到警告,到限流,直到人工介入。

它有一个很聪明的细节:被"赦免"(判为正常)的消息会重新注入回上下文,作为"已验证安全"的先例。这样后续相似的消息就有了参照,判定更稳,也减少了同类误判的重复发生。这其实是在决策层之上加了一层很轻的记忆。

escalation ladder 的价值在于它把"审核"从一个二值动作变成了一个连续的动作空间。不是"删或留",而是"记录 / 警告 / 限流 / 移交人工",每级都有自己的代价。阈值定在哪一级,取决于社区对误伤的容忍度——技术社区容忍度低,就要把自动处置的档位往上抬。

severity = client.systemone(
    state=message_text,
    questions={
        "is_phishing": {"type": "noul", "question": "是否为钓鱼链接或诱导点击?"},
        "is_scam":     {"type": "noul", "question": "是否为诈骗或社工话术?"},
        "level": {
            "type": "score",
            "question": "该消息对社区的危害等级?",
            "labels": ["无", "轻", "中", "重"]
        }
    },
    instructions="你是社区审核员,按社区的公开规则判定。"
)

lv = severity["level"].score
if severity["is_phishing"].probability >= 0.8:
    ban_and_log(message)          # 最高档
elif lv >= 2.5:
    throttle(message)
elif lv >= 1.5:
    warn(message)
else:
    log_only(message)             # 最低档:只记录

阶梯的每一档都写在代码里,if/elif 一眼看全,调整处置策略不需要动模型。

逃逸对抗:变换写法、隐语与混排

审核最难的从来不是"识别直白的脏话",而是识破变换。用户会把字母换成数字或形近字符(a55h0le),会把脏话拆成谐音或隐语(mike_hunt),会混在图片文字里,会在正常句子里夹一句攻击。

profanity-checker(4rays/profanity-checker)针对的就是变换写法这一层。它是个 Cloudflare Worker,对文本或用户名问两个 Noul:一个判字面的脏话,另一个专门判拼音或形近伪装(原文举的例子正是 a55h0le、mike_hunt)。两个判定用 max() 策略合并——任一命中即算违规。阈值、max() 策略、JSON 响应和 OpenAPI schema 全都写在 Worker 代码里,endpoint 还能通过 service binding 被其他 Worker 直接调用。

async function check(text: string, env: Env): Promise<{ flagged: boolean; p: number }> {
  const d = await systemone({
    state: text,
    questions: {
      literal: { type: "noul", question: "文本或用户名中是否含有字面的脏话?" },
      disguised: { type: "noul",
        question: "是否通过数字、形近字符或拼音谐音伪装成脏话(如 a55h0le、mike_hunt)?" }
    },
    instructions: "你是字符级审核器,只判断是否存在脏话,不判断语气。"
  });
  const p = Math.max(d.literal.probability, d.disguised.probability);  // max 策略
  return { flagged: p >= THRESHOLD, p };
}

拆成"字面"和"伪装"两个问题,比合成一个问题更好调——你可以对伪装层单独设一个更严格的阈值,因为实际攻击大多走伪装这条路。

隐语是比字符伪装更难的一层:mike_hunt 这类谐音,只有知道它指什么才判得出来,纯字符串规则完全无能为力——而这正是语义判定的用武之地。你只要在题干里说清"是否通过谐音或圈内隐语表达脏话或攻击",它就能泛化到没见过的写法。代价是每一版新隐语都要过一次判定,所以审核量会随社区活跃度增长,这也是为什么上一层的关键词过滤不能省。

对抗的另一维度是多模态。social-risk-qwen-jev(TianJinWeiBoss520/social-risk-qwen-jev)的做法是把一个 Qwen3-VL LoRA 和一个 Jev 判定模型配对,去判断多模态的社媒风险内容——视觉部分交给 VL 模型,风险判定交给 Jev。文本改写法只是对抗的一半,图片里藏字、图文混排的组合是另一半,这类组合必须靠多模态前端加决策层后端一起覆盖。

MCP 形态的接入则让审核能力可以被 Agent 直接调用。jev-screen-mcp(jiawei686/jev-screen-mcp)是个单工具 MCP server,把屏幕内容过一遍 Jev,把 noul、choice、score 三种问题类型都作为一等公民暴露出来,还带一个确定性的 mock mode 和三个测试。

批量吞吐与成本控制

审核的成本模型很直接:量乘以单次判定成本。所以除了把判定本身压低,还要批处理。

Jev Chat for Twitch(ethanplusai/jev-chat-for-twitch)是个 Chrome 扩展,通过匿名 IRC WebSocket 读取 Twitch 频道聊天,每 20 条消息一批问 Jev 一个类别 Choice,只把匹配选定意图(helpful、questions、funny、feedback)的消息显示在第二列。它的成本数据很具体:每条消息约 504 个输入 token,在每秒 2 条消息的聊天里大约 $0.15/小时,涨到每秒 50 条时约 $0.76/小时。批处理让活跃聊天室的成本保持在可接受的量级。

Rot Guard(plusminushalf/rot-guard)是另一个批处理的例子,但复杂度更高——它对每个 YouTube 视频、X、Reddit、LinkedIn 页面问一个内容类别 Choice、一个"是否符合用户写的目标"的 Noul 和一个 0–4 的"浪费时间" Score,而对 X 帖子和 YouTube 缩略图按每批 10 条问 Choice 和 Noul。它的阈值还会随累计停留时间在 2、10、20 分钟三个节点逐步收紧——用得越久,拦截越严。这是把"用户注意力"这个成本维度写进了阈值策略。

其余的轻量实现也能看出这个思路:jev-slop-guard(三个并发上限、每条一次缓存)、profanity-checker(Cloudflare Worker 边缘执行)、jev_antispam_bot(backmeupplz/jev_antispam_bot,极简 grammY 机器人,配十个测试文件)、HN Jev Sorter(ritza-co/hn-jev-sorter,逐条给 HN 评论分类,约一秒一条)。

关于"逐条 Noul + 多标签优于一个大分类器",Sniff Test(DanRWilloughby/snifftest)提供了一个有说服力的对照。它的场景是文字审查,对每段问十个 Boolean 问题(堆叠的模糊表达、复述式结尾、not-X-but-Y 转折、光秃秃的成本数字),阈值 0.7。它的误报数据很亮眼:54 段干净文本里只有 1 段被误标;对照之下,用它自己的话说,Haiku 4.5 在同一批文本上误标了 37 段。拆成多条布尔判定、各自定阈值,比让一个模型给一个笼统的"合格/不合格"要准得多——误报少,用户体验就好。Perch(lakeday-org/perch)把这个思路用在了代码上:对每个方法(连同它的调用方和被调方)问一个"是否有 bug"的 Noul、一个"是哪类、在哪行"的 Choice、一个严重度 Score,再叠上按语言过滤的 CWE 判定和自定义规则,任一判断超过下限就让 CI 失败。

误报成本在审核里被严重低估。每一条正常内容被误拦,用户就少用一次产品;审核系统的核心指标不只是"抓到多少坏内容",还有"放过多少好内容"。这也是为什么这些项目普遍偏爱逐条判定 + 概率阈值 + 可调松紧,而不是一个黑箱的"合格分"。

阈值校准与回归

审核的阈值不能拍脑袋定,得当分类器来测。jev-spam-eval(bitnovus/jev-spam-eval)给的正是这个:用零样本(zero-shot)的 Jev Boolean 问题做垃圾信息分类,并拿它跟 TF-IDF 基线做对照。关键不在于"Jev 一定比 TF-IDF 强"——传统基线在固定词表上往往很能打——而在于你得有一个基线。如果 Jev 在这个环节并没有明显强过本地就能跑的 TF-IDF,那这部分或许根本不必发请求。

给一批带标注的样本、扫一遍阈值,就能画出误报/漏报的权衡:

import numpy as np

for t in np.arange(0.3, 0.91, 0.05):
    pred = np.array(probs) >= t
    fp = ((pred == 1) & (labels == 0)).sum()   # 误报:正常内容被拦
    fn = ((pred == 0) & (labels == 1)).sum()   # 漏报:坏内容被放
    print(f"阈值 {t:.2f}  误报 {fp:3d}  漏报 {fn:3d}")

阈值落在哪儿,取决于你更怕哪一种错。CodeAlive-AI/mastra-jev-moderation 那组数据(0/49 误拦、9/9 敌对拦截)之所以可信,是因为背后有标注样本撑着;没有标注集,阈值只是一个感觉。

除了命中率,还有一个更细的指标值得盯:Brier 分数——概率预测的平方误差,它同时罚"判错"和"过度自信"。一个把每条判定都压在 0.95 的分类器,只要错一次,Brier 就比一个诚实报 0.6 的分类器还差。这就解释了为什么这些项目宁可让概率留在中间、把灰区交给人工,也不去把每个输出都刷到接近 1。

所以回归测试要把"阈值 + 标注集"一起钉死,每次改问题措辞或指令就重跑一遍。审核系统的改动很微妙——把题干从"垃圾信息"改成"垃圾或推广",误报可能立刻抬头。这类回归不做,就只能等用户投诉来发现问题。

一个分级审核管线

综合上面的做法,一条审核管线大概长这样:

flowchart TD
    MSG["用户消息"] --> L1["第一层:本地规则
关键词 / 正则 / 格式"] L1 -->|确定命中| BLOCK["直接拦截"] L1 -->|通过| L2["第二层:cheap Jev
Noul 拦截 + Choice 类别"] L2 -->|p ≥ 0.7| BLOCK L2 -->|灰区| L3["第三层:升级判定
Score 危害 + 多 Noul 细分"] L2 -->|p < 0.7| PASS["放行"] L3 -->|高危害| ESC["人工 / 封禁"] L3 -->|低危害| PASS L3 -->|赦免| CTX["回注上下文
作为安全先例"] BLOCK --> LOG["审计日志"] ESC --> LOG PASS --> LOG

第一层几乎零成本地筛掉大部分内容;第二层做便宜的二值判定;只有灰区才升到更贵的细粒度判定。赦免的消息回注上下文当先例,是这个阶梯里很实用的一笔。

小结

  • 审核的核心结构是分级 + 逐条:先跑便宜的确定性规则,把灰区往上级送;违规类型拆成互相独立的 Noul,而不是一个大分类器。
  • CodeAlive-AI/mastra-jev-moderation 用一次请求同时问拦截 Boolean 和类别 Choice,生产数据 9/9 敌对拦截、0/49 正常误拦、约 0.4 s、比 LLM 审核员便宜约 4 倍,并在超时和熔断下 fail-open。
  • brainstormity/Jev-Moderation-Bot 用四阶段 escalation ladder,并把赦免消息回注上下文作为安全先例。
  • 对抗变换靠拆分判定:4rays/profanity-checker 用"字面 + 伪装"两个 Noul 加 max() 策略;多模态靠 TianJinWeiBoss520/social-risk-qwen-jev 这类 VL + Jev 组合。
  • 批量吞吐是成本的主战场:ethanplusai/jev-chat-for-twitch 每批 20 条、约 504 token/条($0.15–$0.76/小时);plusminushalf/rot-guard 每批 10 条且阈值在 2/10/20 分钟逐步收紧。
  • 误报是被低估的成本:DanRWilloughby/snifftest 十个布尔判定在 54 段干净文本上只误标 1 段(对照 Haiku 4.5 误标 37 段),说明逐条判定 + 各自阈值比一个笼统判定更准。
  • 阈值要靠标注集和基线来定,不能拍脑袋:bitnovus/jev-spam-eval 用零样本 Boolean 对照 TF-IDF 基线;用误报/漏报权衡曲线加 Brier 分数选阈值,并把"阈值 + 标注集"钉进回归测试。

下一章进入游戏与实时交互,看决策层怎么在不生成一行文本的前提下驱动角色与裁决对局。

📑 Jev 实战:用类型化决策重构软件

1 第 1 章:为什么需要决策层——从"生成一切"到"只做判定" 2 第 2 章:三种类型化输出——Choice、Score、Noul 与调用契约 3 第 3 章:十个决策形态——什么时候该把判断交给 Jev 4 第 4 章:Classification 与 Routing——把分诊做成一等公民 5 第 5 章:Detection 与 Scoring——二元判断与有序评分 6 第 6 章:Search 与 Retrieval——在候选集里找相关 7 第 7 章:Ranking 与 Verification——排序与校验 8 第 8 章:ML Feature Extraction 与 Structured Data Extraction——把文本变特征与结构化数据 9 第 9 章:检索与知识图谱——Search and retrieval / Graphs and knowledge graphs 10 第 10 章:模型路由与 LLM 护栏——Model routing / LLM guardrails 11 第 11 章:语义代码检查——Semantic code linting 12 第 12 章:特征提取与需求预测——Feature extraction / Demand forecasting 13 第 13 章:招聘与线索生成——Recruiting / Lead generation 14 第 14 章:客户支持——Customer support 15 第 15 章:理赔、金融犯罪与风险评估——Insurance claims / Financial crime / Risk assessment 16 第 16 章:法律与合规——Legal and compliance 17 第 17 章:电商市场与广告——E-commerce marketplaces / Advertising 18 第 18 章:内容审核与信任安全——Moderation and trust and safety 19 第 19 章:游戏——Gaming 20 第 20 章:实时应用——150 毫秒预算 21 第 21 章:科学发现——Scientific discovery 22 第 22 章:大数据上的 AI Map Reduce——为什么便宜 100 倍 23 第 23 章:通用验证——Universal Verification 24 第 24 章:Harness Engineering——把决策织进智能体骨架
← 返回本书大纲