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

问题从哪里来

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

检索到相关内容了吗?回答有没有忠实引用文档?问答之间语义匹配吗?这三个问题的答案共同决定了回答的质量。

如果全部交给用户自己判断,系统就没有起到」筛选」的作用。但如果拍脑袋定一个固定阈值 —— 比如相似度大于 0.7 就算高置信 —— 今天的 0.7 和明天的 0.7 可能完全是两回事。不同知识库、不同检索模式、不同嵌入模型,分数分布天然不同,固定阈值迟早失效。

OpsMind 作为面向 IT 运维的智能问答系统,需要对自己的回答有信心判断。高置信的回答可以放心展示给用户,低置信的回答则需要提示风险甚至流转人工处理。这个判断如果出错 —— 误把胡扯当权威,或者频繁把正常回答标记为不确定 —— 系统就会失去信任。

真正卡住的是哪一步

把置信度问题拆开后,本质上有两个难题:

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

要准确判断置信度,需要多个维度的信号 —— 检索匹配度、原文对齐度、问答语义一致性。如果每个维度都做复杂的归一化、缩放、校准,在线接口的延迟就保不住了。用户问一个问题等 3 秒,然后系统花 2 秒算置信度,这不可接受。

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

即使设计出了合理的评分公式,阈值怎么定?如果把所有知识库的分数混在一起看,单一阈值对不同知识库可能是灾难 —— 检索质量好的知识库普遍分数高,质量差的普遍分数低。用同一个阈值切,一边都是高置信,一边都是低置信。

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

我最后怎么处理

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

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

不做任何归一化、不做缩放、不做统计 —— 只在回答生成后计算三个原始分数,然后把它们和加权结果一起写入数据库。

指标 计算内容 权重
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,让每个知识库有自己的自适应阈值。这需要在下一步落地时解决。