自一致性:给审核系统加「不确定」出口
💡 一句话总结:用 Jev 给审核系统建立「敢说不知道」的决策出口 —— 重复体检找出会翻的题、top 概率设阈值、中间地带全部转人工。
「自一致性(self-consistency)」问的是一件事:同一条内容、同一套问题,问两次,答案一样吗? 官方为此做过一组实验(choices 版、nouls 版):拿一条两边不靠的社区帖子,用 8 道 Choice 审核题跑了 15 遍;再拿一张有争议的车险理赔单,用 14 道 Noul 题跑了 15 遍。
结论分两面。稳的题真的稳:这条帖子指向谁、严重度多高,15 次全一致。临界的题会翻:主要风险一道题在 Harassment 和 Violence 之间 11:4 分裂——而翻的时候,概率都低。所以官方的解法不是消灭摇摆,而是给系统加一个「不确定(uncertain)」出口:概率不够高的答案,别自动执行,转人工。 本课约 15 分钟,带你把这整套跑通。
学完你能做什么
- 搭一个带「不确定」出口的内容判定脚本:4 道 Choice 审核题,top 概率低于 0.60 自动转人工
- 写一个自一致性体检脚本:同一条内容换新 uid 重复问 15 次,统计哪些题会翻标签
- 给 Noul 是非判断加不确定带:概率落在 0.30–0.70 之间的一律转人工
- 读懂官方实验的边界:99.2% 是「一致性」不是「准确率」,25.8% 的转人工比例是拿来权衡的
为什么用 Jev,而不是普通 LLM 重复采样?
| 能力 | 普通 LLM 重复采样 | Jev |
|---|---|---|
| 概率从哪来 | 让模型把概率写成 JSON 再解析,0–1% 解析失败,还得剥代码围栏 | 每题自带 probabilities 分布,choice 就是最高概率选项 |
| 不确定出口 | 单选模式只有死标签,没有不确定性估计,全程不弃权 | top 概率低于 0.60 记 uncertain,纯代码逻辑,不加模型调用 |
| 一请求多题 | 8 道题挤一条 prompt,一题的答案成为另一题的上下文 | 对同一 state 并行、独立评估,加题几乎不增响应时间 |
| 出口后一致率 | 84.2%–94.2%(Haiku t=0 是 100%,但它从不弃权) | 99.2%(代价:25.8% 转人工) |
| 速度与成本 | 概率模式约 2–13 秒、$0.002–$0.041 一次 | 114ms、$0.000046 一次 |
| 概率可信度 | 训练目标是生成文本,概率没有校准保证 | RLCD 校准训练:标 0.2 的事约 20% 发生 |
数字都来自官方 cookbook 的同场对比(2026-09-11 采样),LLM 对照组含 claude-haiku-4-5、gpt-5.4-mini(t=0 与默认温度)、gpt-5.5、claude-opus-4-8。三点公平性说明:费用是实验时的历史价格假设,不是当前标价,现行费率见模型与价格;LLM 组延迟在 16 线程并发下测、Jev 组顺序测——即便如此差距仍是数量级的;校准描述的是一组预测的命中率(标 0.2 的事约 20% 发生),不是对单个答案的保证。
再说句实话:官方数据里 claude-haiku-4-5 在 t=0 下的概率波动(0.0012)比 Jev(0.0098)还小,15 次全一致。但它从不弃权——你没有任何信号知道哪次该转人工。Jev 的价值不是「更稳」,是「稳不稳看得见、可出口」。
你现在的困境
- 「同一条被举报的帖子,这轮审核放行、下轮审核删帖。用户截图来质问:你们的标准是掷骰子吗?」
- 「阈值定在 0.5,概率 0.49 拒赔、0.51 赔付——可这两个数本来说的就是一回事」
- 「想跑 3 次投票对齐一下,一条 8 问的 prompt 要 2 到 13 秒,高峰期队列直接堆死,还时不时 JSON 解析失败」
什么时候用这一招
当你需要:
- 审核系统对同一条内容每次给出同一个处置
- 边界内容有明确去处:转人工,而不是随机二选一
- 概率直接驱动代码分支,而不是再解析一遍模型吐的文本
而且不想:
- 写死关键词规则,被措辞变化轻松绕过
- 每条内容跑 N 次投票——又贵又慢,还引入新的解析故障点
- 在模型没把握时放它自动执行删帖、封号这类不可逆操作
🎒 开始前的准备
- 完成了 Noul 是非题:知道
noul是 0–1 概率、没有 confidence 字段、起点阈值 yes ≥ 0.7 / no ≤ 0.3 - 完成了 Confidence 置信度:知道三档用法——高置信自动执行、中等确认或标记、低置信转人工
- 跑通过第一个请求:
.venv里装好了typesafe-sdk,TYPESAFE_API_KEY已在环境变量里
跟我做
第 1 步:设计审核 rubric(4 道 Choice 题问清一条边界帖子)
为什么:出口加不加、加在哪儿,取决于每道题的答案形态。官方 8 道题的 rubric 里有稳的(target、severity,15 次全一致),也有会翻的(primary_risk、link_handling)。我们先挑 4 道有代表性的,全程 15 分钟跑通。
新建 moderation_uncertain.py,贴入下面的代码。帖子是官方 cookbook 原样的边界数据:语言激烈、一半冲人一半冲观点、带站外邀请链接、有一次前科、被用户举报 4 次——没有唯一正确答案,这正是要点:小措辞差异不该随机改变它的处置路径。
# moderation_uncertain.py —— 给审核系统加「不确定」出口
# 依赖:pip install typesafe-sdk(Python 3.10+),环境变量里已有 TYPESAFE_API_KEY
from typesafe_sdk import Choice, TypeSafeClient
# 自动执行阈值:最高概率 >= 0.60 才动手,否则转人工。
# 官方原注释:illustrative automatic-action threshold(示例策略,不是标定保证)
MIN_CHOICE_PROBABILITY = 0.60
# ── 1. 边界帖子:官方 cookbook 原样数据 ──
POST = {
"post_id": "P-88213",
"author": {
"user_id": "u/4471",
"account_age_days": 38,
"prior_strikes": 1,
"followers": 210,
},
"context": {
"surface": "public reply",
"in_reply_to": "another user defending a game patch",
"community": "r/gamedebates",
},
"content": {
"text": (
"Are you seriously this dense? Anyone who defends that patch is a complete clown and "
"should be embarrassed to even post here. People like you are what's ruining this "
"community and honestly you need to be dealt with. Come say it to my face, invite's "
"right here. Keep it up and I'll end your whole channel."
),
"has_link": True,
"link_domain": "discord.gg",
"language": "en",
},
"reports": {"user_reports": 4, "report_reasons": ["harassment", "spam", "threat"]},
}
# ── 2. 审核题:从官方 8 问里挑 4 道 ──
# category 是临界题,primary_risk 是必转人工题,target / severity 是「稳」的对照
QUESTIONS = {
"category": Choice(
instructions="What is the single most applicable content-policy category for this post?",
criteria={
"None": "No policy violation of any kind.",
"Harass": "Insults or demeans a person, with no threat of harm and no protected-class attack.",
"Hate": "Attacks a person or group over a protected characteristic (race, religion, gender, ...).",
"Violence": "Makes a credible threat of harm or incites violence against someone.",
"Spam": "Unsolicited promotion or link spam, with no personal attack.",
"Sexual": "Sexual or adult content.",
},
),
"primary_risk": Choice(
instructions="What is the primary moderation risk that should drive triage for this post?",
criteria={
"Harassment": "Personal attack or targeted abuse is the main risk.",
"Violence": "A threat of harm or intimidation is the main risk.",
"LinkAbuse": "External-link or off-platform coordination risk is the main risk.",
"AccountHistory": "Prior account history or repeat behavior is the main risk.",
"LowRisk": "No meaningful moderation risk is present.",
},
),
"target": Choice(
instructions="Who or what is the content primarily directed at?",
criteria={
"None": "Not directed at anyone in particular.",
"Person": "Directed at one specific individual.",
"Group": "Directed at a protected group or class.",
"Platform": "Directed at the community or platform itself, not a person.",
},
),
"severity": Choice(
instructions="What is the overall severity of this post?",
criteria={
"None": "No violation.",
"Low": "Rude or dismissive, but essentially harmless.",
"Medium": "Personal harassment with no clearly credible threat.",
"High": "Harassment together with a threat that could be read as credible.",
},
),
}
# ── 3. 客户端:自动读环境变量 TYPESAFE_API_KEY,默认走官方端点 ──
client = TypeSafeClient(model="jev-latest")instructions 和选项描述逐字来自官方 cookbook。每道题的选项互斥、覆盖主要情形——问题 id(category 这些)不会发给模型,完整问题都在 instructions 里,这是官方的 rubric 写法。
第 2 步:一次请求问完 4 道题,用 top 概率决定「执行还是转人工」
为什么:同一 state 的所有问题一次发出,Jev 对每道题并行、独立评估——加题几乎不增加响应时间,只是多花几 token。这是它和「一条 prompt 塞 8 问」的本质区别:一题的答案不会变成另一题的上下文。接着上面的代码写:
# ── 4. 出口判定:top 概率 >= 0.60 → 自动执行;否则 uncertain → 转人工 ──
# answer.choice 本身就是最高概率选项,answer.probabilities 是完整概率分布
def choice_decision_with_uncertainty(answer) -> str:
p_max = max(answer.probabilities.values())
return answer.choice if p_max >= MIN_CHOICE_PROBABILITY else "uncertain"
def run_once() -> None:
response = client.system_one(
state={"post": POST},
questions=QUESTIONS,
)
for key in QUESTIONS:
answer = response.answers[key]
p_max = max(answer.probabilities.values())
decision = choice_decision_with_uncertainty(answer)
if decision == "uncertain":
print(f"{key:<13} uncertain(p_max={p_max:.2f})→ 转人工")
# 换成你的转人工逻辑,比如 create_review_ticket(POST, key)
else:
print(f"{key:<13} {decision}(p_max={p_max:.2f})→ 自动执行")
# 换成你的执行逻辑,比如 enforce(POST, key, decision)
if __name__ == "__main__":
run_once()运行:
.venv/bin/python moderation_uncertain.py你应该看到(示例输出——标签走向与官方实测一致,概率数值每次会有小差异):
category Violence(p_max=0.63)→ 自动执行
primary_risk uncertain(p_max=0.55)→ 转人工
target Person(p_max=0.97)→ 自动执行
severity High(p_max=0.91)→ 自动执行target 和 severity 概率高、直接执行;primary_risk 概率不过 0.60,被出口接住——官方 15 次实测里,这道题的最高概率每次都低于 0.60。顺带一提:response.answers["category"] 和 response.choices["category"] 指向同一个答案对象,本页统一用 answers,和官方 cookbook 一致。
💡 这就是 Jev 的独有价值
出口判定用的是返回的 probabilities,不是 API 另外的 confidence 字段,也不需要多任何一次模型调用。普通 LLM 要做到同样的事,得先让模型把概率「说」成文本再解析,官方实验里 LLM 组的解析失败率 0–1%。加题不加请求、加出口不加调用——这两个「不加」都是类型化概率直接给的。并行提问的完整展开见并行提问与投机扇出课。
💡 中文内容注意
官方说明:英语是主要训练语言,包括中文在内的其他语言能用但准确度较低。中文审核场景上线前,先拿自己的真实数据跑一批,重点看 confidence 和概率分布,再定阈值。
- 我跑出了 4 行判定,其中至少一行是 uncertain(如果 4 行全是自动执行,把帖子换成你自己业务里更临界的样本再跑)
第 3 步:重复 15 次,做 Jev 自一致性体检
为什么:出口设在哪儿,不该拍脑袋。官方的做法是把同一条内容、同一套题原样重复问 15 次,看哪些题的标签会翻——翻的题要么给出口,要么改写。每次调用往 state 里放一个新的 uid(一次性随机值):帖子和题目一字不改,只改这个无关字段,让每次调用都是独立抽样。
新建 consistency_probe.py(复用上一个文件的定义——run_once 被 __main__ 保护着,import 不会再发请求):
# consistency_probe.py —— 自一致性体检:同一条帖子重复问 15 次
from collections import Counter
from secrets import token_hex
from moderation_uncertain import (
MIN_CHOICE_PROBABILITY,
POST,
QUESTIONS,
client,
)
NUM_SAMPLES = 15 # 官方实验的重复次数
raw_counts = {key: Counter() for key in QUESTIONS} # 每次调用的原始最高概率标签
decision_counts = {key: Counter() for key in QUESTIONS} # 加 0.60 出口后的应用决策
for sample_index in range(NUM_SAMPLES):
# 帖子与题目一字不改,只换 state 里的一次性 uid —— 让每次调用都是独立抽样
response = client.system_one(
state={"uid": f"{sample_index}:{token_hex(4)}", "post": POST},
questions=QUESTIONS,
)
for key in QUESTIONS:
answer = response.answers[key]
p_max = max(answer.probabilities.values())
raw_counts[key][answer.choice] += 1
decision = answer.choice if p_max >= MIN_CHOICE_PROBABILITY else "uncertain"
decision_counts[key][decision] += 1
print(f"requested: jev-latest, returned: {response.model}")
for key in QUESTIONS:
raw = dict(raw_counts[key])
decisions = dict(decision_counts[key])
status = "标签翻转!" if len(raw) > 1 else "标签稳定"
print(f"{key:<13} 原始标签 {raw} 应用决策 {decisions} {status}")运行:
.venv/bin/python consistency_probe.py你应该看到(一次示例运行;具体计数每次不同。官方 8 问版实测:primary_risk 在 Harassment 11 次 / Violence 4 次之间分裂,target 15 次全 Person、severity 15 次全 High,category 的应用决策在 Violence 和 uncertain 之间摆):
requested: jev-latest, returned: jev-1.14.0
category 原始标签 {'Violence': 15} 应用决策 {'Violence': 12, 'uncertain': 3} 标签稳定
primary_risk 原始标签 {'Harassment': 11, 'Violence': 4} 应用决策 {'uncertain': 15} 标签翻转!
target 原始标签 {'Person': 15} 应用决策 {'Person': 15} 标签稳定
severity 原始标签 {'High': 15} 应用决策 {'High': 15} 标签稳定三件事值得盯住:
- 原始标签翻不翻:
primary_risk的原始标签在两个标签间分裂——不加出口,同一条帖子会路由到不同队列,这就是审核系统「掷骰子」的根源。 - 出口是否接住了它:加了 0.60 出口后,这道题 15 次全部变成 uncertain——不再是两个具体标签打架,而是统一去人工。
- 返回的模型版本:官方实验采样于 2026-09-11,当时
jev-latest解析到jev-1.13.0;今天别名已指向新版本,你看到的计数会不同——这不是翻车,是常态。response.model让每次答案有版本可查;调过阈值的系统,官方建议锁定版本 ID 而不是用别名。
uid 是体检装置,不是 API 参数——它就是 state 里的一个普通字段。官方也提醒:这套设置区分不了「模型对这个无关字段敏感」和「相同请求本身的固有波动」,所以别把 uid 带进生产调用。
第 4 步:用体检结果定出口策略
为什么:体检不是为了得一个分数,是为了把题分成两类——稳的题直接自动执行省人力,会翻的题才需要出口或改写。官方 8 问版全套数据可以直接参考:
| 指标 | 数值 | 含义 |
|---|---|---|
| 原始一致率 | 90.8% | 不加出口,每题最高频标签占比的平均 |
| 出口后一致率 | 99.2% | top 概率低于 0.60 记 uncertain 后的一致率 |
| 转人工占比 | 25.8% | 8 题 × 15 次里,被出口接住的比例 |
| 自动执行占比 | 74.2% | 概率过 0.60、直接行动的比例 |
关键读法:出口把「两个具体标签打架」变成了「统一转人工」。官方实测里,加了出口后没有任何一道题在不同重复里给出两个不同的具体标签(conflicts = 0)——原始标签会翻的题全被出口接住了。代价是四分之一的答案进人工队列,这个比例值不值,要拿你自己业务里「错误自动执行的代价」和「人工看一条的代价」来换算。
你应该看到:自己的 rubric 被分成了两组——原始标签 15 次不变的题(直接自动执行),和会翻或被反复接住的题(加出口,或回头改写问题)。
- 我能说出自己 rubric 里哪几道题需要出口、哪几道题可以直接自动执行
第 5 步:给 Noul 是非题加不确定带(0.30–0.70 转人工)
为什么:是非题没有多选项分布,0.5 两侧就是两个动作。官方 noul cookbook 的理赔例子里,「损失是否在保」这道题 15 次的概率在 0.43–0.53 之间摆——0.49 拒赔、0.51 赔付,动作完全相反,而这俩本是一回事。解法是把阈值变成一条不确定带:低于 0.30 算 no,0.30 到 0.70(含两端)算 uncertain 转人工,高于 0.70 才算 yes。 这和 Noul 课的起点阈值(yes ≥ 0.7、no ≤ 0.3)是同一套。
新建 noul_uncertain.py:
# noul_uncertain.py —— Noul 版出口:0.30–0.70 闭区间 → uncertain 转人工
from typesafe_sdk import Noul
from moderation_uncertain import POST, client
# 两个边界值逐字来自官方 noul cookbook
NOUL_UNCERTAINTY_LOW = 0.30
NOUL_UNCERTAINTY_HIGH = 0.70
def noul_decision_with_uncertainty(probability: float) -> str:
"""把 noul 概率映射成三段决策;0.30–0.70(含两端)算 uncertain。"""
if probability < NOUL_UNCERTAINTY_LOW:
return "no"
if probability > NOUL_UNCERTAINTY_HIGH:
return "yes"
return "uncertain"
# 问句方向要正:yes = 命中你要检查的事
NOUL_QUESTIONS = {
"threat": Noul(
instructions="Does the post make a credible threat of harm against a person?"
),
"targeted_insult": Noul(
instructions="Does the post insult or demean a specific person?"
),
}
response = client.system_one(
state={"post": POST},
questions=NOUL_QUESTIONS,
)
for key in NOUL_QUESTIONS:
probability = response.answers[key].noul
decision = noul_decision_with_uncertainty(probability)
if decision == "uncertain":
# 转人工是纯应用层逻辑:不加问题、不加第二次 API 调用(官方原话)
print(f"{key:<15} noul={probability:.2f} → uncertain,转人工")
else:
print(f"{key:<15} noul={probability:.2f} → {decision},自动执行")运行:
.venv/bin/python noul_uncertain.py你应该看到(示例输出):
threat noul=0.48 → uncertain,转人工
targeted_insult noul=0.93 → yes,自动执行threat 的 0.48 正是这条帖子的写照——威胁措辞从来没说干净过,模型不站边,出口把它送进人工队列。官方的两点提醒照抄给你:一是这条带仍有边缘,0.71 和 0.69 会在 yes 和 uncertain 之间摆,加了带不等于模型变确定;二是 0.30/0.70 同样是示例值,生产边界要用你自己的标注数据、结合「错误决策的代价」和「人工审核的代价」来定。
📋 魔法 Prompt
本页的「Prompt」是给 API 的问题模板——抄走、替换【】占位符(带 ? 的是选填)就能用。
📝 内容审核 rubric(Choice 版)
预期效果:把一个社区/平台的审核标准拆成互斥的 Choice 题,一次请求并行评估,每题自带概率,直接接 0.60 出口。
from typesafe_sdk import Choice
# 必填项:【】里换成你平台的真实规则
MODERATION_QUESTIONS = {
"category": Choice(
instructions="【一句话问题:这条内容最适用哪条平台政策?】",
criteria={
"【选项 A】": "【什么情形算 A——写成可对照的情境,不写「轻微」「严重」这类程度词】",
"【选项 B】": "【什么情形算 B】",
"【选项 C】": "【什么情形算 C】",
"other": "【以上都不是、但确实需要有人看的情形?】",
},
),
"action": Choice(
instructions="【这条内容该做什么处置?】",
criteria={
"Allow": "【放行,不动作】",
"Warn": "【放行但挂警示标签】",
"Remove": "【删除内容,不动账号】",
"Escalate": "【不自动处置,等人工判断】",
},
),
"severity": Choice(
instructions="【整体严重程度如何?每级写成情境】",
criteria={
"None": "【没有违规的情形】",
"Low": "【粗鲁但基本无害的情形】",
"Medium": "【人身攻击、但无明确可信威胁的情形】",
"High": "【攻击伴随可信威胁的情形】",
},
),
# 选填项:链接处置、目标对象、队列路由——官方 8 问都是现成参考?
}
# 输出规范:response.answers["category"].choice / .probabilities / .confidence
# 约束条件:
# ✅ 一道题只测一个维度;选项互斥、并集覆盖常见情形
# ✅ 每个选项描述写成「可对照的情境」,和 state 里的内容对得上
# ❌ 不要把「uncertain」做成一个选项——出口是代码层的,不是模型标签
# ❌ 不要在 instructions 里塞数字量表——测程度是 Score 的活✅ 是非质检清单(Noul 版)
预期效果:把审核红线拆成一串是非题,每题拿到 0–1 概率;0.30–0.70 带内全部转人工,两侧自动执行。
from typesafe_sdk import Noul, NoulCriteria
# 必填项:每句都问「是否命中要查的事」,yes 就是命中
NOUL_QUESTIONS = {
"threat": Noul(
instructions="【内容是否对具体的人发出可信的伤害威胁?】"
),
"doxxing": Noul(
instructions="【内容是否泄露他人未公开的个人信息(住址、电话、证件号)?】"
),
"spam": Noul(
instructions="【内容是否为未经请求的推广或引流?】",
# 选填项:边界模糊的题加 criteria,把 yes/no 的定义钉死?
# criteria=NoulCriteria(
# true="【什么算命中】",
# false="【什么算不命中】",
# ),
),
# 选填项:往清单里加题不影响请求结构,Jev 对每题并行独立评估?
}
# 输出规范:response.answers["threat"].noul → 0–1 概率,没有 confidence
# 约束条件:
# ✅ 问句方向要正:yes = 命中要检查的事
# ✅ 「多严重」这类程度问题交给 Score 题,别硬塞给 Noul
# ❌ 不要拿 0.5 当硬阈值——0.49 和 0.51 会触发相反动作🔧 自一致性体检脚本
预期效果:对一批边界样本重复跑同一 rubric,统计每道题的标签翻转情况——上线前找出需要出口或需要改写的题。
from collections import Counter
from secrets import token_hex
# 必填项:【】里换成你的体检参数
NUM_SAMPLES = 【重复次数,官方用 15】
SAMPLES = 【你的边界样本列表:挑真实业务里最难判的 10–30 条】
flip_report = []
for sample_index, sample in enumerate(SAMPLES):
raw_counts = {key: Counter() for key in QUESTIONS} # QUESTIONS 用上面两个模板之一
for i in range(NUM_SAMPLES):
response = client.system_one(
model="【jev-latest,或锁定的版本 ID】",
state={"uid": f"{i}:{token_hex(4)}", "post": sample},
questions=QUESTIONS,
)
for key in QUESTIONS:
raw_counts[key][response.answers[key].choice] += 1
flip_report.append({
"sample_index": sample_index,
"labels": {key: dict(counts) for key, counts in raw_counts.items()},
})
# 输出规范:每条样本 × 每道题的标签计数;出现超过 1 个标签 = 该题对该样本会翻
# 约束条件:
# ✅ 每次调用换新 uid,样本与题目一字不改
# ✅ 体检结果只用于「改问题、定出口」,别当准确率宣传
# ❌ 别把 uid 带进生产调用——它是实验装置检查点 ✅
moderation_uncertain.py跑出 4 行判定,概率不过 0.60 的题被标成 uncertainconsistency_probe.py里我每次调用都换了新 uid,帖子与题目一字未改- 我知道 0.60 和 0.30/0.70 都是官方给的示例阈值(illustrative),生产阈值要用标注数据自己定
- 我知道官方实验测的是「稳定性」不是「正确性」——99.2% 是一致率,不是准确率
- 转人工队列里能看到原始概率,不是只有一个 uncertain 标签
踩坑提醒
❌ 拿 temperature 0 当免死金牌
官方数据:t=0 的 LLM 一样会摆——gpt-5.4-mini t=0 的原始一致率 99.2%,但加出口后的策略一致率掉到 87.5%(12.5% 的答案被标 uncertain,决策在标签和 uncertain 之间晃);claude-haiku-4-5 默认温度下原始一致率只有 87.5%。更扎心的是那个反例:Haiku t=0 的概率波动最小、15 次全一致,但官方特意在图表里标注——「100% 可重复不等于正确,这个实验不测准确率」。稳 ≠ 对。
❌ 把 uncertain 当成模型输出的第四个选项
uncertain 是你的代码根据概率做的分流,不是模型返回的标签。两个典型错误:一是把它做成 Choice 的一个选项、让模型「选 uncertain」——官方做法是在代码里比对 top 概率;二是以为加了出口模型就变确定了——概率贴着 0.60 时,同一道题还是会在具体标签和 uncertain 之间摆(官方原话:这个策略不会让模型变成确定性的)。出口换来的不是确定性,是「不确定的答案有了统一去处」。
❌ 到处设 confidence 阈值
官方 agent-skill 的常见问题清单里有这条:如果你只关心「选最优」,直接拿最高概率的选项就行,不必到处设 confidence 阈值;有统计算法需求时用 probabilities。本页的出口刻意用 top 概率而不是 confidence 字段——它读起来最直白(「第一名有多大概率」),而且阈值可以按选项数分题设。两种信号怎么选,见 Confidence 置信度。
❌ 拿示例阈值直接上生产
0.60 和 0.30/0.70 是官方 cookbook 的示例策略:不是标定值,也不是为了让实验数字好看而调的。官方建议用你自己的标注样本、在「错误动作的代价」和「人工审核的代价」之间找平衡。另外别名会随新版本漂移——官方原话是调过阈值的请求应锁定版本 ID;response.model 字段让日志里每个答案都有版本可查。更多 Jev 的失败模式(字面理解、计数不可靠、日期比较)见 Jev 1.13 踩坑手册。
进阶技巧(可选)
只给会翻的题加出口。 体检跑完你会得到两类题:15 次全一致的(target、severity)和会翻的(primary_risk)。前者直接自动执行省人力,后者才需要出口。给所有题无差别套出口,会白白推高 25.8% 这样的转人工占比——体检结果就是让你把人工预算花在真正会翻的题上。
换个信号微调出口。 除 top 概率外,官方还列了替代指标:第一名对第二名的概率比(p_max / p_second,适合「两强相争」的题),以及 Noul 的 |2p − 1|(把是非题概率拉到和 Choice confidence 同一个 0–1 尺度,统一你的门控代码)。公式和取舍见 Confidence 置信度。
免代码先试。 控制台 Playground(https://console.typesafe.ai/playground)里贴 state、加问题就能体验;官方 cookbook 页面上还有这条帖子和那张理赔单做好的分享链接,点开就是同一套 state + 问题。
本课小结
✅ Jev 自一致性体检:同一条内容、同一套题,换新 uid 重复问 15 次,统计哪些题会翻标签。
✅ Choice 出口:top 概率 ≥ 0.60 自动执行,低于 0.60 记 uncertain 转人工——用返回的 probabilities,不用 confidence 字段,不加模型调用。
✅ Noul 出口:低于 0.30 算 no、0.30–0.70(含两端)转人工、高于 0.70 才算 yes。
✅ 官方实测:出口把 Jev 的决策一致率从 90.8% 提到 99.2%,代价是 25.8% 的答案转人工;实验测的是稳定性,不是正确性。
关键配置字段回顾:
| 字段 | 本页的值 | 说明 |
|---|---|---|
MIN_CHOICE_PROBABILITY | 0.60 | 示例阈值;恰好 0.60 时取标签 |
NOUL_UNCERTAINTY_LOW / HIGH | 0.30 / 0.70 | 闭区间,两端都算 uncertain |
NUM_SAMPLES | 15 | 体检重复次数,官方实验值 |
uid | f"{i}:{token_hex(4)}" | state 里的一次性字段,仅实验用 |
| 判定输入 | answer.choice / answer.probabilities / answer.noul | choice 即最高概率选项 |
相关教程:
- 75 类行业分类:置信度不够就上报大类——同款「不够确定就升级」的分诊思路,用在行业分类上
- 直播聊天过滤:辱骂、剧透、垃圾消息一次拦下——每条消息 1 请求 3 问题,代码规则决定展示
- 智能路由:意图分类加置信度门控——置信度门控的完整工作流展开
下一课预告
审核对象是 LLM 应用本身时,安检要做得更全套:LLM 护栏:输入输出全安检。
你会学到:
- 用一次 TypeSafe 请求,给每条进出 LLM 应用的消息做全套安检
- Noul 问题组拿到每个风险的概率,Score 题评估照做的危害程度
- 按你设的阈值把消息分成:放行、转人工、直接拦截、转支持团队
- 和本页同款思路——概率 + 阈值分流,只是这次拦的是越狱和有害请求