代码持有阈值,模型只给概率
如果把几百个真实项目放在一起看,最稳定的一条共同点是:
模型只负责回答「有多可能」,代码负责回答「够了没有」。
这不是风格偏好。它是从模型自报的置信度不可信这个事实里推出来的唯一稳妥做法 —— 而这一点在置信度到底可不可信那篇里有独立的实测证据。
具体长什么样
Choice 与 Score 返回答案时附带一个 confidence,Noul 直接返回 0 到 1 的概率。
接口本身不替你做任何判断,阈值是你的。真实项目里这句话落实成了很具体的形态:
- 一个跑在 Cloudflare Worker 上的脏话过滤器,阈值、
max()的聚合策略、以及 对外的 OpenAPI schema 全都写在 Worker 代码里,模型只是其中一个被调用的函数。 - 一个浏览器扩展用它判断评论是否要遮起来,扩展自己钉着 0.85 / 0.7 / 0.5 三档, 并且检查失败时保持遮住(宁可多遮,不可漏遮)—— 这也是阈值策略的一部分。
- 一个工单告警分流器把「要 page」定义成
P(SEV1) + P(SEV2) ≥ 0.80, 这个 0.80 写在它自己的配置里,不在模型里。
三者的共同点是同一个:换模型时阈值不动,换阈值时模型不动。 把阈值交给模型去「决定」的话,这两件事就绑在一起了 —— 而你要调阈值的时候, 会发现自己在改一个提示词。
为什么这条原则这么重要
因为它把两种失败分开了:
- 模型回答得不好 —— 去改问题、改 state、换模型。
- 阈值定得不好 —— 去改代码里那个数。
混在一起之后,一个「准确率不够」的问题既可能是模型的问题也可能是阈值的问题, 而你没有任何办法把它们分开。上一条的告警分流器之所以敢把 0.80 直接写进配置, 正是因为那个数可以被单独拿出来复盘、单独拿出来调。
反例:置信度不总是它看起来的那个东西
这条原则有一个必须知道的前提:你拿到的那个数,未必是校准过的概率。
有独立复算发现,Score 题返回的 confidence 经常正好等于分数的小数部分,
而且这类输出高度集中(在 121 个这样的输出里)。这不是「模型很自信」,
更像这个字段是从分数本身推导出来的一个值。
区别很大:
- 如果它是校准过的概率,那么 0.85 可以用在「85% 的情况下我会是对的」这种推理里。
- 如果它是分数的函数,那么 0.85 只表示「这一题得了高分」,和「我有多大概率对」 没有关系 —— 拿它去定阈值,是在给一个没有概率含义的数设门槛。
同样地,有一篇独立的测量报告直接去核对模型自报的字段与真值是否相符, 结论是那些字段的取值远没有描述里写的那么细(详见公开质疑)。
怎么办
实践里的做法可以归纳成三步,而且顺序不能反:
- 先用阈值做事,别信它的含义。 0.8 这个数是不是「80% 正确」不重要 —— 重要的是在你的数据上,0.8 以上的那些是不是明显更准。这是可以用留出集验的。
- 用你自己的标注拟合阈值。 有工具专门做这件事:拿你自己的标注数据, 为每个问题拟合出「达到目标准确率所需的阈值」,用留出集验证, 并且当模型更新破坏了已锁定的阈值时让 CI 失败。
- 把阈值锁进代码,并给它一个复核日期。 阈值会随着模型版本过期 —— 和价格一样,是要有人定期回头看的东西,不是一次定终身。
一句话
模型的可信度应该体现在你的阈值上,而不是体现在它的自报数字上。 你可以在自己的数据上度量前者,没法度量后者。