文章

LLM 置信度门禁生产实战:用 Logprobs、校准曲线与拒答阈值控制低可信回答

本文深入讲解大模型回答置信度的工程实践,从 Token Logprobs 特征提取到校准曲线与风险覆盖率分析,帮助团队建立可回放、可灰度、可审计的拒答门禁体系,兼顾覆盖率与低可信输出拦截。

为什么”模型说得很确定”仍然不可信

大模型最危险的输出,往往不是语气犹豫的答案,而是结构完整、措辞流畅、细节丰富,却在关键事实处出错的答案。传统系统通常只检查请求是否成功、输出是否完整、内容是否合规,却没有回答一个更直接的问题:这条答案是否值得自动展示给用户?

这需要把生成系统从”有答案就返回”改造成选择性回答(Selective Prediction):高可信答案直接返回,中等可信答案进入补充证据或人工复核,低可信答案明确拒答或降级。

困难在于,大模型并不会天然给出可靠的”正确概率”。即使 API 能返回 Token Logprobs,它表达的也是”在当前上下文和解码条件下,下一个 Token 被模型选择的可能性”,而不是”整条答案在现实世界中正确的概率”。因此,生产系统需要经过三层转换:

  1. 从 Token 级 Logprobs 提取稳定的回答级特征;
  2. 用独立标注数据把原始分数校准为可解释的置信度;
  3. 根据业务风险选择拒答阈值,并持续监控覆盖率与错误率。

核心原理:从 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 发布后会自动触发重新校准与回放测试。

参考资料

  1. OpenAI API Reference — Chat Completions Logprobs
  2. Google Gemini API — GenerateContent / LogprobsResult
  3. Hugging Face Transformers — compute_transition_scores
  4. Jiang et al., How Can We Know When Language Models Know? On the Calibration of Language Models for Question Answeringhttps://arxiv.org/abs/2012.00955
  5. Tian et al., Just Ask for Calibration: Strategies for Eliciting Calibrated Confidence Scores from Language Models Fine-Tuned with Human Feedbackhttps://arxiv.org/abs/2305.14975
  6. Phillips et al., Entropy Alone is Insufficient for Safe Selective Prediction in LLMshttps://arxiv.org/abs/2603.21172
  7. Dong and Shinnou, Uncertainty-Aware Abstention in Large Language Models with Provable Alignment Guaranteeshttps://arxiv.org/abs/2607.04430

常见问题

Token Logprob 能直接当作答案正确率吗?
不能。Logprob 表示模型在当前上下文中选择某个 Token 的相对可能性,不等于事实正确率,必须经过任务级标注数据校准后才能用于门禁。
置信度阈值应该如何确定?
应在独立校准集上绘制 Risk-Coverage 曲线,根据可接受错误率、最低覆盖率和置信区间选择阈值,而不是拍脑袋设定固定分数。
所有大模型 API 都能返回 Logprobs 吗?
不能。不同供应商、模型和接口的支持范围不同,生产系统需要定义无 Logprobs 时的降级策略,并避免把不同模型的原始分数直接混用。