ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

个人 RAG 的阈值怎么调:先允许“没找到”,再讨论召回率

个人 RAG 的阈值怎么调:先允许“没找到”,再讨论召回率

问生活知识库“去年那次搬家花了多少”,系统找来一篇旧住址日记,语义很像,年份却不对。调高相似度阈值未必能修好这个问题,因为日期条件从一开始就没有进入检索。

对私人记录来说,错误地拼出一个答案往往比少回答一次更令人困扰。先明确哪些问题可回答,再用标注集选择阈值,比凭感觉给出一个“万能 0.75”可靠。

相似度、重排分数和概率分开看

嵌入模型先召回候选,重排模型联合读取问题与文档,再对候选排序。BAAI 的 bge-reranker-base 模型卡给出这种交叉编码器的使用方式;它的原始分数不能直接当作“答案正确概率”。换模型、换版本或换语料,都要重看分布。官方模型卡

2026 年 10 月 21 日 NVIDIA 的检索模型介绍也把重排放在候选检索之后。个人库可以借鉴分工,不能照搬其文档基准里的分数阈值。官方介绍

用小标注集看清取舍

下面分数是手工构造的教学输入,没有加载重排模型。保存为 threshold.py,运行后能看到阈值提高时,误收与漏收怎样变化。

cases = [ ("同年搬家账单", 0.82, True), ("上年同名账单", 0.76, False), ("同年维修凭证", 0.61, True), ("无关日记", 0.20, False), ] def evaluate(threshold): tp = fp = fn = 0 for _, score, relevant in cases: accepted = score >= threshold tp += int(accepted and relevant) fp += int(accepted and not relevant) fn += int(not accepted and relevant) precision = tp / (tp + fp) if tp + fp else None recall = tp / (tp + fn) if tp + fn else None return tp, fp, fn, precision, recall assert evaluate(0.6)[:3] == (2, 1, 0) assert evaluate(0.8)[:3] == (1, 0, 1) for t in (0.6, 0.8, 0.9): print(t, evaluate(t))

0.9 时没有被接受的候选,精确率用 None 表示,不把空集合算成 100% 正确。真实评估还要区分“库里有答案但没找到”和“库里本来没有答案”,否则拒答指标会失真。

把时间约束放到排序之前

“去年”必须相对于提问时间解析,例如以 2026 年为参照时使用 2025-01-01 到 2026-01-01 的左闭右开区间。跨年记录、时区与用户指代不明时,先询问或展示解释后的条件。

标注集应包含正确记录、近似但不相关记录、过时记录和无答案问题。调参集与最终测试集分开保存,避免不断看同一批题调到漂亮结果。

本文只实跑了教学分数上的指标计算,未运行嵌入模型或重排模型,也没有宣称私人库准确率提升。一个愿意说“这份记录不足以回答”的助手,才给后续核实留下空间。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进