ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RAG检索不准?别只换向量模型,入库方案才是关键

RAG检索不准?别只换向量模型,入库方案才是关键 做RAG检索增强生成应用落地有两年多我几乎每周都会碰到同一种求助检索结果不对是不是该换个更强的向量模型但等我到现场看了数据九成问题的根子根本不在向量上而在入库方案上。很多人把“RAG”简单地理解成“把文档切成小段、转成向量、塞进向量库”认为只要这三步做到位检索就该准。可现实是不同文件有不同的脾气扫描件里藏的是图片表格的语义在行列关系里长文档的逻辑在章节结构里代码的逻辑在函数边界里。把这一切都塞进同一个向量库里等于把所有书都按厚度摆上书架却不看分类和目录检索当然不准。今天这篇我就把自己调RAG坑里爬出来的经验聊透——从文件分类、分块策略、OCR、表格重建到检索端的混合召回一步步说清楚什么叫“不同文件就该有不同的入库方案”。1. 先纠个偏为什么检索不准九成锅不在向量1.1 检索链路上一共有五道关卡embedding只是其中一环很多人一想到RAG脑子里浮现的就是embedding好像检索质量全靠向量模型决定。但把完整的RAG链路拆开看从文件进库到答案出来至少要经过五道关卡解析文件内容能不能被正确读出来、切块文本以什么粒度存储、向量化embedding映射到向量空间、索引用什么结构组织、带什么metadata、召回与排序怎样把候选集找出来并排好序。这五道关卡就像一条流水线embedding只是中间的“编码员”。流水线前端的解析和切块如果出了问题信息在入口就已经丢失或错乱了后面的向量模型再强大也只能在一个残缺的世界里找答案。我见过太多团队文件是扫描件却不做OCR结果向量库里存的是一堆图片的默认描述或者表格被按固定字数切得稀碎问“第三季度总收入”时答案散落在四个不连续的chunk里。这种情况换成OpenAI的embedding也一样没救因为输入端就错了。1.2 向量模型之间的差距远小于切块方式带来的差距我做过一个比较糙但很说明问题的实验同一份PDF用bge-m3和另一个更强的商用embedding模型分别向量化在同样的query下对比hit rate召回命中率好的模型大概能领先几个百分点。可当我改了分块策略把固定300字符硬切改成按段落边界切hit rate直接从60%出头跳到了85%以上。几十个百分点的提升足以把换模型带来的几个点提升淹没掉。这是很多人的直觉盲区。大家总觉得“向量模型检索质量”但embedding解决的是语义相似度计算问题它只能保证“意思接近的文本向量更近”。如果入库切块时把完整的一句话从中间劈开把表格拆得只剩下零散单元格把图片内容直接忽略那向量空间里根本没有可以召回的正确内容。说白了向量模型是翻译官不是资料管理员。资料都没放对位置翻译官再强也不知道去哪找。1.3 先分清你到底是召回失败、排序失败还是生成幻觉排查检索问题时第一步不是急着改embedding而是先判断问题出在哪个环节。三种失效模式症状完全不同。召回失败指的是与query真正相关的文档压根没进入候选集。比如问“合同第三条的违约金比例”但第三条被切块时和第五条混在了一起或者表格被拆散正确答案没被单独建模这导致检索结果里根本没有可用的信息。改分块、补OCR、加结构化解析才是解药。排序失败指的是正确答案其实已经出现在召回结果里了但排在top5甚至top10之外。这时换向量模型影响很小重点应该放在rerank重排上或者调整召回策略让正确片段更容易排前。生成幻觉指的是检索到的片段是对的但LLM没把答案完整输出或者被无关片段带偏了。那问题出在prompt和生成侧向量模型更是无辜的。我把线上问题的表现和这三个环节对照之后发现大约七成是召回阶段的问题两成是排序真正纯粹卡在生成侧的不到一成。所以当你觉得“RAG检索不准”时先别急着摸向量模型的调参旋钮——按上面三条对号入座找到真正出问题的环节才谈得上优化。2. 为什么“把所有文件embedding进同一个库”是偷懒做法2.1 坑一扫描件和图片被当成透明人企业中大量的历史资料是扫描件比如合同、银行流水、盖了章的审批单。这类文件看起来是PDF实际上就是一页页的图片。很多入库流程会直接用文本解析器去读它结果要么读出一堆乱码要么读出的内容为空于是向量库里什么都存不进去——或者更糟存进去的只是文件名。等用户问“这批合同里有没有约定超过15%的违约金”时系统当然答不上来因为真正的文本从未出现在向量库里。这不是向量模型的问题而是解析环节压根没做OCR光学字符识别这一步。正确做法是把扫描件先送进OCR引擎把文字“挖”出来再把识别出的文本作为后续处理的基础。所有步数都做好了向量模型才有内容可找。2.2 坑二表格被切得稀碎如果说扫描件的坑是“看不见”那表格的坑就是“亲密关系被拆散”。表格的语义不是靠一行行文字独立表达的而是靠行、列、表头的交叉关系。比如一张产品参数表第一列是型号第一行是“重量”中间的值是“1.5kg”——这个信息的完整含义是“型号AS-200的重量是1.5kg”。但固定长度的常规切块不考虑这个结构。它可能把表头切到上一个chunk把数值切到下一个chunk上下文一断语义就飞了。用户问“AS-200的重量是多少”时检索系统可能在库里找到“AS-200”所在chunk却偏偏找不到“重量1.5kg”那一格甚至把“AS-300”的数据当成答案。这种问题你换十个向量模型都解决不了必须从入库角度为表格设计专门的方案。2.3 坑三长文档按固定字数硬切把上下文拦腰截断长文档是另一个重灾区。运营手册、调研报告、产品白皮书动辄几十上百页。最简单的分块方式是设定一个固定长度比如每512个token一刀再搭一点重叠。这样实现起来确实省事但它完全无视文档本身的语义边界。我见过一个案例一份50页的技术方案某一章的核心结论落在上一章的末段硬切后结论和论证被拆到两个chunk里检索时无论怎么调阈值都只能召回一半内容。固定切块的问题在于它还容易把列表拆散、标题与正文分离、段落首位割裂。这并不是说固定切块完全不能用而是说它只适合内容本身没有结构的纯文本对于有明确标题层级的长文档按标题和段落边界切效果要好得多。信息密度高的表格和文档更不能用一刀切的方式。2.4 坑四代码仓库被当成散文库现在好多人把代码仓库直接摸进RAG当知识库用这是另一种风格的一刀切。代码的语义边界不在字符长度里而在函数、类、方法、模块的结构边界里。按500字符硬切一段代码轻则把函数签名和它的注释分开重则把一个if-else块拦腰斩断检索出来一坨没法读的残片。另外代码仓库还有大量天然噪音构建产物、依赖锁定文件、自动生成的代码、上千行的配置文件。这些内容如果不过滤会大量占用向量库容量还会在检索时频繁“抢镜”。所以代码类文件入库至少要做到按语法树边界切分、按文件类型过滤、把注释和代码关联存储而不是用处理散文的方式来处理源码。注意以上四个坑有一个共同点——它们都是在入库阶段就产生了信息丢失。信息一旦丢失后面的向量化、索引、召回全部变成在“残缺的地图”上找路。这就是为什么我始终坚持RAG 优化的第一优先级永远是入库方案而不是向量模型。3. 实操一套可分文件的入库方案可直接抄作业3.1 入库的第一步先给文件做体检和分类一谈到入库很多人第一反应是选向量数据库、选embedding模型但真正专业的第一步是对文件做一次“体检”搞清楚它属于什么类型。分类维度有三个信息载体纯文本、扫描图片、混合排版、结构程度是否含有表格、JSON、层级标题、语义单元特征以段落为单元、以行记录为单元、以函数为单元。建议在入库管道里加一个前置分类器。最简化的做法是用文件名后缀加内容抽样判断PDF先尝试提取原生文本抽几页看文字量扫描件文字量为零或极少需要OCRExcel、CSV天然是结构化数据有大量.py、.java后缀的是代码库。分类清楚后再为每一类匹配专门的入库管道。很多RAG框架和知识库搭建项目其实本质上就是在帮你做这件事只不过多数人图省事跳过分类直接统一入库。3.2 方案A文字版PDF与长文档——语义切块加父子块索引对于纯文字版PDF和长文档我的推荐方案是“语义切块父子块索引”。具体操作分四步。第一步用解析工具抽取版式和文字保留标题层级信息例如用PyMuPDF或pdfplumber处理PDF用docx库处理Word。第二步按标题、段落等语义边界切块而不是按固定字数硬切。chunk_size可以参考300至800个tokenoverlap设10%到20%即可。第三步建立父子块索引把大段落比如一个完整章节当作“父块”存储把其中切出的小片段当作“子块”检索时用子块做召回匹配但把命中的子块对应的父块内容一并交给LLM作为上下文。这样可以同时兼顾召回的精准度和生成上下文的完整性。在LangChain生态里这个模式就是ParentDocumentRetriever。# 语义切块的关键点按段落/标题边界切而不是按字符数硬切 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, ], )我这么调整之后最常见的变化是之前问“方案的资金预算”时返回的是预算表附近的一段无关描述调整后能正确回到包含“总预算”标题的那一节上下文完整带出具体数字。3.3 方案B扫描件和图片——OCR加多模态描述双轨入库扫描件和图片类文件的入库方案核心是“OCR多模态描述”双轨并进。OCR负责把图片里的文字还原成可检索文本。工具选择上中英文混排场景我推荐PaddleOCR它在这类场景下识别效果不错工程化也比较省心纯英文场景用Tesseract也能胜任。OCR的常见失误点有两个一是版面混乱表格中的文字被识别成横排文本二是公式、手写体、印章遮挡容易出错。我的处理办法是OCR出文本后不要直接按普通文本入库而是尽可能带上版面结构信息表格区域、标题区域并按区域组织内容。更关键的另一轨是为图片本身生成一份语义描述并存成独立的索引。如今可以用多模态大模型为图片生成一段详细的caption比如“这是一张柱状图展示2024年四个季度营收其中第四季度最高为2000万元”。这段描述会作为图片的“文字化身”参与检索。于是用户问“哪个季度营收最高”时即使OCR没识别出图里的字语义描述也能把这张图召回来。最终图片本身存一份原始文件便于查看文本描述存一份作为检索入口图文检索的能力就有了。3.4 方案C表格与结构化数据——行级重建该走图谱就走图谱表格的处理不能套用普通文档的分块逻辑。我的做法通常分两步走。第一行级重建。每行记录转成一个自包含的文本单元把表头信息拼进行内容里。比如产品参数表的表头是“型号 / 重量 / 电压”某一行是“AS-200 / 1.5kg / 220V”我会把它重写为“型号AS-200重量1.5kg工作电压220V”。这样每个行单元独立成chunk查询“AS-200 重量”时就能精确命中。这一步很简单但对表格型召回有立竿见影的效果。第二信息密度高、关系复杂的结构化数据直接走图谱或关系表路线。比如几十个SKU的供应链数据每一行都重写一遍会导致重复性描述挤爆向量库此时更靠谱的方案是让RAG退后半步把数据存进数据库或图数据库用NL2SQL或基于本体的查询来精确抽取。这与目前很受关注的“ontology rag”和“GraphRAG”思路是一致的——先为领域数据定义schema和关系再让模型在这个结构化底座上做检索。本质上这还是在实践“不同文件不同入库方案”只不过把入库目标从“向量库”换成了“图谱或关系库”。3.5 方案D代码仓库——按语法结构切别按字符硬切代码类文件入库我会用tree-sitter做语法解析把切分边界设定在函数、类、方法上每个函数或类作为一个独立的chunk。这样做的好处是检索回来的代码片段往往是完整可编译的方法体而不是被切了一半的残句。同时不要丢掉代码的注释。很多项目会在函数上写docstring这部分要单独提取出来和函数本体做关联索引。这样用户问“findUser是怎么实现的”时既能通过docstring里的描述精确召回又能拿到完整函数体进行上下文生成。最后记得做噪音过滤把node_modules、构建产物、生成代码、锁文件等排除在入库范围外。3.6 一个综合例子一份含图、表、长段的报告完整走一遍入库流程假设要入库一份“2024年新品发布报告”里面既有产品参数表格、柱状图又有大段的FAQ问答。旧的偷懒方案是全文件整体embedding结果问FAQ问题时还能凑合回答问表格和图表数据时基本翻车。用分类型入库方案后流程是这样先做文件体检识别出三个主要区块。表格区块走3.4的行级重建方案把参数表每一行都转成自包含的文本单元存入向量库的typetable分区同时把这些参数行也同步进一份结构化JSON以备精确查询。柱状图区块走3.3的多模态描述方案用多模态模型生成“各季度营收对比”的语义描述图片本身存文件索引。长段的FAQ走3.2的父子块方案按问答对边界切块每个“问题答案”作为一个父块。这样混合入库后整个知识库内部形成了多种索引类型向量索引管语义相似BM25倒排索引管关键词精确匹配元数据过滤器管文件类型和区块类型结构化JSON管精确数值查询。用户问“哪个季度营收最高”多模态描述能召回柱状图对应区块用户问“AS-200重量”行级重建的表单元能直接命中用户问“FAQ里怎么处理退货”父子块能带出完整回答。单靠一种向量库显然是做不到这个效果的。提示这里有个小细节每个chunk都要带好metadata——来源文件名、所在页数、区块类型、章节路径。metadata齐全后面的过滤、rerank、引用溯源都会轻松很多。我踩过的坑是前期不带类型字段后期想按类型做差异化召回时只能把全库重灌一遍。4. 检索端向量只是其中一路召回别把宝全押在它身上4.1 混合召回为什么是标配向量负责语义BM25负责精确入库方案改好了检索端的策略也要跟着升级。我一直强调的观点是不要把召回的希望全部寄托在向量相似度上。向量擅长“意思相近但字面完全不同”的语义匹配比如“押金什么时候退”和“deposit refund timeline”但BM25这类关键词检索也有它不可替代的优势——处理精确的、字面重叠度高的查询比如“合同第三条”“税率5%”“AS-200型号”。举个例子用户问“合同第三条的违约金比例是多少”向量召回容易把“第三条”和“第五条”搞混因为语义上它们都是合同条款但BM25可以精确地根据“第三条”“违约金”“比例”这些词来锁定对应区域。所以成熟的生产级RAG基本都会采用双路或多路召回向量召回一遍BM25召回一遍然后再融合。在工程上Elasticsearch这类支持densesparse混合检索的系统或者向量数据库里保留一个倒排索引都是这条路线的具体落地方式。4.2 rerank把排序从“向量距离”交给“交叉编码器”召回环节的目标是“别漏”排序环节的目标才是“排准”。向量召回后top50里通常已经有了正确答案但top5不一定有。如果直接把top5交给LLM排序上的偏差会被放大。所以我会在召回之后接一个rerank步骤。rerank最常用的模型是cross-encoder把query和文档片段拼在一起送入模型得到更精细的相关性打分。这个环节的效果立竿见影同样是top5有没有rerank差距很大。代价是它比bi-encoder双塔向量模型慢所以常规做法是先粗召回top20到top50再rerank精排到top5或top10。我一般在系统里配两层第一层向量加BM25粗召回第二层cross-encoder精排两者缺一不可。4.3 融合的坑分数归一化与权重多路召回之后怎么把向量分和BM25分融合成一个最终排序这里藏着一个常见的坑。BM25的得分范围受文本长度影响很大可能是0到30的数值向量相似度则常见在0到1之间。如果直接把两个分数相加等于让BM25的原始分数主导了排序向量召回的贡献被稀释到可以忽略。更稳妥的做法是先把两路分数分别做归一化再融合或者使用RRFReciprocal Rank Fusion这类基于名次的融合方式。RRF的思路很简单——不看具体分数只看每个文档在每路召回中的排名然后用一个倒数公式融合排名。这种方式的优点是对异常分数不敏感工程上也更容易维护。权重方面如果你的领域里精确关键词更重要可以适当调高BM25的权重如果语义扩展更重要则调高向量权重。4.4 agentic RAG 与 Ontology RAG入库方案正走向“越分越细”聊完基础的检索端再说说最近特别热的方向Agentic RAG和Ontology RAG。Agentic RAG的核心思路是让LLM扮演调度者根据用户意图选择不同的工具和检索入口——这意味着系统里有不止一个知识库、不止一种检索方式。比如用户问产品参数时走结构化查询接口问文档政策时走向量检索问代码问题时走代码索引。这本质上就是把“不同文件不同入库方案”做成了可被模型动态调度的多库架构。Ontology RAG和GraphRAG则在入库阶段走得更远它们会给数据建立本体schema和实体关系网络把文档、表格、实体、关系都显式建模检索时既可以用向量也可以沿着图谱关系做多跳查询。比如此前提到的那种“FAQ、参数表、报告混合”的知识库用Ontology RAG建模后就能得到“某个参数属于某个型号、出现在报告第几页、FAQ里提到过它”这种关系链。你会发现这两个趋势走到最后都不是在“把向量模型换得更强”而是让不同的信息结构进入不同的索引形态再让调度逻辑决定走哪条路。这恰好印证了标题那句话不同文件就该有不同的入库方案。5. 从“感觉不准”到“量化验证”RAG调优评测实操5.1 先把“不准”翻译成指标改入库方案到底有没有用不能靠“感觉”。我一般会先固定一套baseline指标最常见的是hit rate召回命中率以及recallk、MRR、答案准确率。hit rate衡量的是针对一组评估query正确答案是否出现在召回的top候选里。它直接反映入库和召回的质量不受生成侧影响非常适合用来验证改入库方案的效果。评估脚本的思路很简单准备一组query和每个query对应的期望答案片段跑一遍检索流程看正确答案是否在返回结果中。下面这个骨架可以作为参考total len(queries) hit 0 for q in queries: docs retriever.retrieve(q, top_k5) if q.expected_fragment in docs: hit 1 print(hit_rate:, hit / total)我强烈建议把这一步做成CI的一部分改一次入库配置就跑一遍避免重构之后效果悄悄倒退。5.2 挑query像给产品提bug一样认真评测集质量决定了调优方向的正确性这里最忌讳的是用LLM凭空造一批测试问题。我常用的做法是从线上用户日志里抽取真实query因为真实query才包含各种奇怪说法、错误措辞和口语化表达。数量不必多二三十条覆盖主要业务场景就够关键是每条query都要标注它期待的答案应该出现在哪个文件哪一段。如果团队维护成本有限可以先做top10高频问题效果也很明显。5.3 A/B验证改完入库方案怎么确认有效确定评测集后在新旧两套入库方案上分别跑同一个评测脚本重点看hit rate和top3命中率的变化。比如旧方案固定切块时hit rate是62%新方案按语义切块后跑到86%这就是一个真实的提升。如果只提升了3到5个百分点而且测试集基数很低我的判断标准是先不要急着欢呼再扩大query集验证确认不是偶然。另外提一句hit rate只衡量召回阶段当召回稳定之后还要单独评估生成阶段的答案正确率。方法可以是在固定上下文的情况下对比不同prompt的生成结果这一步与检索解耦定位问题更干净。5.4 常见问题速查表一表定位问题在哪个环节为了节省排查时间我把最常见的症状整理了对应关系分享出来症状问题环节排查动作推荐方案明明有这个数据库里就是查不到入库遗漏或解析丢失检查原文件是否扫描件、解析后的文本是否完整补OCR检查解析日志比对原文与入库文本能查个大概但总是答非所问切块把上下文切碎了翻看命中的chunk前后文是否完整按标题/段落边界切启用父子块正确答案有出场却老是排不进前几名排序不足观察正确答案在第几个返回结果中出现加rerank模型调融合权重或RRF表格、参数类问题一塌糊涂表格结构被破坏检查表格被切成了几块、表头在哪行级重建结构化数据走图谱或关系库代码问题返回残缺代码代码按字符硬切观察返回片段是否为完整函数用tree-sitter按函数/类边界切片段是相关的但答案数字不对生成侧幻觉核对上下文有无矛盾片段prompt是否限制了引用范围降低温度要求必须引用原文对关键数值单独做校验图片/图表信息完全参与不了检索缺少多模态处理确认原始文件的不透明区域用多模态模型生成语义描述与OCR文本双轨入库这张表不算完整但它能帮你快速把“检索不准”定位到入库、召回、排序、生成四个环节中的具体一个省去大量瞎试的时间。最后说一个我自己的实践体会入行头半年我几乎把所有调优时间都花在了刷向量模型和向量数据库参数上效果一直原地踏步。后来老老实实从文件分类、解析、切块这些“土办法”做起才把检索质量真正拉了上来。RAG 这个系统入口决定了上限向量模型只是兜底。下次再觉得检索不准动手改任何代码之前先问问自己我的文件真的用对了入库方案吗
返回列表