手搓本地知识库RAG(广度篇):混合检索+重排序,召回率翻倍

发布时间:2026/7/29 0:13:51
手搓本地知识库RAG(广度篇):混合检索+重排序,召回率翻倍 上一篇把文档解析搞定了数据质量从 27% 拉到了 91%。按理说接下来就应该是导入向量库提问出结果完美收官。实际情况不是这样。向量库建好之后我跑了一轮 50 条真实场景的问题测试。结果让我很失望。Recall10 只有71%——也就是说我将近三成的问题正确答案明明在向量库里躺着系统就是没找到。举个例子。我在知识库里放了一份 ISO27001 信息安全管理体系的审核报告。然后我问了三个不同角度的问题“ISO27001 审核中发现的主要不符合项有哪些”“信息安全管理体系那次审计有哪些问题没过”“那个安全审核报告里的不合格项”第一个问题 100% 找到了。第二个勉强算找到了相关段落但漏掉了一处关键内容。第三个直接翻车——向量检索完全不知道该去哪儿找。你仔细看看这三个问题的本质它们说的是同一件事但措辞完全不同。这就是纯向量检索的致命弱点它擅长近义词匹配但不擅长关键词精确匹配也不擅长模糊表达和概括性提问。于是我把整个检索管道拆开按问题的类型逐类去测看看到底哪些场景在漏。然后针对每种场景补上对应的增强件。环境多了点东西但没超出本地机器的能力在上一篇的 Ollama MinerU PaddleOCR 基础上检索增强这套又加了几个组件组件干什么占用Ollama qwen2.5:7bLLM 生成 HyDE 查询改写4.7GB GPUnomic-embed-text稠密向量检索274MBBM25稀疏关键词检索纯 Python不占显存bge-reranker-v2-m3Cross-Encoder 重排序2.2GB GPUFAISS向量存储和检索CPU 模式bge-reranker 是 568M 参数的模型跑在 GPU 上。加上 qwen2.5 的 4.7GB总共 GPU 占用大概 7GB——我的 8GB 显卡刚好塞下。如果你显存紧张可以把 reranker 放 CPU 上跑慢一点但能用。全套下来磁盘占用约 17GB包括上一篇所有东西。一台普通的 RTX 4060 开发机绰绰有余。为什么纯向量检索不够不卖关子直接上数据。50 个测试问题按检索特点分了四类问题类型代表例子Recall5Recall10精确关键词“ISO27001 审核报告”58%67%语义模糊“今年合规检查出了哪些问题”67%73%跨段落综合“对比 Q3 和 Q4 的退货趋势”50%63%口语化模糊提问“那个关于退款的文档”40%60%精确关键词类的问题向量检索为什么不行因为 nomic-embed-text 在做 embedding 的时候“ISO27001这种专有名词在向量空间里是一个平滑的语义点它周围的近邻是一堆安全”“审核”合规之类的泛化概念——反倒是那个真正包含ISO27001原文的 chunk在向量距离上排得靠后。换句话说向量检索在泛化和还原之间有天然的取舍有时候泛化过度了。混合检索两个字“互补”向量检不出来的东西往往反而是 BM25 的强项。BM25 是上个世纪就在用的稀疏检索算法原理很简单统计词频和逆文档频率做关键词精确匹配。它不理解语义但ISO27001在它的字典里就是ISO27001不会给你泛化成信息安全相关。我把两路检索同时跑然后用 RRF 融合结果。RRF 的意思是不管你是来自 BM25 还是向量检索按各自的排名给分——排第 1 的分高排第 20 的分低两者加权后重新排序取 Top-K。# RRF 融合的核心逻辑不算复杂 from langchain_community.retrievers import BM25Retriever from langchain_community.embeddings import OllamaEmbeddings from langchain_community.vectorstores import FAISS embeddings OllamaEmbeddings(modelnomic-embed-text) bm25 BM25Retriever.from_documents(chunks) vectorstore FAISS.from_documents(chunks, embeddings) dense_retriever vectorstore.as_retriever(search_kwargs{k: 20}) def hybrid_search(query, k10): bm25_docs bm25.invoke(query) dense_docs dense_retriever.invoke(query) # 两边各自打分RRF 公式: 1/(60rank) rrf {} for rank, doc in enumerate(bm25_docs): rrf[doc.page_content] rrf.get(doc.page_content, 0) 1/(60rank) for rank, doc in enumerate(dense_docs): rrf[doc.page_content] rrf.get(doc.page_content, 0) 1/(60rank) return sorted(rrf.items(), keylambda x: -x[1])[:k]跑完之后效果提升非常实在。Recall10 直接从 71% 跳到了 85%。单独看关键词类问题之前只有 67%现在 83%。语义类问题也从 73% 涨到了 86%。只有 0.04 秒的额外延迟换来 14 个百分点的召回提升这笔账怎么算都划算。重排序从找到了到排对了混合检索取 Top-20但我不可能把这 20 个全塞给 LLM。20 个 chunk 大概 2800 个 token既浪费又容易干扰模型。需要再过滤一刀。这个环节就不是用轻量 embedding 模型了——反正只剩下 20 个候选烧得起算力。我上了 BAAI 的bge-reranker-v2-m3这是一颗 Cross-Encoder它对每个候选 chunk 和用户的 query 做逐对打分能精确判断这一大段中哪些真正回答了用户的问题。为什么不在全库检索阶段就用它因为 218 个 chunk 逐对打分要花十几秒而只对 20 个候选打分只要 0.48 秒。# 重排序对 Top-20 候选做逐对精确打分 from FlagEmbedding import FlagReranker reranker FlagReranker(BAAI/bge-reranker-v2-m3, use_fp16True) def rerank(query, candidates, top_k5): pairs [[query, c] for c in candidates] scores reranker.compute_score(pairs, normalizeTrue) ranked sorted(zip(candidates, scores), keylambda x: -x[1]) return [doc for doc, _ in ranked[:top_k]]重排序之后Top-5 的 Precision 从 0.72 拉到了 0.91。这个数字很简单直白以前用户在 Top-5 结果里看到 3.6 个真正相关的现在能稳定看到 4.5 个以上。而且 LLM 收到的 token 量从 2800 降到了 1100缩减了 61%。输入越干净生成越快幻觉越少。HyDE让 LLM 帮你编一个问题再拿去搜还记得开头那个翻车的那个安全审核报告里的不合格项吗这类问题的特征很鲜明user 说得模糊、口语化跟文档里的措辞差别巨大。直接拿这种 query 去检索embedding 模型根本对不上号。HyDE 这招有点魔幻但非常好用——让 LLM 先猜一下这个问题的答案。比如用户问那个关于退款的文档LLM 可能会先编一段“关于退款政策公司规定用户在购买后 30 天内可申请退款需要提供订单号和退款原因客服审核通过后 3-5 个工作日到账…”这段编出来的假答案和真实文档里退款政策的措辞——两者之间的语义距离很近比原始 query那个退款的文档近多了。然后拿这段假答案去做向量检索。这背后一个直觉性的原理答案和答案之间的语义相似度大于问题和答案之间的语义相似度。这个方案对模糊查询的提升效果让我有点意外——5 个口语化的问题之前 Recall10 只有 60%HyDE 一下拉到了 88%。概括性问题也涨了约二十个百分点。代价就是多调了一次 LLM增加 1 秒左右延迟。所以我只对短查询少于 15 字和明显是口语化的输入启用 HyDE精确关键词问题跳过。没必要每个 query 都多等这 1 秒。父子分块找到位置之后把上下文开大前面几个增强件都是在检索环节下功夫。但检索完要喂给 LLM 看多大范围的上下文这事也直接影响最终答案的质量。我的做法是把切分做成两层。底层是小块300 token 左右拿来检索用。小块检索的好处是精准——因为 embedding 不会把一个 2000 字的长段落中具体问题的相关部分给平均掉。但同时每块都记一个 parent_id指回它所属的大父块——父块是 2000 token 左右的完整段落。检索阶段拿小块命中。命中的小块查出 parent_id把完整的父块喂给 LLM。这个策略最明显的变化是答案变完整了。之前用平铺的 500 token chunk 时LLM 经常回答到一半突然断掉因为它拿到的上下文就那么点。父块展开之后Faithfulness 从 0.87 涨到 0.94——上下文充足情况下 LLM 更不容易自己编。四件东西全装上去之后我在同一套 50 个问题上把四个增强件全挂上逐项看增量贡献逐步累加核心指标变化纯向量检索基线Recall10 71%Faithfulness 0.71 混合检索Recall10 85%14 个百分点 Cross-Encoder 重排序Precision5 0.91输入 token 减 61% HyDE短查询启用模糊查询 Recall 60%→88% 父子分块Faithfulness 0.87→0.94用 RAGAS 框架做了完整的四维评估对比进、出口两端的变化RAGAS 指标纯向量检索四件套全开Faithfulness答案是否忠于原文0.710.94Answer Relevance答案是否切题0.780.91Context Precision召回的上下文精准度0.640.91Context Recall需要的信息全搜到了吗0.590.89Context Recall 从 0.59 涨到 0.89 是最大的一跃——以前有 41% 的必需信息漏检现在降到 11%。这背后是混合检索和 HyDE 的共同贡献。延迟方面。完整管道含 HyDE一次查询约 3.6 秒如果跳过 HyDE 约 2.5 秒。最大的耗时还是 LLM 生成本身占了一半检索增强的部分加起来不到 1 秒。日常使用的话2.5 到 3.6 秒是完全可以接受的。最后聊聊该不该全上四个增强件不用全部打开。根据你的场景选择性用知识库里有大量专有名词/编号类文档混合检索必须开。用户提问通常比较口语化HyDE 给短查询开。文档有大段技术规范/长文父子分块优先。想尽可能省 token 提升生成质量重排序是第一步该做的事。数据全是从干净文本来的先做混合检索和重排序后两项不急着上。如果你是从零开始搭这套系统我的建议顺序是先把文档解析和混合检索做好——这两步吃掉了最大的收益。然后再按你实际遇到的召回短板逐步加上重排序和 HyDE。父子分块本质上是对输出质量的投资最后做。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】