RAG 质量
发布门禁、引用可靠性与剩余风险;下方保留 90 题黄金集的完整实验记录
CURRENT RELEASE GATE
当前 RAG 达到发布门槛
可发布这不是主观评分:主检索、90 题生成与中文厂商噪声专项分别绑定到可追溯 run。 自动引用精度受稀疏标注影响,只作诊断;发布正确性采用段落支持门与人工 P0 审计。
B9-20260810T195839ZGEN-20260810T201738ZSPECIALIST-20260810T183222Z
89.9%
主集 Recall@20 · 门槛 85%
01检索覆盖已达标
主集 Recall@20 89.9%;15 题中文厂商专项在加入 真实近邻噪声后仍为 93.3%,没有用第二次查询冒充 A/B。
02回答出口有硬门
模型引用不能直接下发;服务端绑定原文、移除弱支持句并处理假前提。 90 题中可答题误拒与诱导题错误断言都为 0。
03上线后仍要持续观察
噪声结论目前只有 15 题专项样本;每次切换生成模型后必须重跑生成回归。 自动 citation precision 不能替代人工高风险数字审计。
下面是算法演进记录,不是当前发布快照。
从 B1 纯稠密(MRR 0.7630)到 B13(MRR 0.8731),提升 11.0 个点。 这里保留了三轮「不好看」的结果:B2 的并集比基线还差、B8 扫完 42 组权重后的结论是不改,以及 B13 修好中文分词后端到端 ±0.0000。 只展示赢的那几轮,这页就不是评测记录而是宣传页。 下面「生成侧与延迟」里还有一条:GEN 写下的假设被 GEN-FIX 证伪, 两轮都留着。
10 轮检索走势
B4 与 B7 之后的绝对值不完全可比:B4 当时排除了 2 道标注不可用的题, 零分块修复后 90 题全部参与。B7 之后是同语料同题集,可以直接比。
逐轮记录
B1 · 纯稠密基线
基线B1-20260803T155249Z
- 改了什么
- bge-m3 向量检索,无融合、无重排。先测基线,再加任何东西。
- 判据
- 无门槛——这一轮的目的是产出后续所有轮次的比较对象。
- 结果
- 总分掩盖了三件事:recent_updates 是最弱类别(MRR 0.468,最差两题首个命中排第 16、17 位),因为稠密向量里没有时间;MXFP4 单题 Recall@20 只有 0.33,被 fact_check 整体 0.9556 盖住;可答题最低分 0.5778 与不可答题最高分 0.7205 重叠 0.1427,说明拒答不可能靠相似度阈值决定。
最新动态
MRR 0.4676
R@10 0.6189
事实核查
MRR 0.8833
R@10 0.9556
原理解释
MRR 0.9467
R@10 0.8556
B2 · 稀疏通道
有得有失B2-20260803T155644Z
- 改了什么
- 加入 Postgres tsvector 关键词通道,先用轮转交错与稠密合并。
- 判据
- 修好 B1 点名的精确词失败(MXFP4 题 Recall@20 0.33)。
- 结果
- 预测被证实:MXFP4 题 R@20 0.33 → 1.00、排名 4 → 1。没预测到的是并集比纯稠密还差——轮转交错无条件给弱通道一半名额,好结果被挤出去了。RRF 因此从「验证权重」升级为必需项。另量到中文缺口 52.1 个百分点:稀疏通道对含 ASCII 专名的问句 R@20 = 0.5798,纯中文只有 0.0588。
同轮对照 纯稀疏通道单独测: Recall@10 0.3613 · MRR 0.3138 · nDCG@10 0.2759
最新动态
MRR 0.4241
R@10 0.4678
事实核查
MRR 0.8889
R@10 0.9444
原理解释
MRR 0.9400
R@10 0.8556
B3 · RRF 融合 + 时间过滤
有得有失B3-20260803T174553Z
- 改了什么
- 加权 RRF 取代轮转交错;解析出的时间窗作为两个通道的过滤器。
- 判据
- 把 B1 点名的最大缺口 recent_updates 关上。
- 结果
- recent_updates MRR 0.4676 → 0.7333(+26.6pt),R@20 做到 0.9036 击败 B1 与 B2。起作用的是把时间窗当过滤器,而不是加一条独立的时间通道(实测只贡献 +0.004 Recall)。但其余五类 MRR 下降——答案进了候选集却排得靠后,这正是 reranker 的适用场景,B4 因此有了可证伪的门槛。
最新动态
MRR 0.7333
R@10 0.6800
事实核查
MRR 0.7472
R@10 0.9222
原理解释
MRR 0.6952
R@10 0.7222
B4 · 交叉编码器重排
达标采纳B4-20260803T174404Z
- 改了什么
- bge-reranker-v2-m3 对融合后的前 40 个候选重排序。
- 判据
- 先立后测:MRR 需 > 0.7630 且 R@20 ≥ 0.9036,两条都达标才准进 MVP。
- 结果
- MRR 0.8574、R@20 0.9126、nDCG +23.7%,全部达标。B3「Recall 升、MRR 降」的形态被完全纠正。候选数 40 比 100 在四项指标上都略高,而且快 3.2 倍(1376ms vs 4378ms)——融合排在 40 名之后的候选几乎不含答案,喂进去只是给交叉编码器加噪声。唯一退化的是 recent_updates(MRR 0.7333 → 0.6484):交叉编码器不知道时间。
同轮对照 100 候选: Recall@10 0.8665 · MRR 0.8562 · nDCG@10 0.8108
最新动态
MRR 0.6484
R@10 0.7911
事实核查
MRR 0.9667
R@10 0.9556
原理解释
MRR 0.9667
R@10 0.8222
B7 · 时效融合
达标采纳B7-20260804T075951Z
- 改了什么
- 对 freshness_required 为真的问题,在重排之后把新近度与相关性融合,权重 0.30。
- 判据
- 补回 B4 唯一的退化,且不得影响其他类别。
- 结果
- recent_updates MRR +10.4pt,其余五类逐位相同——不是影响很小,是没有影响。这是作用域正确的改动应有的样子。权重刻意只占 0.30:相关性仍主导,「只是新」不能压过真正回答问题的。
最新动态
MRR 0.7522
R@10 0.7911
事实核查
MRR 0.9643
R@10 0.9524
原理解释
MRR 0.9667
R@10 0.8222
B8 · 融合权重网格调优
结论:不改B7-20260804T102253Z
- 改了什么
- 42 组 dense/sparse/temporal 权重组合全扫一遍。
- 判据
- 规格要求权重必须用评测集调优,而不是拍脑袋。
- 结果
- 负结果,保持原权重不变。 网格确实找到更好的融合序(融合前 MRR +5.9 点),但接上重排后两组权重的差距塌缩到 0.0004,四个类别逐位完全相同。→ 融合前指标是个会骗人的优化目标,它测的是一个马上会被交叉编码器覆盖的中间态。网格的真正价值是证明现行值在平台区(全网格 R@40 只波动 0.0043)而非悬崖边(sparse ≥ 0.8 才开始伤 R@20)。
同轮对照 网格最优权重: Recall@10 0.8530 · MRR 0.8641 · nDCG@10 0.8101
最新动态
MRR 0.7522
R@10 0.7911
事实核查
MRR 0.9000
R@10 0.8889
原理解释
MRR 0.9667
R@10 0.8222
B9 · §6 directness / source_fit
达标采纳B9-20260804T104220Z
- 改了什么
- 在交叉编码器之后补上两个重排维度:问题词项在标题中的覆盖率、信源等级与问题类型的亲和度。
- 判据
- 增益应落在 timeline 与 explainer,其余类别不得退化。
- 结果
- timeline MRR +4.45pt、explainer R@10 +3.34pt,其余类别在噪声内(最差 −0.07pt),abstention 逐位相同。R@20 不变是对的——这两维只在重排结果内部重排序,不引入新文档。写这个模块时踩到中文分词坑:`MIN_TERM_CHARS = 2` 把中文全删了,directness 对每道纯中文问题稳定返回 0.0 且不报错。
最新动态
MRR 0.7522
R@10 0.7911
事实核查
MRR 0.9000
R@10 0.8889
原理解释
MRR 0.9667
R@10 0.8556
B10 · §6 entity_subject / repost
达标采纳B9-20260804T114016Z
- 改了什么
- 补上最后两条元数据调整:目标实体为主语 +0.05、重复转载 −0.10。
- 判据
- 此前判定为「拿不到数据」,本轮先回库核对该判定。
- 结果
- 先前的「拿不到数据」是错判,从没去库里核对过。 实际查:item_entity.role 有 subject 1848 / mention 2593 / object 1496,`duplicate_of_id` 也一直在写——数据从 M2 起就在库里。修完后 timeline R@10 +1.66pt、recent_updates +1.33pt、abstention nDCG +2.31pt,没有任何一个类别、任何一项指标下降,这在十轮评测里是第一次。至此 §6 的五条调整全部实现。
最新动态
MRR 0.7522
R@10 0.8044
事实核查
MRR 0.9000
R@10 0.8889
原理解释
MRR 0.9667
R@10 0.8556
B12 · 自适应重排深度
达标采纳B9-20260807T070516Z
- 改了什么
- 把重排候选数按问题类型路由:两类走 20,其余保持 40。
- 判据
- 只有在某一类的指标逐位不变时才允许减半,任何下降都保持 40。
- 结果
- 先死掉两个直觉规则,才找到能站住的那个。 规则一「简单事实题跳过重排」——B3→B4 实测 fact_check 从重排拿到 +0.2195 MRR,是收益第二大的类别,直觉完全反了;唯一不受重排影响的是 abstention,而 B1 已证明不可答题无法事先识别(与可答题的相似度区间重叠 0.14)。规则二「候选少时跳过重排」逻辑上成立——候选数不超过证据预算时,重排改变不了模型读到哪些段落——但 128 次真实查询里融合候选数从未低于 60,规则永远不触发。真正成立的是深度而不是有无:depth 20 整体掉 2.9 个点,但 comparison 与 recent_updates 逐位完全相同,而 explainer −6.3pt、fact_check −5.9pt、timeline −3.6pt。于是只对那两类减半,形状与 B7 一致:在测过有效的地方生效,其余原样不动。样本量写在明处:每类 15 题。
同轮对照 重排深度 20: Recall@10 0.8365 · MRR 0.8438 · nDCG@10 0.7907
最新动态
MRR 0.7522
R@10 0.8044
事实核查
MRR 0.9000
R@10 0.8889
原理解释
MRR 0.9667
R@10 0.8556
B13 · 中文分词:修好了通道,系统没动
结论:不改B9-20260807T114536Z
- 改了什么
- Postgres `simple` 把整段中文切成一个词元。加 CJK 字符 bigram,索引与查询共用同一个 IMMUTABLE 函数(ADR-0018)。
- 判据
- B2 量到纯中文问句稀疏 R@20 仅 0.0588(含 ASCII 专名 0.5798),本轮应把这个差距关上。
- 结果
- 通道确实修好了,端到端却没动。 MRR ±0.0000,R@10 −0.0043,六类里五类逐位相同。原因 B2 自己就写过、当时没被当回事:「稠密通道对中文只低 4.9 个百分点——缺口在通道设计边界内,不在系统层」。稀疏通道的中文失败一直被稠密通道兜住了。但站内搜索是另一回事,它只有 FTS 没有稠密兜底,实测:「开源」4 → 130 条、「量化」5 → 48、「融资」2 → 18、「智谱」0 → 2,而 ASCII 查询「GLM」15 → 15 不变。所以改动保留——收益在另一个界面上,且是真的;RAG 侧记为负结果,代价是索引 15MB → 18MB。
同轮对照 修复前(无 bigram): Recall@10 0.8669 · MRR 0.8731 · nDCG@10 0.8191
最新动态
MRR 0.7522
R@10 0.7822
事实核查
MRR 0.9000
R@10 0.8889
原理解释
MRR 0.9667
R@10 0.8556
与 RAGAS 术语的对应
三项没有 RAGAS 对应:story_coverage(RAGAS 假设文档彼此独立, 而四家媒体报道同一次披露是一条证据不是四条)、citation_coverage(句级引用覆盖率是本项目的核心主张)、 以及误拒率。Noise Sensitivity 目前采用 15 题中文厂商专项集与 8 个真实近邻噪声 做同候选快照 A/B;它已经覆盖最容易串线的场景,但仍只是专项小样本, 不能冒充完整 90 题噪声回归。
生成侧与延迟
GEN · 生成侧评测(含拒答判定)
GEN-20260807T120415Z
- 测了什么
- 90 题端到端真实生成。本轮新增拒答判定:对 12 道诱导题,用受控的二值判官问「这段回答有没有把那个假前提当事实陈述出来」。
- 结果
- 先修指标,再读结果。 原来的 `refusal_rate_on_unanswerable` 从 66.67% 掉到 8.33%,看着像核心能力崩了。查下去发现它测的是形式不是正确性:拒答被定义为「文本为空或零引用」,而 §3.13 刻意把死胡同式拒答改成了有据可查的否认——「证据里没有提到罚款,也没有任何监管机构开出罚单的记载」并附上检索到的内容。这类回答有引用,于是被判成「没拒答」。另一个信号 `must_not_claim` 是子串匹配,它自己的注释就写着「否认时提到该词也算命中」,而且只覆盖 15 题里的 4 题。两个信号都在测表面形态。新增判官后:12 道诱导题里断言假前提的为 0,支持度 0.8330 → 0.8313 基本不动,引用覆盖率 +12.5pt。结论:拒答能力没有退化,退化的是衡量它的方式。本轮同时暴露一条真问题:误拒率 1.28% → 7.69%(1 题变 6 题)、Story 覆盖 −8.2pt。当时记下的假设是「语料涨到 1473 条后证据位竞争加剧」,并写明那是假设不是结论——下一轮证明它是错的。
refusal_rate_on_unanswerable
0.0833
forbidden_term_mentioned
4
presupposition_asserted_rate
0
GEN-FIX · 生成侧复测:误拒归零
GEN-20260807T153537Z
- 测了什么
- 同样 90 题,只改了一处:解析模型输出时的失败分支。把上一轮那六道误拒的原始模型响应抓出来看了之后改的。
- 结果
- 语料竞争那个假设是错的,六道题一道都不是检索问题。 抓原始响应发现两条各自独立的失效路径,都在出口处:①模型直接用 markdown 回答、没套 JSON——答案完整、`[E1][E2]` 一个不缺,只是 `json.loads` 失败,于是正文被清空、判成拒答;②模型把证据编号只写进 `claims[].evidence_ids`、正文里不标——而绑定只扫正文,四条论断六个编号绑出零条引用。两条都是「模型偶发偏离输出契约,而每次偏离赔上整个答案」,也解释了为什么两轮之间会从 1 题跳到 6 题:这是随机脱靶,不是某类问题的固定缺陷。修法是给失败分支加带守卫的回退(不像 JSON 且至少有一个编号,才按正文解析;正文一个编号都没有,才回退到 claims),核验一寸不放——两条路径照走同一套绑定与不变量检查,编造的编号照样剥掉。结果:误拒率 7.69% → 0.00%(78 道可答题零误拒,比 08-04 基线还低),Story 覆盖收回 5.6pt,引用覆盖率 90.7%,must_contain 命中 100%,而断言假前提仍是 0——放宽没有换来幻觉。唯一下行的是支持度 −2.2pt:被救回的六题分数确实偏低(0.7046 对 0.7707),但它们的 Story 覆盖是 0.8889、远高于全体的 0.7419,救回来的是覆盖面更宽的实质答案;其余 72 题代码路径逐字未变却也动了 −2.0pt,那是模型自身的轮间抖动。多发表六个答案换来均值略降,这是正确的代价。
refusal_rate_on_unanswerable
0
forbidden_term_mentioned
4
presupposition_asserted_rate
0
LAT · 端到端延迟
LAT-20260804T092526Z
- 测了什么
- 24 题实测 p50 / p95 与各阶段耗时占比。
- 结果
- 这组数字推翻了此前的一个判断。 原以为「父块/折叠是打磨项,检索是大头」,数据说反了:本地计算(dense + sparse + fuse + select + parent)合计不到 1%,99% 的时间花在三次外部 API 往返上。直接后果是压延迟只能动网络侧,而任何「多算几路」的实验成本可以忽略。