去年年底我们接手了一个企业内部知识库问答系统的优化需求客户反馈里有一句话特别扎眼“向量检索明明显示相似度 0.87怎么答案跟问题完全不沾边”这句话几乎成了后来复盘时反复引用的样板。企业 RAG 里“排序高分、答案错误”不是偶发事故而是嵌在架构里的系统性偏差。分数高只代表检索片段在嵌入空间里离问题“近”并不代表片段里真的含有能推导出答案的事实。这篇文章我打算从一次完整的验收过程说起梳理我们如何通过内部嵌入模型 Embed 5 和检索置信预测指标 RCP把“高分错误”这类的验收边界从拍脑袋变成可以计算的约束。全文会涉及实际的复现路径、公式推导、阈值设计和踩坑记录。如果你正在做企业级 RAG 系统的召回评估、生成质量治理或者被“相似度很高但答案不对”折磨过这篇文章应该能给你一条新的排查思路。1. “高分错误”现场检索命中了生成却翻车先还原一下我们遇到的真实问题不然后面讲 Embed 5 和 RCP 会显得抽象。1.1 现象0.87 的高分答非所问的答案当时客户的知识库里有大量产品说明书、售后政策、FAQ 条目。他们给了一个测试问题“2024 年购买的某型号设备退货期限是多少天”系统返回的检索结果里第一条片段的排序相似度 0.87非常高。但最终生成的答案是“该设备适用于家庭及小型办公场景支持远程固件升级……”完全没提退货期限。我们第一反应是生成阶段出问题了于是把检索到的片段单独拉出来看。结果发现片段内容是产品概述的引言里面确实出现了“2024 年新政策”“售后”“设备”这些与查询重叠的词汇但整段话没有一处提到“退货期限”“天数”“申请条件”。向量模型把“语义相关”误解成了“问题可回答”排序分数高但可供生成器提取的事实是零。1.2 两阶段黑箱检索器只负责“找”生成器只负责“编”“排序高分、答案错误”的根子在 RAG 的串联结构上。检索器Retriever的优化目标是找到与查询在嵌入空间中距离近的文本片段它根本不知道这些片段拼接起来能不能支撑一个完整答案。生成器LLM则默认“检索到的片段就是可信事实来源”如果片段里缺少关键信息它不会主动说“信息不足”而是会基于片段里的边角料强行组织语言。这个问题在企业场景里被放大的原因有三个一是企业文档普遍很长分块策略往往按固定字数硬切真正的答案经常被切到别的块里二是 FAQ 条目用词高度重合比如“退货”“退款”“售后”反复出现在不同条目中排序器很容易被关键词带偏三是多轮迭代后旧文档和新文档并存检索器可能召回语义相近但内容已过时的片段。这些因素叠加起来就会形成“排序分数高、答案可用性低”的典型错配。1.3 为什么通用评估指标发现不了它我们最开始用的评估方式是 NDCG、MRR、命中率这类常规检索指标。NDCG 看排序质量、MRR 看第一个正确答案的位置、命中率看 Top5 里有没有相关片段。这些指标都只回答“相关片段是否被找出来”不回答“这些片段是否足以支撑最终答案”。实际上它们给了我们一个虚高的安全感检索指标一直很漂亮用户却持续反馈答非所问。后来我们才意识到需要给“检索上下文质量”单独建模。RCP 指标就是在这个缺口上长出来的。2. Embed 5 做了什么把“感觉相关”变成“排序分数”的有限过程要理解 Embed 5 为什么救不了答案错误得先搞清楚嵌入模型的上限在哪里。2.1 Embed 5 是内部第五版嵌入模型它优化了召回没优化答案Embed 5 是我们项目组内部自研的文本嵌入模型第五个版本专门针对客服问答场景做领域微调。第四版用的是通用语义训练对“设备型号”“售后条款”这类生僻词表示不稳定第五版在标注了约几万条领域问答对后做了继续预训练Top5 召回率从 78% 提到了 86%。单看这个数字升级效果很明显。但端到端一测就露馅了。我们把同一个测试集跑在 Embed 4 和 Embed 5 上答案正确率只从 61.5% 涨到了 65.0%。涨了 3.5 个百分点远低于召回率提升的 8 个百分点。差距说明了一个事实嵌入模型的升级空间主要贡献给了“让相关片段排得更靠前”但生成阶段能否把片段里的信息正确用起来是另一套逻辑。2.2 余弦相似度的局限语义近不等于事实全嵌入模型最终输出的是向量排序依据大多是余弦相似度。这个操作有一个很深的局限它把“整个片段”转化为一个固定长度的向量片段越长不同含义的句子就越容易互相稀释。比如一条 FAQ 条目上半句是“2024 款设备支持 Wi-Fi 6”下半句是“退货期限为 7 天”整段向量会被平均成一个“混合语义”。当查询是“退货期限”时模型能感觉到这段文字相关却没法精确指出哪一句话是答案。我们内部做过一个对比实验把同样一篇文档切成 128 字块、256 字块和 512 字块分别用 Embed 5 检索同一组问题。结果 128 字块的召回率最高但因为上下文被切得太碎实体指代经常断裂512 字块上下文完整可排序分数普遍偏低因为块里有太多无关内容稀释了语义。最终我们选了 256 字块加 30 字重叠但这只是经验值不同知识库最优跨度差异很大。2.3 Embed 版本切换造成的“分数通胀”假象升级到 Embed 5 之后还有一个非常坑的现象整个测试集里所有查询的排序分数平均值都涨了。原因很简单领域微调让模型对售后、退货这些词更敏感任何包含这些词的片段都会获得更高的相似度。于是很多人会误以为“嵌入升级带来了整体质量提升”但打开高分片段逐个看很多都是只碰词、不碰事实。这就是我常说的“分数通胀”。如果团队只用排序分数做监控永远发现不了问题必须引入类似 RCP 这种“事实充足度”维度的指标来判断高分片段到底有没有把答案的事实支撑起来。3. RCP 推导把“检索上下文能用几分”变成数学约束RCP 全称是 Retrieval Confidence Prediction我们自己定义的检索置信预测指标。它回答的问题是给定查询和检索片段这些片段里有百分之多少是真的能支撑生成正确答案的“可用上下文”。3.1 先定义清楚相关、可用、可支撑是三件事设计 RCP 之前我们不得不先把概念拆清楚。“相关”指片段与查询在语义上有关联“可用”指片段包含可以组织答案的信息“可支撑”指生成答案中的每一个事实判断都能在片段里找到依据。Embed 5 排序分数只能近似描述“相关”RCP 要捕捉的是“可用”。一张表格能说清楚区别维度排序分数Embed 5 输出RCP我们定义的指标衡量对象查询与片段的语义距离片段对最终答案的贡献程度分数来源余弦相似度 / 模型打分人工标注、规则校验、生成交叉验证理想性质高分意味着“看起来相关”高分意味着“答案事实够用”对生成的意义弱只能决定召回顺序强直接决定能否通过验收常见失败模式碰词即高分、片段过长稀释片段过短缺上下文、多跳事实断裂排序分数是 Embed 5 这类模型直接输出的RCP 是我们后加的校准层。3.2 RCP 计算公式与一个可直接运行的实现给定查询 q检索系统返回 N 个片段编号从 1 到 N。人工评测时给每个片段打一个二值标签 labellabel1 表示“这个片段包含回答问题所需的关键事实”label0 表示“不包含”。同时记录每个片段的归一化排序分数 score以及基于排序位置计算的权重 position_weight。RCP 的计算方式如下RCP Σ(label_i × score_i × position_weight_i) / Σ(score_i × position_weight_i)position_weight_i 1 / log2(i 1)这是常见的 DCG 位置衰减思想排名越靠前的片段权重越高。如果返回片段里存在部分信息可用的情况可以把 label 拓展为 0、0.5、1 三档0.5 代表“片段提供间接线索不能单独支撑答案”。为了让人能直接跑通这套逻辑我当时写了一个很精简的 Python 实现import math def compute_rcp(retrieved_items): retrieved_items: list of dict, each has keys: - label: 0 or 0.5 or 1表示该片段是否能支撑答案 - score: Embed 模型给出的归一化排序分数 - rank: 排名从 1 开始 numerator 0.0 denominator 0.0 for item in retrieved_items: rank item[rank] weight 1.0 / math.log2(rank 1) numerator item[label] * item[score] * weight denominator item[score] * weight if denominator 0: return 0.0 return numerator / denominator # 示例数据 items [ {rank: 1, score: 0.87, label: 0}, # 高分但无关键事实 {rank: 2, score: 0.82, label: 1}, # 含答案关键事实 {rank: 3, score: 0.75, label: 0.5}, # 只提供间接线索 {rank: 4, score: 0.60, label: 0.5}, {rank: 5, score: 0.55, label: 0}, ] print(compute_rcp(items))跑出来的结果大概是 0.39。也就是说即便排序分数平均值高达 0.72RCP 依然只有 0.39意味着这套检索结果对生成器的“可用支持度”严重不足。3.3 为什么 RCP 比直接问人工“这回答对吗”更可靠还有一种做法是直接让标注员看最终答案给分比如“正确 / 不正确”。实操下来我们发现这种粗标签很难定位问题。标注员判定答案不正确时我们没法分清是检索漏召回、检索片段过时、还是生成阶段幻觉。RCP 把标注对象从“完整答案”下沉到“单个片段”粒度细了之后问题就能被定位到具体环节。在项目里我们通常让两个人独立给 label不一致的地方交给第三人仲裁。标注规范只有一条核心准则不看最终生成的答案只看“如果我是生成器能不能依据这个片段亲口说出正确回答的每一个要点”。这样一来RCP 既评价了检索结果质量也间接预判了生成阶段的可用素材是否充足。4. 从 Embed 5 到 RCP 的三层验收边界阈值怎么定、样例怎么算有了 RCP 这个工具接下来就是最关键的问题什么样的分数能过验收什么样的不能我们最终建立了三层验收边界。4.1 第一层召回边界Top5 里必须有“事实片段”第一层最简单Top5 切片中至少有一个片段的 label 为 1否则直接判定“检索失败”。这层边界对应最朴素的需求——用户想查退货期限你必须真的把写着退货期限的段落捞回来。Embed 5 的排序分数在这里只做参考不做判定因为分数高的片段可能只是导语文本看起来相关实际没有事实。有些团队会把这条边界简化为“Top1 必须命中”我们不太建议。企业文档里答案经常分散在多个小节Top1 可能是总述真正的条款在后面。所以我们用 Top5 作为召回边界同时要求 Top5 内至少一个片段能完整支撑答案。召回边界没过后面两层就不用看了。4.2 第二层上下文支撑边界RCP 必须大于 0.7第二层是 RCP 的绝对阈值。我们内部定的标准是单轮问答场景 RCP ≥ 0.7 才算通过复杂推理场景可以放宽到 0.6但必须满足“答案所需的关键事实覆盖度超过三分之二”。这个 0.7 是怎么来的我们统计了历史评测集里 1200 个问答对的 RCP 值与答案正确性的关系。结果很有意思RCP 低于 0.5 时端到端答案正确率只有 41%RCP 在 0.5 到 0.7 之间正确率约 63%RCP 大于 0.7正确率能到 81%。再往上提升不明显所以把边界定在了 0.7。不同企业知识库的基础质量差异很大直接用 0.7 可能不合适但这个“先统计相关性再定阈值”的方法是可以复用的。4.3 第三层生成忠实度边界答案里的每句话都得有出处第三层边界针对生成阶段答案中的每一个关键事实句都必须能在检索片段里找到依据。我们采用“句子级忠实度”计算公式很简单faithfulness 答案中能在检索片段中找到依据的关键事实句数 / 答案中全部分键事实句数你可以在评测集里让标注员人工判断也可以让另一个更强的 LLM 做交叉验证。我们的经验是用一个独立的模型做“检索增强验证”把生成的答案切分成陈述句逐句判断它是否被检索片段支持支持则计 1部分支持计 0.5完全捏造计 0。最终要求 faithfulness ≥ 0.8。4.4 一个完整的验收样例计算为了让你直观看到三层边界怎么联动我放一个真实风格的计算过程。假设查询是“订单显示已签收但没收到货怎么申请售后”Embed 5 检索返回以下五个片段排名片段主要内容排序分数RCP 标注 label位置权重1已签收但未收到的处理流程0.9111.002售后申请入口介绍0.850.50.633配送异常免责条款0.8000.504退款时效说明0.760.50.435会员积分规则0.6000.39第一步检查 Top5 里是否有 label1 的片段。有第一段就是召回边界过。第二步计算 RCP。分子等于 1×0.91×1.0 0.5×0.85×0.63 0.5×0.76×0.43 ≈ 1.32分母等于 0.91×1.0 0.85×0.63 0.80×0.50 0.76×0.43 0.60×0.39 ≈ 2.36。RCP ≈ 0.56低于 0.7第二层不上。这里明确卡住排序分数虽然都在 0.6 以上但可用信息贡献不足。如果调整了检索策略让第三个片段变为“配送异常后的补救条款”且 label 改为 1RCP 会重算。假设排序分数变为 0.86则分子增加 0.86×0.500.43分母也相应增加最后 RCP 大约在 0.72。只有这种情况三层边界才全部通过。5. 上线前必须补的几类坑高分片段、过期文档、多跳问题边界会用了不代表万事大吉。验收过程中我们踩了不少坑有些甚至差点让整套体系失真。5.1 片段截断引发“假高分、假低 RCP”企业文档里经常有一个自然段落同时覆盖多个问题。固定字数切片会把“退货期限说明”和“退款时效说明”硬生生切成两半结果两半片段和查询都相关排序分数都高但各自 label 都到不了 1。RCP 看着偏低实际原因是切片策略太粗糙不是检索模型不行。我们后来改成了“语义章节优先、定长兜底”的切片策略。先按文档已有的标题和章节切超过阈值的章节再按句子边界补切并且保留 20% 的上下文重叠。切出来的片段RCP 标注一致性提高了不少。5.2 过期文档对 RCP 的污染知识库更新频率比想象中快得多。2023 年的退货政策和 2024 年的新政策并存时Embed 5 可能把两段内容都排到前排。如果标注人员不知道最新政策是哪条会把旧政策片段也标成 label1RCP 被迫虚高。这次我们吃了大亏直接导致一批本应失败的用例通过了验收上线后被用户投诉。修正办法是在每个片段上附加“生效时间”“文档版本”“业务线”元数据RCP 计算时要求“时间有效”作为前置条件。如果片段对应政策已过期label 强制为 0。5.3 多跳问题让“单片段 RCP”失效有些企业查询天然是多跳的。比如“退款到账后多久可以重新下单”需要同时知道“退款到账时间规则”和“下单资格恢复规则”两个事实分别存储在两个片段里。单片段 RCP 只看单个片段能否支撑答案这类查询会稳定地得到 0.5 的低分。针对这个问题我们把 RCP 扩展出“多片段联合标签”只有当 TopK 片段的联合信息覆盖了问题所有关键实体和关系时才对片段组打整体 label1。计算方式在单片段 RCP 基础上增加一个覆盖度系数。如果你也在处理企业级的复杂问题建议从需求梳理阶段就开始标注“这个问题是否需要多个事实联合支撑”否则后续评估会一直有偏差。5.4 标注集规模与成本的控制RCP 依赖标注而企业场景的标注成本不低。一开始我们只标注了 300 条跑出来的指标波动极大换个查询模板RCP 能从 0.55 跳到 0.82。后来我们用两个办法扩量一是从历史问答日志里捞用户明确点“赞”或点“踩”的会话把回答被踩且能确认是检索上下文缺失的样本自动标成 label0二是利用更强的通用模型做预标注人工只复核不一致的部分。这样把标注集扩大到 2500 条后指标才稳定下来。我自己在实际项目里最深的一个体会是企业 RAG 的验收不能停留在“排序分数高不高”这个维度。Embed 5 这类嵌入模型能帮我们把相关片段找得更准但它定义不了“什么是正确答案”。RCP 补上的正是这一层校准逻辑——把检索上下文对生成答案的事实支撑程度显式建模让验收边界不再依靠直觉。如果你手头也有一个被“高分错误”困扰的检索系统不妨先从清理评测集、统计 RCP 与答案正确率的相关性开始用数据找出那个属于你自己业务场景的边界比直接换更大的模型更管用。