ARTICLE DETAIL

资讯详情

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

RAG烂大街之后,六个决定生产级效果的关键分水岭

RAG烂大街之后,六个决定生产级效果的关键分水岭 最近总有人跟我聊RAG开口就是“这东西是不是烂大街了随便一个教程就能搭出知识库问答。”我通常不反驳只是让对方把demo跑起来然后问几个稍微拐弯的问题“你上传的那份财报里第三季度的现金流到底是多少”“某份合同里关于违约金条款的免责条件是哪几条”——十个里有九个会当场卡壳。RAG确实烂大街了但烂大街的只是那条流水线加载、切块、向量化、检索、拼接提示词。这五步谁都能跑通真正的分水岭在更细的地方恰恰是很多人不愿意深挖的部分。这篇内容想聊的就是那些决定RAG“能不能用、好不好用、敢不敢上生产”的关键节点。我会把六处最典型的差别掰开揉碎来讲。每一处都不是“选哪个库”的问题而是“为什么这么做”的问题。适合刚跑通第一个RAG demo、正准备把它往真实场景推进的朋友也适合已经上了线但被效果反复折磨的团队。1. 先把公开的流水线摊开看1.1 人人都会的“五段式”流水线今天的RAG框架无论是LangChain、LlamaIndex还是Dify都把流水线封装得异常顺滑。只要准备一批文档走完下面五个步骤就能得到一个看起来有模有样的问答机器人加载Loader读入PDF、Word、Markdown、TXT等文件。切分Splitter把长文本按固定字符数或句号切成一堆chunk。向量化Embedding用Embedding模型把每个chunk转成高维向量。存储与检索Vector DB把向量写进向量数据库查询时算相似度召回top-k。生成Generation把召回的chunk塞进prompt交给大模型生成回答。这五步就是那套“烂大街的流水线”。做个类比整个过程就像是把图书馆里的书先拆成一页页每页编号扔进一个巨型索引柜。用户来问问题时你根据关键词和语义先捞出一叠纸片递给一位写手让写手根据纸片内容组织答案。听起来很合理对吧问题在于一旦文档复杂、问题刁钻、场景真实这套流水线就会在原形毕露的同时产生大量“貌似合理但实际错误”的输出。1.2 从“能跑通”到“能干活”之间到底差了什么流水线能跑通和它能在正式场景中干活完全是两码事。我见过太多团队把demo演示得很漂亮一上真实数据就翻车。翻车的症状高度一致召回内容驴唇不对马嘴。问“年假可以拆分休吗”召回的是考勤打卡制度的段落。答案看似流畅实则胡编。模型自信地给出了一个结论但你在原文档里根本找不到依据。关键信息被淹没。明明文档里有明确数字模型却回答“未找到相关信息”。没有出处。回答无法回溯到具体文档和章节用户不敢信。出现这些症状时绝大多数人的第一反应是“换个更好的大模型”或者“把embedding模型换掉”。但根据我的实际操作经验绝大多数问题的根子不在模型而在流水线的上游环节——文档没有处理好、chunk切得不对、检索逻辑太粗糙、上下文没有组织好、效果没有度量。下面这六处才是一套RAG真正拉开差距的地方。2. 第一处分水岭文档接入不是“能读出来”而是“把结构还原出来”2.1 真实文档远比你想象的脏很多教程里的RAG用的是什么文档干净的TXT、规整的Markdown、排版简单的网页。但真实世界的文档是什么样扫描版PDF、双层PDF、多栏排版、跨页表格、页眉页脚、水印、公司印章、手写批注……我第一次做企业知识库时遇到的第一个PDF表面上能提取出文字但所有页面都是图片扫描件提取出来的内容全乱了。用这种数据喂给RAG无论后面用多强的模型都注定回答不准。文档接入是整条流水线的地基这里一旦出错后面所有环节都是拿垃圾做淘金。所谓“脏文档”常见问题有四类扫描件没有文本层必须OCR识别识别错了就是永久性错误。文本层存在但不完整原始排版顺序和阅读顺序不一致提取后段落前后颠倒。表格被拆得支离破碎跨页后列名和数值分离。页眉页脚、目录、注释混进正文成为干扰噪声。处理这些问题的关键不是“换一个更牛的Loader”而是针对文档类型做专门的解析管线。我目前的常用组合是这样的普通文本型PDF直接用PyMuPDF提取文本读取速度快对复杂版面有一定容忍度。扫描件或图片型PDF先用PaddleOCR识别PaddleOCR对中文支持比Tesseract好得多识别后再做版面分析尽量按阅读顺序拼接文本。表格密集文档用Camelot或pdfplumber抽取表格结构转换成Markdown表格再作为chunk存库。这一步很关键因为大模型读Markdown表格的能力远强于读被碾成一段文字的表格。Word内容用python-docx读取段落和表格并保留标题层级信息为后续的结构感知切分做准备。HTML页面用trafilatura提取正文它能去除导航、广告、页脚等噪声。2.2 “知识库能存图片吗”这个问题背后的多模态陷阱很多人问“RAG知识库能存储图片吗”这是一个典型的新手疑问。直接回答如果你只是把图片文件原样丢进文本向量库那是存不了的因为Embedding模型根本不认识图片的像素。但图片里的信息完全可以进入RAG只是要经过一道转换。正确做法是对图片做OCR或图像内容理解把像素转成文字。如果图片本身承载的是“描述性信息”或“版式信息”可以用视觉大模型如GPT-4o或本地Qwen-VL生成一段图片描述文字。将OCR结果或图片描述文本切块、向量化与正文一起进入知识库。比如你上传一个产品安装说明书里面的安装示意图是图片。直接存图没有意义但给图生成一段“图中显示将A零件插入B槽口用螺丝固定”的文字描述后用户问“安装时需要拧几个螺丝”检索系统就能通过这段描述正确召回。多模态RAG的核心从来不是“存不存图片”而是“图片里的信息是否变成了可检索、可引用的文本”。2.3 本地文本拆解工具的选择心得有热搜词在问“有没有本地的RAG文本拆解工具”。如果你对数据安全敏感文档不能外传那就必须搭建本地文档解析管线。我目前最顺手的组合是PyMuPDF做文本提取PaddleOCR做中文OCRpython-docx处理Wordtrafilatura处理网页Camelot处理表格。这套组合完全本地运行不依赖外部API而且满足95%以上的企业文档场景。注意选择拆解工具时别只看单步效果要看“整条链路”是否可控。比如OCR工具识别率很高但版面分析能力差输出文字的阅读顺序完全错乱后续切分照样受害。我踩过这个坑识别结果是逐行输出的但表格里“列名在奇数行、数值在偶数行”导致chunk上下文断裂检索永远找不到完整信息。后来改为“识别版面分析按阅读顺序拼接”三步走才把问题解决。3. 第二处分水岭分块不是“按字数切字符串”而是“按语义边界切知识单元”3.1 朴素切块为什么一定会翻车绝大多数教程会把分块策略写成“chunk_size500, overlap50”看起来像个参数配置实际上这是RAG里最需要动脑子的地方。固定按字符数切分有几大天然的坑容易把一句完整的话拦腰切断。尤其是中文按英文token数切分时句子可能被切开在完全没有语义关联的位置。容易切断表格、代码片段、列表项。表格头在上一块数据在下一块代码函数签名和函数体被拆开。语义边界被无视。两段讲同一件事的文字可能被分到不同chunk而中间夹着另一段无关内容导致检索时只召回了一半上下文。举个例子某公司规章制度里有一条“员工累计工作已满1年不满5年的年休假5天已满10年不满20年的年休假10天。”如果按固定200字符切分恰好在“不满5年的”和“年休假5天”之间切断那大模型看到的片段就是“员工累计工作已满1年”加一段无关内容。它很可能答非所问。这就是为什么很多人调大模型的prompt、换更强的模型都没有用——问题根本不在生成端而在切分端。3.2 三层分块策略越往上越显功力我在实际项目中把分块策略分成三个层级。初级做法是按换行符、句号做启发式切分中级做法是结构感知切分高级做法是配合父子分块和摘要分块。三者不冲突可以组合使用。结构感知切分 是性价比最高的一步。如果你用的文档来自Markdown、HTML或带标题样式的Word在解析阶段就应该保留标题层级然后在切分时以标题、段落、列表为单位而不是以字符数为单位。这样每个chunk自然就是一个“语义完整的小节”。LangChain的MarkdownHeaderTextSplitter、RecursiveCharacterTextSplitter都支持这类思路但更重要的是我自己在上游解析阶段就把标题信息带进了文本结构里而不是让切分器自己去猜。父子分块 解决的是“精确召回”和“上下文完整”之间的矛盾。做法是先用较大粒度生成父块比如整个章节再用较小粒度生成子块比如每个段落或每200字。检索时在子块上做向量匹配命中后返回其对应的父块交给LLM生成。这样既能提升召回精度又能保证模型看到完整的上下文。我做过一次合同问答用父子分块后关于“违约金计算方式”的回答准确率提升非常明显因为模型看到的不再是孤零零的一句法条而是整个条款上下文。摘要分块 的思路是为每个chunk生成一段摘要检索时先在摘要层面做匹配命中后再把完整chunk交给模型。它适合chunk数量巨大、重叠度高、噪声多的场景。代价是离线阶段会增加一次LLM调用构建成本变高但检索精度提升很明显。3.3 分块参数的“经验值”与取舍逻辑关于chunk_size和overlap我给不出一个万能值但可以分享一些选择逻辑。纯中文场景我常用“按语义段落切分为主字符数控制在400~800左右”。为什么是400到800因为太短的chunk信息量不足同时会让向量化结果丢失语境太长的chunk会让Embedding向量语义发散召回精度下降而且会占用上下文窗口。overlap我建议在10%~15%之间它的作用是缓解边界切断问题但不能把overlap当救世主根本解还是“按语义边界切”。如果文档是代码那么必须按AST或缩进块切而不是按字符数切。如果是FAQ直接把每个问答对作为一个chunk。如果是产品手册按章节和子标题切。我建议每次调整分块参数后都要回到检索测试集上看hit rate和MRR平均倒数排名而不是凭感觉说“好像变好了”。4. 第三处分水岭检索不是“相似度top-k”而是混合检索加重排序4.1 纯向量检索为什么会在硬事实问题上失灵现在的Embedding模型很强大但对“精确信息”并不敏感。我做过一个企业知识库里面有各种设备型号和工单编号。用户问“X-2000型号的维护周期是多少”文档里写的是“X2000设备每半年维护一次”。从语义上看X-2000和X2000确实很像向量检索也能召回但一旦用户问“编号A-1023的合同是哪份”纯向量检索经常把“A-1023”和“A-1033”搞混。数字、型号、日期、人名、否定词这些恰恰是向量检索的软肋。另一个常见问题是“语义相似≠正确答案”。问某公司2023年应收账款是多少top-3召回结果可能包含2024年、2022年甚至其他公司的数据因为它们语义都在“应收账款”这个主题上。这种场景下只看向量相似度就是在赌运气。4.2 混合检索让关键词召回补上向量模型的短板我的标准做法是“关键词检索向量检索”并行两者结果合并后再统一排序。关键词检索用BM25这类经典算法它对精确词、稀有词、编号、型号非常友好向量检索擅长理解和泛化同义表达。两者互补性很强实践效果显著。在工程实现上我有两种推荐路线如果团队已经在用Elasticsearch那恭喜你ES天然支持BM25和向量检索kNN可以同时做两路召回并融合结果。如果用的是专用向量数据库如Qdrant、Milvus、Weaviate它们大多也支持稀疏向量把BM25分词结果映射成稀疏向量后可以与稠密向量一起检索。合并策略不需要太复杂。最简单的可以用加权和关键词得分和向量得分各自归一化后取0.5/0.5更稳一点的做法是先看关键词精确命中是否达到阈值如果达到则优先关键词召回结果否则以向量检索为主。4.3 重排序模型两阶段检索里真正提准的关键我以为混合检索已经是很多团队的上限了但真正让RAG效果上一个台阶的是加了一路重排序Rerank。原理不复杂第一阶段让向量检索或混合检索快速召回较宽的结果比如top-20到top-50这个阶段追求“别漏掉正确答案”第二阶段用一个更强、也更慢的排序模型对这些候选做精排。常见方案是交叉编码器Cross-Encoder它会同时看到查询和文档的内容能捕捉到“两者是否真正相关”的语义关系精度远高于双塔结构的Embedding相似度。为什么双塔Embedding不适合做最后排序因为双塔为了效率查询和文档是分开编码的交互被压缩到最后一层向量点积里而交叉编码器让查询和文档在模型内部充分交互精度自然更高。代价是计算开销大不能对全量文档库跑只能在召回后的候选集上跑。本地可用的开源模型里BGE-Reranker系列在中文场景表现不错。实际操作中我的顺序是# 示例调用BGE reranker对候选集排序伪代码 query X-2000型号的维护周期是多少 candidates retrieve_top_50(query) reranked reranker.rank(query, candidates) final_top5 reranked[:5]加上重排序后我的知识库问答在回答“有没有依据”上提升最明显。之前模型经常引用不相关的片断加了重排后进入prompt的chunk相关性大大提高幻觉率明显下降。5. 第四处分水岭知识组织决定了RAG的思考天花板5.1 扁平向量库丢了“知识之间的关系”大多数RAG把知识库做成一个扁平的chunk集合所有文本被切碎各自向量化彼此之间不存在任何关系和层级。这对简单问答还算够用但一旦问题需要跨文档推理、需要理解“A依赖于B”或“B是C的子类”扁平向量库就会暴露出结构性弱点。举个例子一家公司的报销制度包含多份文档《差旅费管理办法》《费用报销流程》《借款及冲抵规则》。用户问“员工出差回来后机票费用应该按什么流程报销”这个问题需要把《差旅管理办法》里“机票属于差旅费”的条款和《费用报销流程》里“报销申请步骤”的条款拼在一起。纯向量检索可能召回了三份文档里的各一个chunk但模型很难判断这些chunk之间是什么关系更不知道应该从哪一份开始、以哪一份为准。5.2 不给知识建模至少做元数据和层级在引入复杂的知识图谱之前有两个性价比极高的中间方案。第一是给所有chunk打元数据。元数据可以包括文档来源、发布时间、部门、文档类型、权限级别、适用地区。检索时用元数据过滤先缩小范围再算向量相似度。这个操作不费太多事但效果立竿见影。我之前在检索前增加“发布日期部门”过滤后咨询“差旅报销”相关问题噪声数据减少了一大半。第二是充分利用文档本身的层级结构。解析文档时保留“文档→章节→小节→段落”的树状关系检索到一个子块后可以把它的父章节内容一起返回。这和上文的父子分块一脉相承。层级结构能让模型看到知识所处的上下文位置而不仅是孤立的碎片。5.3 从wiki和ontology到知识图谱到底是否值得做热搜词里同时出现了“wiki和RAG”和“ontology RAG”。这两个方向其实指向同一个问题高质量的知识组织。维基百科式的内容天然带有定义、目录、链接和分类非常适合RAG因为每个条目就是一个完整知识单元条目的“概述段”甚至可以当作摘要分块来用。如果你的知识源本来就是结构化的产品目录、法规条目、药品说明书、零件手册直接用这些结构效果远超把文本切成碎片。Ontology本体更进一步它定义概念、属性以及概念之间的关系。到底是否值得为RAG引入知识图谱我的判断标准很简单如果业务问题中存在大量“多跳关系”和“约束条件”比如“哪些药品不能与阿司匹林同服”“某型号配件兼容哪些旧机型”那就值得做。做法也分轻重轻量方案是先用LLM从文档里抽取“实体-关系-实体”三元组存入图数据库检索时先沿图找出相关实体再回原文档中找到对应证据重量方案是定义正式本体建立类层级和属性约束让检索沿着业务逻辑路径走。前者一两周能上线后者需要领域专家深度参与成本高很多只在强关系型业务里推荐。6. 第五处分水岭生成侧的上下文工程不只是在prompt里堆参考6.1 上下文不是垃圾桶塞得越多错得越离谱很多人提升RAG效果时第一反应是“把top-k从3改成5”甚至把召回的10个chunk全部塞进prompt。我一开始也这么干过结果发现回答不但没有更好反而开始出现奇怪的干扰。大模型的注意力是有限的当上下文里有大量无关或弱相关的内容时它会“淹没”掉关键信息。这就像你把求助信写在十页纸的杂物清单中间信息虽然都在但看的人很容易漏读重点。正确的上下文工程应该做减法第一相关性过滤把明显低相关的chunk移除第二去重召回结果里如果有多段来自同一章节的重复内容只保留最完整的一段第三按阅读逻辑重排把“结论”“定义”“证据”放在提示词更靠前的位置。这里我还有个实操习惯限定了上下文窗口内chunk的总字数比如最多2500字宁缺毋滥。6.2 引用溯源和“会拒答”的模型才是好模型RAG问答最容易被忽视的一点是要让模型“知道哪些答案它不知道”。我见过的多数RAG系统用户问一个知识库里没有答案的问题模型依然会自信地编一个答案。这不是模型的问题是提示词没有把“只能依据提供资料回答”的约束贯彻到底。我通常会在系统提示词里明确三件事必须在回答中标注来源格式类似“来源XX文档·第X章”。如果提供的资料不足以回答问题明确回答“根据现有资料无法回答”不要推测。多个来源的信息有冲突时明确指出存在冲突列出各来源的说法而不是强行融合成一个错误结论。为了让引用可机器校验我还会让模型输出结构化JSON字段包括answer、references、confidence。这样上游可以用代码校验每个reference是否真的存在于召回chunk中如果模型标记了某个来源但chunk里根本没有就说明出现了幻觉直接拦截人工修改。这一招对控制幻觉非常有效。6.3 RAG与Agent结合什么时候该“多轮检索”热搜词里有“RAG智能体”这也是我很感兴趣的方向。传统RAG是“一问一召回一回答”问题含糊时就会翻车。比如用户问“我们公司制度上是怎么规定的”这句话里没有明确对象单次检索不知道该查什么。Agent式RAG会让模型先分析这个问题需要哪些信息然后分轮次发出多个检索请求“考勤制度里关于请假的规定”“年假管理制度中关于申请流程的条款”再汇总多轮结果作答。更进一步的Agent还会做检索改写的循环第一轮召回结果空就改写查询词再试一次。这种“检索Agent”的引入需要把RAG从单轮流程改造成“模型可控的循环”规划检索需求、调用工具、观察结果、决定下一步。LangChain、LlamaIndex都有对应抽象Java生态里LangChain4j也支持这类链式调用。我的建议是如果单轮检索的hit rate已经不错只是偶尔会遇到模糊query可以先做一个“多轮改写”的小功能而不是一上来就上复杂Agent框架。7. 第六处分水岭评测与优化闭环没有度量就没有“变好”7.1 凭感觉调参越调越瞎很多人调RAG参数全靠“感觉”——多问几个问题觉得回答不错就算完事。这种评估方式的坏处是个人的几个测试问题完全不代表真实分布而且随机波动会被当成改进。比如你只是换了chunk overlap恰好撞上一个“看起来更顺眼”的答案就以为调参有效实际上换个问题就原形毕露。正确做法是建一个离线评估集。评估集至少包含50~100条真实问答对按业务高频问题、边界问题、无答案问题三类来构造。不要求一次性做到1000条先从业务里高频出现的50条开始比没有评估集强十倍。评估集建好之后每次改动上线前都在同一套数据上跑一遍对比指标再决定要不要上。7.2 RAGAS指标和检索侧指标要分开看我见过团队只盯着“模型回答是否像人话”完全不看检索质量。实际上RAG的效果应该拆成两个阶段分别评估检索侧指标Hit Rate正确答案是否出现在召回集中、MRR正确答案排在第几位、Recallk。生成侧指标用RAGAS这类工具计算的Faithfulness忠实度答案是否忠于提供的上下文、Answer Relevance回答与问题的相关性、Context Relevance召回内容与问题相关程度。为什么要分开看因为当端到端回答错误时你得知道问题出在“没召回”还是“召回了但模型没用上”。我的经验是先用检索侧指标排查召回把Hit Rate提到90%以上再去看生成侧指标。如果召回的chunk里根本没有正确答案换任何一个大模型都白搭。RAGAS这类工具现在支持用LLM作为裁判自动打分虽然不能完全替代人工判断但可以快速筛出大量明显badcase。我给的建议是离线用RAGAS跑一遍把Faithfulness得分低的样本捞出来人工再看一遍分析是检索问题还是生成问题。7.3 日志与线上反馈持续优化的数据燃料评测集是“静态”的线上用户提问是“动态”的。我负责过的RAG系统上生产之后最重要的动作就是记录每一条问答日志包括用户query、召回的chunk和得分、最终答案、用户有没有点击“点赞/点踩”、用户是否复制了回答等。这些数据是后续优化的最大富矿。有了日志之后做一轮持续优化闭环每周拉取点踩/复制失败率高的query人工分析badcase。如果发现某种问题反复出现就针对性地改进缺文档就补文档切分不合理就调分块召回不准就加重排或混合检索模型答非所问就调系统提示词和上下文组织。每次改动先在离线评估集上验证再灰度上线观察线上指标。提示把“无答案问题”单独当成一类评估样本别漏掉。很多RAG系统看起来指标不错是因为评估集里全是知识库能回答的问题。现实里用户总会问知识库之外的问题如果你发现模型在这些问题上频繁胡编说明“拒答”指令没有生效需要单独优化。我在实际推进RAG项目时最大的体会是六处不是要你一次性全部改造到位。你先定位当前系统最拖后腿的环节——如果召回结果离题万里就攻文档接入和分块如果召回结果相关但答案还在胡编就攻上下文工程和引用约束如果效果忽好忽坏说不清就先建评估集。先找到最痛的一处动手比抱着“换个更贵的模型”这种念头有效得多。RAG烂大街的从来不是这套技术方向而是那些不动脑子的复读式实现真正的分水岭说到底就是你有没有在这些细节上真正下过功夫。
返回列表