RAG工程化实战:从分块策略到混合召回与质量评估

发布时间:2026/9/13 3:56:23
RAG工程化实战:从分块策略到混合召回与质量评估 构建RAG系统的时候很多人一开始都觉得这事挺简单把文档切一切塞进向量库查询的时候做一次相似度检索把结果丢给大模型就能回答私有问题了。但凡是真正上手做过的人多少都被教训过。切分错了检索结果看起来像“相关”但实际是噪音只用向量召回遇到精确数字、专有名词就疯狂翻车最要命的还是评估——没有一套质量评估体系你根本说不清这次改动到底是变好了还是变差了。这篇文章想聊的就是围绕一套真实可落地的RAG工程链路把分块策略、混合召回、质量评估这三块硬骨头挨个啃一遍。我会把项目里踩过的坑、试过的方案、最后稳定运行的参数都写出来顺便把最近比较热的“多路召回”“Agentic RAG”“Ontology RAG”和“本地知识库Ollama部署”这些点也串进去。适合谁看如果你正在搭RAG知识库、想把检索效果调得再稳一些、或者准备在团队里建立一套可复用的RAG质量评估流程这篇应该比你看十篇概念介绍都有用。1. RAG项目的整体设计思路拆解1.1 核心问题RAG不是把零件拼起来就行很多团队做RAG是从“先跑通一个Demo”开始的装个LangChain或LlamaIndex选一个向量数据库加载几篇文档一个能回答问题的机器人就出来了。但Demo能跑和系统能交付之间隔着好几个数量级的距离。真实业务场景里文档格式五花八门有PDF、Word、Markdown、HTML还有扫描件和表格。用户的提问方式也千奇百怪有人问“上个月的销售数据是多少”有人问“这个故障码对应的处理流程里第三步具体怎么操作”还有人直接问“之前那个叫张工的同事在处理这个问题时用了什么方法”。这些问题单靠“语义相似度”是搜不出来的。语义检索擅长处理“意思相近但表述不同”但在处理“精确匹配”“结构化查询”“跨段落推理”这些需求时通常得叠加至少一路关键词检索甚至还得引入路由和Agent式的多步检索。所以说到底RAG的工程化核心不是把工具堆起来而是把“文档怎么进系统”和“问题怎么出结果”这两头的策略设计清楚。1.2 方案选型背后的取舍逻辑我自己的路线是这样定的框架选了LangChain和LlamaIndex各试了一轮最终以LlamaIndex作为主流程LangChain只做局部辅助。原因不是谁更“高级”而是LlamaIndex对文档切分、索引构建、查询管道这些RAG原生概念的封装更贴合我们的需求改起来顺。向量库方面初期用FAISS做试验速度快但功能单一后来上线迁移到了Milvus原因是团队需要支持增量写入、过滤查询和多人协作。如果你只是个人用FAISS甚至Chroma都够了。检索上我把Embedding向量检索和BM25关键词检索同时做再用一个Rerank模型把两路结果合并重排这套组合在当时几乎所有候选方案里效果最稳定。大模型这块线上用的GPT-4o和开源的Qwen系列都跑过后来考虑到数据隐私和成本又在本地方案里测试了Ollama部署效果也能接受。选型最核心的标准其实就一条在可接受的延迟和成本内尽可能减少错误答案。而错误答案最大的来源就是“检索结果根本不对”。所以这篇文章的主线我会一直围绕“怎么让检索更准”和“怎么证明它真的更准”展开。1.3 什么样的场景适合什么样的RAG先说结论如果你的知识库是“事实型问答”比如规章制度、产品说明书、FAQ那传统RAG加混合召回基本够用。如果你的场景是“对话型任务”比如客服机器人需要根据历史订单、当前库存、用户情绪综合决策那我建议你关注一下Agentic RAG——让LLM在每一轮动态决定搜什么、什么时候搜、搜完还要不要接着搜。如果你的知识有很强的结构关系比如设备维修手册里“故障码-原因-处理步骤”是关联的或者法律法规里“条款-解释-案例”是层层引用的那纯靠向量检索很容把关联信息切碎。这时Ontology RAG值得试先构建一个领域本体把实体和关系显式建模检索时先定位到相关实体再沿着关系取上下文效果会比“一锅端”好很多。2. 核心环节一分块策略怎么定才对2.1 分块的本质给知识做“信息粒度管理”我在项目里说过一句经验RAG的上限由分块决定下限由召回决定。这句话听起来有点绝对但实践下来确实如此。因为Embedding模型把一整块文本压缩成一个高维向量时过长的块会被“平均化”重要的细节被稀释过短的块又缺乏上下文检索出来之后喂给大模型也答不到位。分块的本质是决定系统以什么粒度来理解知识。“粒度”大了块内内容杂检索精度下降“粒度”小了块数量多前后文容易断裂。理想状态下一个数据块应当是一个语义完整的单元。比如在操作手册里一块就对应一个操作步骤在制度文档里一块就是一个条款。“语义完整”这四个字比任何固定长度算法都重要。但语义完整是有代价的。用固定长度分块代码写起来简单计算成本低用语义或结构分块需要额外的处理逻辑还可能因为分块器本身的错误把文档切得更乱。实际项目里我不会只依赖一种切法而是给不同类型文档设计不同的分块管线。2.2 常见分块方式对比固定长度、滑动窗口、语义分块、结构化分块固定长度分块是最容易实现的按Token数或字符数切比如每512个Token切一块再让相邻块之间重叠64个Token。优点是好理解、好实现、任何文档都能跑缺点是遇到表格、代码、列表时经常切断逻辑。滑动窗口本质上也是固定长度只不过用“块-区域”的方式保留上下文适合句子比较长的场景。语义分块尝试在句子边界、段落边界或主题变化处切分。实现上可以用Embedding算相邻句子的相似度相似度低于阈值就断开。这种方法对散文类、论述类文档效果好但计算量大而且阈值需要反复调。结构化分块则是针对Markdown的标题、PDF的章节、表格的行列等结构化信息来切。比如一个Markdown文档可以按照“一级标题”拆成多个大块再在每个大块内按“段落”切这样切出来的块天然带层级信息检索时还能把父节点的标题作为上下文补进去。我在实践里用的是“结构化分块为主定长分块兜底”的组合能识别结构的文档走结构化切分识别不了的就用递归字符切分器按段落-句子-字符的优先级切兜底Token数控制在600左右。2.3 实操参数选择与我的测试结论关于块大小和重叠率我直接说一组可以起步的参数块大小600 Token重叠100 Token。这组参数在多个中英文混合数据集上跑下来检索召回率和最终答案准确率都比较平衡。你可能会问为什么不是512也不是1024。我的测试结果是块太小比如256单块信息不足大模型经常“读不懂”上下文块太大比如1024检索命中后的噪音明显增加重排阶段要花更多力气才能把正确答案拎出来。600是个折中值具体项目里你还得自己跑一组对比实验。重叠Token的作用是为了避免检索问到“前一块尾部”的内容时因为被切掉而没有命中。重叠太小小于32基本没用重叠太大超过200既浪费存储又会让相邻块之间的重复内容增加反而降低了检索的多样性。另外提醒一句切分之后一定要给每个块保留一个“来源字段”比如文档名称、页码、章节路径。这块信息在后续追查问题和做引用溯源的时候几乎是救命的。3. 核心环节二混合召回与多路召回的实现3.1 为什么单路检索不够用不少RAG教程只教你向量检索也就是把用户问题Embedding化再在向量库里找TopK相似块。这在“同义改写型”问题上表现不错比如用户问“怎么申请报销”文档里写的是“报销申请流程”向量夹角很近能召回。但在三类问题上向量检索会翻车第一类是精确匹配问题比如型号“K7M2”、编号“GB/T 19001”这类Embedding很难体现字符级差异经常把相近但不同的编号混在一起第二类是用词冷僻的问题比如用户问“磕碰的处置”文档里写的是“外观损伤的处理流程”向量模型如果没训练过这个场景召回率和正确率都不好看第三类是反义和否定型问题比如“哪些情况不允许报销”向量检索容易把“允许”相关的段落都当成答案。多路召回的思路就是别把鸡蛋放在一个篮子里。我用了两路基座检索一路是向量检索用Embedding模型把问题和文档块映射到同一个向量语义空间另一路是BM25关键词检索依赖词频和逆文档频率来匹配精确词。两路各自召回TopK然后合并成一个候选集合再做重排。这种方法在工业界叫“混合召回”或“多路召回”本质上就是用语义匹配扩大覆盖面用词汇匹配兜住精确命中。3.2 混合召回里最容易被忽略的三个细节第一向量检索的K值不要只取一个TopK。我在线上会同时保留多个层次的召回Top10给重排Top30给兜底Top50给日志分析。只取Top10会导致重排阶段的选择空间太小明明第15名是对的但前面10个都是噪音重排也无能为力。第二BM25检索的字段权重要调。同一个文档块里标题、关键词、正文的重要程度不一样。我在BM25索引里把“标题”字段的权重设为正文的1.5倍再给“标签”字段设1.2倍实测对命中率提升很明显。不调权重的话BM25在长正文文档里容易把真正属于核心标题的信息淹没。第三融合分数要稳定。最简单的融合方式是“分数归一化之后再按权重相加”比如vector_score叫相似度bm25_score是原始分两者量纲完全不一样直接相加就是一场灾难。我一般用两种融合方式之一要么倒排融合RRF把两路召回的排名序号拿来算不关心原始分天然无量纲问题要么让重排模型直接处理原始特征。二级标题尽量体现操作性和长尾词。3.3 重排序从“检索相关”到“任务相关”召回只是第一轮海选真正决定答案质量的是重排序。我的做法是把混合召回得到的候选块全部灌进一个交叉编码器Rerank模型常见的比如bge-reranker或Cohere Rerank让模型同时看到“用户问题”和“文档块”直接输出相关性分数。交叉编码器比双塔式Embedding模型精度高不少因为它在计算时能充分交互问题与文档的内容缺点是慢所以只能用在第一轮召回之后的候选集合上。重排序输出后我通常只保留Top3到Top5作为最终上下文送进LLM回答。如果保留太多LLM的注意力会被无关片段分散幻觉概率也会上升。这个“Top3到Top5”的数字不是拍脑袋而是我用评测集跑了多轮之后的结果Top1的准确率不够Top10的准确率够了但答案变啰嗦Top3到Top5刚好在准确率和生成质量上达到平衡。3.4 Agentic RAG与Ontology RAG带来的新思路传统RAG是一次性检索然后生成。但在复杂问题下比如“先查这个客户的历史订单再看退货原因同时查一下同类产品最近的故障率”一次检索根本做不完。Agentic RAG的思路是把检索权交给LLM让它像一个研究员一样第一步查什么、第二步根据结果判断还要不要查、什么时候可以开始写答案全由Agent自己决策。这种模式在“多跳问答”里优势明显但代价是延迟变高、成本变大也更容易出错所以必须配上严格的质量评估和失败回退机制。Ontology RAG则是在检索前先加一层“领域知识结构化层”。比如医疗知识库先定义“疾病”“症状”“药物”“禁忌”这些实体和关系再让文档切块时关联到对应实体。检索时先通过实体识别把问题定位到若干实体再沿着本体关系把相关文档片段拉出来。这样做的好处是答案的“关联性”不再只是文本相似度而是由领域逻辑约束的。缺点是构建本体的成本高不适合快速迭代的场景。4. 核心环节三质量评估与迭代闭环4.1 质量评估到底在评什么很多人做RAG质量评估只看“大模型答得对不对”这其实是不够的。一个完整的RAG质量评估至少要拆成三个独立维度一是“检索质量”二看“生成的答案是否忠实于检索到的上下文”三看“答案是否有用”。具体指标上可以参考RAGAS提出的一套框架Context Precision上下文精确率衡量召回的块中有多少真的和正确答案相关Context Recall上下文召回率衡量正确答案需要的信息是否都被召回了Faithfulness忠实度衡量生成的答案是否严格基于检索上下文有没有在上下文之外胡编乱造Answer Relevancy答案相关性衡量答案是不是答在了问题上。这四个指标加起来基本能把一个RAG系统的质量画像勾勒出来。不过指标不能只看平均分要按业务问题类型分开统计。我在项目里会把测试集分成“事实问答”“流程步骤”“比较分析”“开放讨论”四类分别计算指标。原因是一个系统可能在“流程步骤”上满分但在“比较分析”上惨不忍睹平均数会把这种差距糊弄过去你会以为系统还不错。4.2 评估数据集和评测流程怎么搭评估数据集是整个质量评估体系里最值钱的资产。理想的数据集应该有几百条甚至上千条“问题-标准答案-来源文档”三元组。标准答案不一定非得和人工写的完全一致但必须给出答案里应包含的核心要点和对应的依据片段。如果团队资源有限最少也要有100到200条覆盖主要业务场景的评测样本。评测流程上我建议做成自动化的CI回归测试。每次改动分块逻辑、换Embedding模型、调召回参数或改提示词都能自动跑一遍评测集输出各指标对比。我最早是手动跑后来实在跑不动了才改成脚本化。这个环节的重要性怎么强调都不过分没有评测集你所有的优化都是“我觉得好”有了评测集才是“数据说更好”。4.3 线上效果监控与反馈闭环离线评测集只能保证改动方向线上真实效果还得靠另一套监控。我在线上系统里加了三个日志维度一是每次检索的候选块Top5和最终用于生成的Top3二是LLM生成整个回答的回流三是用户点击和点赞行为。前两个是过程日志用来复盘“为什么这么答”第三个是结果信号用来判断用户是否满意。我还会定期从线上日志里抽一批“回答不够好”的case补充进评测集。这件事需要运营或业务同学配合不然工程师自己很难判断哪些答案是错的。每次迭代时我强制要求“评测集规模只增不减”新加进来的bad case会变成下一步优化的靶子。这个流程跑通之后RAG系统才算进入真正的迭代循环。5. 工程化落地用Ollama搭本地RAG知识库的实践5.1 为什么选择Ollama做本地部署如果你有数据隐私要求或者不想每次调用都掏API费基于Ollama搭建本地RAG知识库是个务实选择。Ollama的核心价值是把开源模型的下载、运行、管理封装得非常简单一条命令就能拉起一个LLM服务不需要自己配置Python环境、CUDA、依赖库这些坑。我的典型组合是Ollama跑Qwen2.5-7B或Llama 3.1-8B作为生成模型再用一个bge-m3做Embedding。纯本地方案的好处是文档和查询都不用出内网合规上的压力小很多。缺点也同样明显7B模型的能力上限比GPT-4这类云端模型低尤其在复杂推理和长文本理解上所以分块和召回策略更要做好否则模型一弱整体效果就更加拉胯。5.2 本地RAG的索引更新与存储设计本地RAG不是把文档一次性索引完就结束了。业务文档会更新规章制度会修订这就要求索引具备“增量更新”能力。我的方案是按文档ID做版本管理每次导入新文档时计算全文哈希如果哈希和上次一致就跳过如果哈希变了就删除旧块并重新分块、Embedding、入库。这样既避免了重复存储又防止旧版本内容残留误导检索。存储上我用的是SQLite保存文档元数据和分块映射关系向量存进Milvus或FAISS。之所以不把所有东西都塞进向量库是因为很多查询的过滤条件其实是结构化字段比如“只看2024年的制度”“只看XX部门的文档”这些用SQL过滤比向量库的标量过滤更灵活。先SQL过滤出候选文档ID再进向量检索整体效率会高很多。5.3 延迟、成本与缓存策略RAG的延迟主要来自三个环节Embedding查询、向量库检索、LLM生成。Embedding和检索的延迟相对可控百毫秒级别LLM生成才是大头尤其是生成长答案时可能要好几秒。我的优化思路是能缓存就缓存能短则短。对相同或高度相似的问题做一个语义缓存命中后直接返回历史答案不再跑完整链路。缓存键用的是“Embedding相似度BM25命中重叠”双重判断避免只靠阈值导致误命中。大模型的提示词里我也会明确要求“控制答案长度不展开无关信息”。这既省Token又缩短生成时间。别小看这个细节批量场景下一次省几百Token成本差异会变得很明显。6. 常见问题与排查经验速查6.1 高频问题与处理手段这里把我在实际项目中遇到最多的几类问题整理成一张速查表排查时可以对照着看。现象可能原因解决建议检索结果看起来相关但答案不对分块切断了关键信息或重排后关键上下文被挤出TopN检查分块边界调大重叠增加候选集TopN精确型号/编号总是匹配错误只有向量检索没有关键词兜底加BM25多路召回对编号做规范化处理大模型回答里出现文档没有的内容填充了“知识缺口”Faithfulness低下提示词强约束“只能基于上下文回答”无答案时明确说不知道类似问题不同问法效果差异巨大Embedding模型对领域词汇不敏感换更大的Embedding模型或在评测集上验证后再切换线上日志显示某类问题总是答错评测集覆盖不足抽取bad case入库重跑离线评测后再调整策略增量更新后检索到旧版内容版本管理缺失按文档哈希做增量替换旧块强制删除6.2 几个容易踩的深坑第一个坑是直接修改提示词但不评估。很多人觉得模型答得不好就调提示词调完感觉“好像好一点”结果上线之后又变差。原因很简单你没有量化评估感觉都是错觉。我后来养成的习惯是任何改动不管多小都要跑一遍相同评测集对比指标再决定是否保留。第二个坑是忽视文档解析环节的质量。OCR出来的扫描件表格转成Markdown后行列错乱PDF里文字顺序诡异这些都会让分块Embedding效果大打折扣。文档解析是整个RAG链路里最“脏”的活儿却往往是最容易被忽略的。我的建议是在解析后加一道“结构校验”比如检查表格行列数是否一致、标题层级是否完整不合格的文档打回预处理。第三个坑是重排模型没有跟Embedding模型配套调优。你换了Embedding模型向量空间分布就变了原来重排模型学到的相关性判断可能会局部失效。严谨的做法是每次更换Embedding或重排模型后都要做一整轮端到端评测而不是只看检索出来的TopK顺眼不顺眼。第四个坑是忽略“无答案”的应对。RAG系统并不需要对所有问题都硬答。有些问题知识库里根本没有答案硬套上下文生成结果往往是幻觉。我在系统里专门加了一个“拒答路由”当重排后的最高得分低于某个阈值时直接提示“知识库中没有找到相关信息”而不是硬着头皮生成。这个阈值是从评测集上跑出来的虽然会牺牲一点覆盖率但整体可信度高了很多。最后一个建议来自我个人的体会做RAG不是一次性的“搭完就完”它更像是做产品迭代。你把分块、召回、评估这三件事串成一个闭环哪怕初期效果一般只要数据在积累、bad case在收、评测集在扩容系统就会越滚越稳。反过来如果只把精力放在“换更贵的模型”或“调一个看似聪明的提示词”上而不去管检索和评估那系统永远都只能在原地打转。