为 RAG 系统设计自适应置信度评分 —— 不用固定阈值,让数据说话

问题从哪里来

RAG 系统面临一个实际问题:它给出的回答到底可不可信?

三个问题共同决定回答质量:

  • 检索到相关内容了吗?
  • 回答有没有忠实引用文档?
  • 问答之间语义匹配吗?

全部交给用户判断,系统就没起到」筛选」作用。

但拍脑袋定固定阈值 —— 比如相似度大于 0.7 就算高置信 —— 今天的 0.7 和明天的 0.7 可能是两回事。不同知识库、不同检索模式、不同嵌入模型,分数分布天然不同,固定阈值迟早失效。

OpsMind 是面向 IT 运维的智能问答系统,需要对自己的回答有信心判断:

  • 高置信回答放心展示。
  • 低置信回答提示风险,甚至流转人工。

判断出错 —— 误把胡扯当权威,或频繁把正常回答标为不确定 —— 系统就失去信任。

真正卡住的是哪一步

把置信度问题拆开,本质有两个难题。

第一个难题:在线评分的复杂度控制。

准确判断置信度,需要多个维度的信号 —— 检索匹配度、原文对齐度、问答语义一致性。

如果每个维度都做复杂归一化、缩放、校准,在线接口延迟就保不住。用户问一个问题等 3 秒,系统再花 2 秒算置信度,不可接受。

第二个难题:阈值的自适应性。

即使设计出合理的评分公式,阈值怎么定?

把所有知识库的分数混在一起看,单一阈值对不同知识库可能是灾难 —— 检索质量好的知识库普遍分高,质量差的普遍分低。用同一阈值切,一边全是高置信,一边全是低置信。

核心矛盾:在保证在线低延迟的前提下,实现随数据分布自适应。

我最后怎么处理

最终方案分两层:在线链路只算原始分,离线任务负责生成阈值。

flowchart TB
    subgraph Online[在线链路 - 低延迟]
        Q[用户提问] --> R[检索]
        R --> S[计算三个原始分 S1/S2/S3]
        S --> CONF[Conf_raw 加权求和]
        CONF --> DB[(写入数据库)]
        CONF --> GRAD{读取阈值分级}
    end
    subgraph Offline[离线链路 - 每天一次]
        DB --> PULL[拉取近 7 天全量 Conf_raw]
        PULL --> CLEAN[清洗脏数据]
        CLEAN --> PCT[计算 P70/P30/P10 分位数]
        PCT --> CONF2[(阈值存入配置中心)]
        CONF2 --> GRAD
    end

在线链路:只算三个原始分数

不做归一化、不做缩放、不做统计。回答生成后只算三个原始分数,连加权结果一起写库。

指标 计算内容 权重
S1 Query 与 Top1 召回文档的余弦相似度 0.4
S2 AI 回答与召回文档集的平均余弦相似度 0.4
S3 用户问题与 AI 回答的余弦相似度 0.2

原始置信度公式:

Conf_raw = 0.4 × S1 + 0.4 × S2 + 0.2 × S3

S1 和 S2 各占 0.4,因为它们分别回答」找没找对」和」答没答对」。S3 只占 0.2,因为问答语义相似有参考价值,但高相似不代表答案正确 —— 两个错误答案之间也可能很相似。

整个过程在回答生成的同一请求里完成,只追加三次余弦相似度计算和一次加权求和,对延迟影响可忽略。

离线链路:用分位数自动切阈值

每日定时任务从数据库拉近 7 天全量对话的 Conf_raw 和 S1,先清洗空回答、检索失败等脏数据,再算三个分位数:

  • P70:高分分割线。
  • P30:低分分割线。
  • P10:极低分分割线(兜底用)。

结果存配置中心,在线接口实时读取,无需重启服务。

不用固定数值的原因:P70 在任何分布里都代表」前 30% 的好回答」,无论分数绝对值高低。这就是」让数据说话」—— 阈值不是 0.7,而是」比最近 70% 的回答好」。

两套规则,各管各的

拿到阈值后,不是简单地」高于 P70 就信、低于就怀疑」。不同场景用不同标准。

基础分级(给前端展示):

  • Conf_raw ≥ P70 → 高置信:完整输出答案,展示参考文档来源。
  • P30 ≤ Conf_raw < P70 → 中置信:答案前加」匹配资料有限,内容仅供参考」。
  • Conf_raw < P30 → 低置信:弱化确定性表述,不展示文档引用。

严格兜底(决定是否转人工):

只有同时满足两个条件,才判」无有效资料」并拦截回答:

  1. Conf_raw < P30(整体分低)。
  2. S1 < P10_near7days(检索分也极低)。

只满足条件 1 不满足条件 2,说明」整体分低但至少检索到了东西」,可以给出答案并提示风险,不拦截。

这个双重校验的核心是 减少误判 —— 只有检索和整体都差才转人工。单一阈值做拦截必然大量误报。

这件事留下的经验

自适应比绝对值可靠

用分位数做阈值的本质是承认一个事实:不知道」好分数」应该是多少,但知道」比大部分问答好的分数」就是好分数。

不追求绝对标准,追求相对排序。而且天然适应数据漂移 —— 某天知识库更新了一批高质量文档,整体分数普遍上升,P70 自动跟着上移。不需要任何人调参数。

在线和离线分开是通用模式

不止置信度,任何」在线快、离线准」的场景都可以参考这种分层:

  • 在线链路:只做必要原始计算,数据落库。
  • 离线链路:基于历史全量数据做统计分析,结果下发在线服务。

在线追求低延迟,离线追求准确性,两者通过配置中心解耦。离线任务失败不影响在线服务 —— 阈值不更新就继续用上一次的,不会中断。

下一步:按知识库维度拆分

当前方案把所有知识库的分数混在一起算分位数,隐含假设是不同知识库的检索质量分布差不多。

实际上可能不是这样。更合理的做法是按知识库维度分别算 P30 / P70,让每个知识库有自己的自适应阈值。这需要在下一步落地时解决。