拓冰建站拓冰建站
首页 / 资讯中心 / 正文

RAG 知识库问答实战(5):检索召回优化与重排

上一篇完成了带拒答和引用的最小问答链路但链路质量受候选证据上限约束生成模型再强也无法使用没有召回的事实。本篇聚焦检索漏召回依次加入查询规范化、混合检索、融合、去重和重排并让每一步都能独立评测。一、痛点用户语言与文档语言天然不一致用户问“电脑坏了找谁”制度可能写“终端设备故障由 IT 服务台受理”向量检索能处理部分语义差异却可能输给精确型号、日期和错误码。关键词检索擅长字面匹配又会漏掉同义表达。另一类问题包含多个意图例如“额度是多少超过后谁审批”单向量常只命中其中一半。优化前先分类失败相关片段不在 Top-K 是召回问题进入候选却排名靠后是排序问题证据排名正确但答案错是生成问题。不要通过无限增大 K 掩盖召回候选越多会提高重排成本并把噪声送入上下文。保存每个检索器的原始列表和分数才能定位贡献。二、原理混合召回扩大覆盖重排提高精度典型漏斗是稠密向量与 BM25 各取 20100 条用 Reciprocal Rank FusionRRF合并再去重最后由 cross-encoder 重排前 2050 条并选前 5 条。RRF 基于名次而非不可比的原始分数某文档在列表排名 r就贡献1/(kr)它无需校准不同检索器分数且对单个列表极端值不敏感。下面实现 RRF。两个列表分别模拟关键词和向量召回重复文档累积分数因此同时被两路认可的片段自然前移。fromcollectionsimportdefaultdictdefreciprocal_rank_fusion(rankings:dict[str,list[str]],constant:int60,)-list[tuple[str,float]]:scores:dict[str,float]defaultdict(float)forretriever,documentsinsorted(rankings.items()):seen:set[str]set()forrank,doc_idinenumerate(documents,start1):ifdoc_idinseen:continueseen.add(doc_id)scores[doc_id]1.0/(constantrank)returnsorted(scores.items(),keylambdaitem:(-item[1],item[0]))rankings{bm25:[c_error_42,c_network,c_reset],dense:[c_reset,c_account,c_error_42],}fusedreciprocal_rank_fusion(rankings)fordoc_id,scoreinfused:print(f{doc_id}\t{score:.6f})运行输出c_error_42 0.032266 c_reset 0.032266 c_account 0.016129 c_network 0.016129并列结果用稳定 ID 排序保证回放一致。实际系统还可为不同通道加权但权重要通过验证集调参。若精确错误码是核心可以提高关键词通道权重若用户口语化明显可提高稠密通道权重。不要在测试集上反复调到最好再把同一结果称为泛化效果。三、实现查询改写要保留原意并限制扩张改写包括拼写纠正、缩写展开、对话指代消解、多查询扩展和问题分解。它可能提升召回也可能改变实体或时间范围因此必须保留原查询作为一路并记录改写文本。对“它的保修期呢”先结合会话得到明确实体对包含两个子问题的查询分别检索最后合并证据。重排器读取完整的“查询—片段”对比单独编码向量更精确但计算成本随候选数增长。先做便宜召回再批量重排给重排设置超时失败时可降级使用融合排名。下面用可解释的特征模拟重排词项覆盖为主标题命中和现行版本加分。fromdataclassesimportdataclassdataclass(frozenTrue)classCandidate:chunk_id:strtitle:strtext:strcurrent:booldefrerank(query_terms:set[str],item:Candidate)-float:body_hitssum(terminitem.textforterminquery_terms)title_hitssum(terminitem.titleforterminquery_terms)coveragebody_hits/max(len(query_terms),1)returncoverage0.2*title_hits(0.1ifitem.currentelse0.0)query_terms{差旅,额度,审批}candidates[Candidate(old,差旅制度,差旅额度及审批流程,False),Candidate(new,差旅审批,国内差旅额度标准,True),Candidate(leave,请假审批,年假申请流程,True),]rankingsorted(candidates,keylambdax:(-rerank(query_terms,x),x.chunk_id))foriteminranking:print(f{item.chunk_id}\t{rerank(query_terms,item):.3f})运行输出old 1.200 new 1.167 leave 0.300生产中可使用 bge-reranker、Cohere Rerank 或其他 cross-encoder。选型仍应基于中文业务集、最大长度、吞吐和部署方式。重排分数通常不是概率不能未经校准就解释为“90% 可信”。拒答阈值应在独立数据上按业务误答成本选择。四、踩坑重复、过滤和新鲜度会扭曲 Top-K相邻重叠片段可能占据五个名额却都来自同一段。可按内容哈希做精确去重再按文档与章节限制配额或使用 MMR 在相关性与多样性之间折中。多证据问题不能过度去重否则不同条款会被误删。去重规则需看来源位置和语义而不只是字符串相似。权限、租户、语言和生效日期属于硬过滤相关性属于软排序。硬条件不能交给重排器“降权”。对时效性强的内容可加入衰减或版本优先但旧制度仍可能是用户明确询问的对象所以先解析时间意图。缓存键必须包含改写版本、过滤条件与索引版本避免跨用户复用受限结果。中文检索还应评估分词策略。专有名词被切错会削弱 BM25可维护经过审批的用户词典并对型号、工单号和英文缩写保留原形。词典更新同样需要版本化与回归因为增加一个短词可能改变大量文档的词频。查询侧的数字、单位和日期应同时保留规范化形式与原始形式。对于多跳问题单次 Top-K 往往只命中起点。可先召回第一条实体关系再以其中的名称构造第二次受限查询但要限制步数与总候选数并保留每一步的因果链。若中间实体不确定应返回不足而不是沿错误路径继续扩展。图检索适合关系密集场景却不必成为所有问答的默认组件。五、验证做消融实验而不是堆功能以基础向量检索为 A依次加入 BM25、RRF、改写、重排和去重每次只改一项报告 Recall20、MRR、nDCG10、最终上下文命中率、p95 与单次成本。按错误码、口语查询、多跳问题等切片否则总体平均会掩盖关键场景退化。线上小流量实验还应看拒答率、引用点击和用户改写查询的比例。评测时保存候选在各阶段的名次变化原始召回是否包含、融合后是否上升、重排后是否进入上下文。这样的漏斗日志能快速发现某个重排器偏爱长文本或去重器误删同章节不同条款。只有新增模块在目标切片上带来稳定净收益才值得承担延迟和维护成本。本篇把候选检索升级为可解释的多阶段漏斗。下一篇将处理纯文本管线无法保留的信息表格结构、扫描页、图片说明和图文关联。参考来源Cormack 等Reciprocal Rank FusionElasticsearch混合搜索与 RRFSentence TransformersCross-Encoder Reranking 觉得有用就点个赞 收藏方便回头查阅有疑问直接在评论区留言我看到都会回。 本文属于《RAG 知识库问答实战》系列持续更新关注不迷路。 文章里的代码都能直接跑。想要可直接 clone 的完整工程 配套部署脚本 / 踩坑清单评论一声或发邮件到cj2664qq.com我免费发你。如果你正好在做类似系统、或有工程化难题想找人做也欢迎邮件聊一句——我按实际情况评估能落地的就接单或出方案。评论和邮件都能直接找到我不用跳别的平台。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门