为什么”模型说得很确定”仍然不可信
大模型最危险的输出,往往不是语气犹豫的答案,而是结构完整、措辞流畅、细节丰富,却在关键事实处出错的答案。传统系统通常只检查请求是否成功、输出是否完整、内容是否合规,却没有回答一个更直接的问题:这条答案是否值得自动展示给用户?
这需要把生成系统从”有答案就返回”改造成选择性回答(Selective Prediction):高可信答案直接返回,中等可信答案进入补充证据或人工复核,低可信答案明确拒答或降级。
困难在于,大模型并不会天然给出可靠的”正确概率”。即使 API 能返回 Token Logprobs,它表达的也是”在当前上下文和解码条件下,下一个 Token 被模型选择的可能性”,而不是”整条答案在现实世界中正确的概率”。因此,生产系统需要经过三层转换:
- 从 Token 级 Logprobs 提取稳定的回答级特征;
- 用独立标注数据把原始分数校准为可解释的置信度;
- 根据业务风险选择拒答阈值,并持续监控覆盖率与错误率。
核心原理:从 Token 概率到回答门禁
Token Logprob 到底表示什么
对生成序列 \(y_1, y_2, \dots, y_T\),模型给出的 Token Logprob 通常可写为:
\[ \log p(y_t \mid x, y_1, \dots, y_{t-1}) \]
其中 \(x\) 是输入上下文。一个常见的回答级分数是长度归一化后的平均 Logprob:
\[ \text{score_mean} = \frac{1}{T} \sum_{t=1}^{T} \log p(y_t \mid x, y_{<t}) \]
\[ \text{confidence_proxy} = \exp(\text{score_mean}) \]
长度归一化可以缓解长答案累加 Logprob 后天然更低的问题,但它仍只是置信度代理信号,不能直接解释为正确率。
生产中建议同时保留多种特征:
| 特征 | 用途 |
|---|---|
| 平均 Logprob | 反映整段生成的总体稳定程度 |
| 低分位数 Logprob(如 P10) | 捕捉少数高风险 Token |
| Top-1 与 Top-2 Margin | 判断关键位置是否存在强竞争候选 |
| Token Entropy | 衡量候选分布是否分散 |
| 关键片段分数 | 只统计日期、金额、实体、选项标签等决定正确性的 Token |
| 终止状态与输出长度 | 排除截断、工具调用中断等非知识性低分 |
对于分类、选项题和受约束抽取,最好只对最终标签或关键字段计算分数。对于开放式问答,单纯平均所有 Token 会被礼貌用语、标点和固定模板稀释,应把实体、数字、引用和结论句单独建模。
为什么原始分数必须校准
假设某类回答的原始置信度为 0.8,只有当长期统计中这类回答大约有 80% 正确,它才称得上”校准良好”。如果 0.8 分的答案实际只有 55% 正确,系统就是明显过度自信。
校准的输入不是训练集,而是一份独立的校准集:
原始特征 → 是否正确的人工/规则标签 → 校准模型 → 校准后概率
常用校准方法对比:
| 方法 | 适用场景 | 特点 |
|---|---|---|
| Logistic / Platt Scaling | 单调关系较明确 | 参数少、稳定 |
| Isotonic Regression | 样本充足、关系非线性 | 不假设具体函数形状 |
| Temperature Scaling | 自托管模型(有完整 Logits) | 简洁高效 |
| 分桶校准 | 快速原型 | 对样本量和桶边界敏感 |
校准模型必须与以下版本一起管理:
model_id + model_revision + prompt_version + decoding_config + task_domain + locale
只要模型版本、系统提示词、温度、工具链或输出格式发生明显变化,就不能默认旧校准器继续有效。
门禁真正关心的是 Risk-Coverage
普通准确率只说明”全部回答中有多少正确”,却不能评价拒答策略。置信度门禁需要同时看两个量:
- Coverage:有多少请求被系统接受并自动回答;
- Risk:被接受回答中的错误比例。
给定阈值 \(\tau\):
\[ \text{Coverage}(\tau) = \frac{\text{accepted_requests}}{\text{all_requests}} \]
\[ \text{Risk}(\tau) = \frac{\text{wrong_accepted_answers}}{\text{accepted_requests}} \]
阈值越高,通常风险下降,但覆盖率也会降低。上线目标不应该是”置信度越高越好”,而是找到业务可接受的风险—覆盖率平衡点。
对于高风险场景,不应只使用校准集上的经验错误率。更稳妥的方法是计算错误率的置信上界,只在上界仍低于目标风险时开放自动回答。
工程落地:建立可版本化的置信度服务
第一步:构建真实的校准数据集
校准数据至少应包含:
request_id: req_20260711_001
domain: product_support
locale: zh-CN
model_id: model-x
prompt_version: support-v17
decoding_config: temperature: 0
answer: "..."
correct: true
error_type: null
raw_features:
mean_logprob: -0.42
p10_logprob: -1.31
mean_margin: 1.08
answer_tokens: 86
数据集需要覆盖:
- 常见问题与长尾问题;
- 已知答案与未知答案;
- 不同语言、文体和输入长度;
- 容易混淆的实体、日期、版本和数字;
- 线上真实失败样本,而不只是合成题库。
推荐按时间切分训练、校准和测试数据,避免同一问题的近似改写同时出现在多个集合中。
第二步:提取稳定的回答级分数
下面是一个简化示例,将 Token Logprobs 转换为回答级特征:
from __future__ import annotations
import math
from dataclasses import dataclass
from statistics import mean
@dataclass(frozen=True)
class ConfidenceFeatures:
mean_logprob: float
p10_logprob: float
geometric_mean_probability: float
token_count: int
def build_features(token_logprobs: list[float]) -> ConfidenceFeatures:
if not token_logprobs:
raise ValueError("token_logprobs must not be empty")
ordered = sorted(token_logprobs)
p10_index = max(0, math.ceil(len(ordered) * 0.10) - 1)
avg = mean(token_logprobs)
return ConfidenceFeatures(
mean_logprob=avg,
p10_logprob=ordered[p10_index],
geometric_mean_probability=math.exp(avg),
token_count=len(token_logprobs),
)
实际系统还应处理:
- 供应商没有返回 Logprobs;
- 流式响应只返回部分 Token 分数;
- 特殊 Token、空白和标点是否纳入统计;
- 工具调用、拒绝回答和内容过滤事件;
- Top Logprobs 数量在不同模型间不一致。
第三步:训练校准器,而不是直接设阈值
下面使用 Isotonic Regression 展示基本流程:
from __future__ import annotations
import numpy as np
from sklearn.isotonic import IsotonicRegression
def fit_calibrator(
raw_scores: list[float],
correctness: list[int],
) -> IsotonicRegression:
if len(raw_scores) != len(correctness):
raise ValueError("raw_scores and correctness must have equal length")
if len(raw_scores) < 200:
raise ValueError("calibration sample is too small for this policy")
model = IsotonicRegression(
y_min=0.0, y_max=1.0, out_of_bounds="clip",
)
model.fit(np.asarray(raw_scores), np.asarray(correctness))
return model
def calibrated_confidence(
calibrator: IsotonicRegression, raw_score: float,
) -> float:
return float(calibrator.predict([raw_score])[0])
代码中的 200 只是示例门槛,不应作为通用经验值。样本量应根据任务分层数量、错误率和目标置信区间确定。
第四步:把门禁设计成三态,而不是二态
生产系统建议输出三种决策:
| 置信度范围 | 决策 |
|---|---|
| confidence ≥ answer_threshold | 自动回答 |
| review_threshold ≤ confidence < answer_threshold | 补充证据、规则核验或人工复核 |
| confidence < review_threshold | 拒答或返回安全说明 |
三态策略比简单的”回答/拒答”更实用。中间区域可以执行成本可控的二次验证,例如检查引用是否存在、核对数据库字段、验证数值格式或请求人工确认。
一个可审计的门禁结果应包含:
{
"decision": "review",
"raw_score": -0.73,
"calibrated_confidence": 0.64,
"answer_threshold": 0.82,
"review_threshold": 0.55,
"calibrator_version": "support-zh-v4",
"model_revision": "model-x-2026-07-01",
"reason_codes": ["LOW_ENTITY_MARGIN", "DOMAIN_SHIFT"]
}
第五步:按业务损失选择阈值
不同场景的错误成本完全不同:
| 场景 | 策略 |
|---|---|
| 营销文案 | 可接受较高覆盖率和较低门槛 |
| 内部知识助手 | 对版本号、金额和制度条款提高门槛 |
| 医疗、法律、财务等高风险建议 | 不能仅依赖模型置信度自动放行 |
阈值评审至少应查看:
- Risk-Coverage 曲线;
- 可靠性图(Reliability Diagram);
- Brier Score;
- Expected Calibration Error(ECE);
- Accepted Set Error Rate;
- 人工复核量与拒答率;
- 不同领域、语言和租户的分层结果。
其中 ECE 适合观察整体校准情况,但上线门禁更应关注低目标错误率下的 Risk-Coverage 表现。仅用平均 ECE 可能掩盖高置信区间中的少量严重错误。
供应商与自托管模型的接入差异
部分托管 API 可以返回所选 Token 及其 Top 候选的 Logprobs。例如 OpenAI Chat Completions 响应可包含 Token、Logprob 和 Top Logprobs;Gemini 的生成配置提供 responseLogprobs 和 Top Logprobs 数量,并在结果中返回逐步候选与 Log Probability Sum。
自托管模型通常能拿到更完整的 Logits。Hugging Face Transformers 的 compute_transition_scores 可以根据生成分数计算已选 Token 的 Transition Scores,适合构建自己的回答级特征。
但不能把不同平台的原始分数直接比较,因为以下因素都会改变分布:
- Tokenizer 和词表;
- 对齐训练与模型家族;
- Temperature、Top-p、Top-k;
- 是否返回归一化 Logits;
- Beam Search 或 Sampling;
- 系统提示词和输出模板。
正确做法是为每个明确的运行配置单独校准,或者训练一个把模型标识与任务元数据纳入输入的统一校准模型。
适用场景
置信度门禁更适合以下任务:
- 有客观答案的知识问答;
- 分类、意图识别和选项判断;
- 实体、金额、日期、版本号等结构化抽取;
- 带可验证引用的企业知识助手;
- 可以定义”正确/错误”标签的客服和运维问答。
它不适合单独决定以下任务是否成功:
- 创意写作、开放讨论和主观建议;
- 缺乏明确正确标签的复杂推理;
- 需要外部事实验证但没有证据链的回答;
- 高风险自动决策。
在这些场景中,Logprobs 最多只能作为一个辅助信号。
常见误区
误区一:把平均 Token 概率当成答案正确率
平均 Token 概率衡量的是生成顺滑程度。一个常见但错误的事实,也可能由模型以很高概率生成。
误区二:只看最小 Logprob
最低分 Token 经常是罕见名字、数字拆词、标点或格式符号。直接取最小值会产生大量误报,应结合 Token 类型和关键字段位置。
误区三:所有模型共用一个阈值
模型、Tokenizer、提示词和采样参数变化后,分数分布会漂移。统一使用 0.8 之类的阈值通常没有统计意义。
误区四:用模型自报”我有 95% 把握”直接放行
语言化置信度在某些任务中可以作为有用信号,但仍需在本业务数据上校准。它不是天然可信的概率。
误区五:只监控拒答率,不监控被接受答案的错误率
低拒答率可能只是门槛过低。真正需要守住的是 Accepted Set 的风险,而不是让系统尽量多回答。
误区六:校准集与测试集混用
在同一批数据上选择阈值并报告结果,会显著高估效果。阈值选择、最终评估和线上灰度必须使用分离的数据。
上线检查清单
- 已确认目标模型与接口能够稳定返回 Logprobs;
- 已定义无 Logprobs、响应截断和工具调用场景的降级策略;
- 已建立独立的训练集、校准集和最终测试集;
- 已按领域、语言、租户和问题类型检查分层校准;
- 已对数字、实体、引用和选项标签构建关键 Token 特征;
- 已绘制 Reliability Diagram 与 Risk-Coverage 曲线;
- 阈值由目标风险、覆盖率和置信区间共同确定;
- 校准器与模型、提示词、解码参数建立版本绑定;
- 中间置信区间已配置补证或人工复核流程;
- 日志中记录原始分数、校准分数、阈值、版本和原因码;
- 已设置分数分布漂移、覆盖率突变和错误率升高告警;
- 模型升级或 Prompt 发布后会自动触发重新校准与回放测试。
参考资料
- OpenAI API Reference — Chat Completions Logprobs
- Google Gemini API — GenerateContent / LogprobsResult
- Hugging Face Transformers — compute_transition_scores
- Jiang et al., How Can We Know When Language Models Know? On the Calibration of Language Models for Question Answering — https://arxiv.org/abs/2012.00955
- Tian et al., Just Ask for Calibration: Strategies for Eliciting Calibrated Confidence Scores from Language Models Fine-Tuned with Human Feedback — https://arxiv.org/abs/2305.14975
- Phillips et al., Entropy Alone is Insufficient for Safe Selective Prediction in LLMs — https://arxiv.org/abs/2603.21172
- Dong and Shinnou, Uncertainty-Aware Abstention in Large Language Models with Provable Alignment Guarantees — https://arxiv.org/abs/2607.04430