资讯动态

RAG工程化六大分水岭:从解析、检索到评测的实战指南

发布时间:2026/10/3 11:04:52 来源:尧图企业网站定制
RAG 烂大街了。这是我这半年在技术群和面试场合听到最多的一句话。说这话的人手里多半有个用 LangChain 三行代码搭出来的知识库问答 demo——读文件、切文本、跑 embedding、塞进向量库、检索、拼 prompt、丢给 LLM一套下来连半小时都用不了。确实这套流水线已经普及到不能再普及网上随便翻一份“RAG 教程”80% 都是同一个套路连 chunk_size 都是抄来抄去的那几个。但我一直觉得烂大街的只是那条流水线不是 RAG 本身。恰恰相反真正把 RAG 从 demo 推到生产环境的人从来不在“装库跑通”这个层面较劲他们的精力几乎全花在六个地方。这六处才是 RAG 工程真正的分水岭也是我最近一年陆续接手、复盘了几个知识库项目之后最想分享的东西。如果你正在搭 rag知识库如果你觉得 rag实战 里“装上就跑”和“跑起来真能用”之间差了不止一个量级或者你正被知识割裂、rag hit rate 上不去这类问题卡住这篇内容应该能帮你把注意力从流水线挪到真正的攻坚点上。下面按我自己的优先级把这六处分水岭挨个拆开讲每处都会带具体参数、代码片段和踩坑记录。1. 分水岭一文档解析与切分——决定 RAG 上限的脏活累活很多团队对“解析”的理解就是“读文本”。生产环境里哪来那么多干净的 TXT几乎全是 PDF、Word、扫描件、网页、PPT。PDF 有单栏双栏、有表格、有页眉页脚、有各种流程图扫描件要先 OCRWord 里可能还挂着批注和修订。直接用 PyPDF2 把 PDF 抽成纯文本双栏文章会抽成一整行乱序文字表格数据全部搅在一起这种输入到了检索阶段效果能好才怪。1.1 解析从“读文本”到“理解版式”我把解析分成四个层次按成本从低到高排列文本抽取层PyMuPDF 处理 PDF、python-docx 处理 Word只拿纯文本和基本元数据。布局分析层用 pdfplumber 或 LayoutParser 先做版面识别把一页拆成标题区、正文区、表格区、页眉页脚区。双栏文章这时候才能按栏读而不是从左到右一锅端。表格抽取层数据密度高的表格直接进向量库是灾难。用 Camelot、TableTransformer 之类把表格结构还原成二维数据再转成 Markdown 或多行键值对文本。OCR 层扫描件和图片型 PDF先走 PaddleOCR 或 Tesseract识别出来的文字再进上面三层。这一步很多人嫌重但你手里只要有一批扫描合同跳过去就等着检索效果崩。实操里最常见的坑是“文档体检没做”。我接手过一个项目团队把一堆 PDF 丢进一个通用解析函数结果其中有 30% 是扫描版解析出来全是空行。排查了半天才发现不是检索模型的问题是根本就没读到内容。所以动手之前先写个脚本统计一下总页数、表格数量、每页抽出的字符数、疑似扫描页的数量。根据这个体检结果再决定解析管线怎么配而不是一套函数吃天下。1.2 切分别再用固定窗口撞大运切分是 RAG 里讨论最多、也最容易出错的一步。常见策略有这么几种切分策略优点缺点适用场景固定窗口切分实现简单、参数可控很容易切断语义一句完整结论被劈成两半格式统一的通用文档父子块切分检索用小块生成用大块兼顾精度和上下文逻辑复杂存储和查询都要多留一层FAQ 型知识库、答案需要上下文支撑的场景语义切分按语义边界断句切出来的块更“自然”要额外算一遍 embedding 或分类成本高长文、逻辑性强的制度文档结构感知切分按 Markdown 标题、PDF 段落、列表层级切依赖源文档结构足够规整有清晰标题体系的文档、HTML 转文本我见过最蠢但不罕见的模式不管什么文档一律 chunk_size500、overlap50 硬切。遇到一个长段落讲到三个不同主题向量被平均稀释用户问其中任何一个主题都检索不到精确内容。反过来切太碎也不行——检索 Top-K 里全是“半句话”拼给 LLM 看的都是上下文缺失的碎片生成出来的答案东一句西一句毫无依据。真正的做法是把“检索粒度”和“生成粒度”分开想。检索用小块提高命中精度生成用父块补充完整上下文这就是父子块方案的核心逻辑一个块是用于检索的子块一个块是把它连同上下级结构一起包起来的父块先把子块查出来再拿父块的内容去喂 LLM。它比单纯调大 chunk_size 高明得多因为大块只是“更长”父块是“结构完整”。1.3 切分配参的实战经验关于参数别直接抄别人的。chunk_size 跟你选的 embedding 模型强相关text-embedding-3-small 支持到 8K token但塞满 8K 并不意味着检索更准反而可能引入大量无关信息把向量稀释。中文场景我自己的经验是单块 300-800 字比较稳overlap 控制在 10%-20% 之间主要目的是让跨块边界的关键句不会因为恰好被切开而失联。判断切得好不好别靠感觉。掏 50 个真实问题出来查一下每条问题的“正确答案到底落在哪个块里”看看是不是每次都能落到同一个语义完整的块上。如果答案是“正确答案总被劈成两半”那问题就出在切分策略上换个切法比换个 embedding 模型管用得多。解析和切分属于 RAG 的底座工程这块不扎实后面所有分水岭都无从谈起。2. 分水岭二检索质量——Hit Rate 是一切的地基2.1 先认识 Hit Rate 这个数字先定义一下 hit rate在检索返回的 Top-K 结果里“包含正确答案”的比例是多少。它只关心正确答案有没有被捞上来不关心答案最后生成得好不好。很多团队一上来就死磕 prompt 模板、换大模型生成效果却始终不行原因往往不是生成环节弱而是检索环节压根没把对的资料捞出来。我在一个订单文档问答项目里初始 hit rate 只有 46%无论怎么优化 prompt答案质量都上不去后来把 hit rate 干到 89%生成质量才真正迎来质变。检索失败通常有三种典型原因词表鸿沟用户问“报销额度上限”文档里写的是“可报销金额最高为”。两句话语义相同但字面完全不一样纯向量检索对这种情况的覆盖率有限。语义漂移一个 chunk 里塞了三个不同主题embedding 把整段向量往中间地带推用户查任何一个主题都差一点。领域术语在医疗、法律、制造这些领域通用 embedding 模型对专有词根本没有概念查“GB 4706.1 标准”这种编号时向量空间里完全抓瞎。2.2 三件套混合检索、重排、Query 改写应对词表鸿沟和领域术语最立竿见影的手段是混合检索BM25 负责关键词精确匹配向量检索负责语义匹配两边结果做融合。BM25 吃“报销额度上限”这种字面词向量吃它和“可报销金额最高为”的语义关系两者一叠加覆盖率立刻不一样。融合算法我推荐 RRFReciprocal Rank Fusion因为它不用校准两类检索各自的分数直接用排名位置做计算。实现起来十几行def reciprocal_rank_fusion(results_list, k60): fused_scores {} for results in results_list: for rank, doc_id in enumerate(results, start1): fused_scores[doc_id] fused_scores.get(doc_id, 0) 1 / (k rank) return sorted(fused_scores.items(), keylambda x: x[1], reverseTrue)重排是第二件套。先用粗召回模型一次性拉回 50 到 100 条再用 Cross-Encoder 精排取 Top-5。Cross-Encoder 同时把 query 和 document 放进一个模型让注意力在两者之间交叉精度远高于向量检索阶段的 Bi-Encoder。代价是速度慢所以它只适合做粗召回之后的小规模精排。第三件套是 Query 改写。当用户问“去年西南地区电力项目验收周期平均多长”这种组合问题时直接用原始 query 去检索容易漏可以先让 LLM 把问题拆成“西南地区电力项目有哪些”“每个项目验收周期多久”“平均周期计算”几个子查询分别检索再聚合。改写这一步会让单次请求延迟增加几百毫秒但复杂问题上的命中率提升非常明显。2.3 Embedding 选型与微调选 embedding 模型时别盲目追新。中文场景里 bge-m3、text-embedding-v3 这类支持长文本和多语言的模型是稳妥选择如果预算和时间允许用领域语料把 embedding 微调一版收益通常比换更大参数量的模型还大。微调不需要海量数据几百到一两千条与你的知识库真实查询高度相关的(query, passage)对就够跑起来。注意一定要做负样本挖掘不然模型只会学到“什么都相关”微调完指标反而下降。至于 RAG 还有没有别的检索技巧比如按元数据过滤、分库检索这些属于场景定制核心思想是一致的在把问题交给 LLM 之前先保证候选资料的质量。没有 hit rate 作为地基生成环节再折腾也补不了窟窿。3. 分水岭三知识组织——从向量堆到本体与图谱3.1 知识割裂的问题到底出在哪不少人搭建知识库时把文档全部切碎后一股脑塞进向量库没有任何结构。这种“所有东西都往里堆”的玩法遇到“我们公司哪两个产品线营收占比最高”或者“A 项目负责人和 B 项目的客户是不是同一家公司”这类跨文档问题立刻露怯。原因就是文档之间的关联没有被建模每个 chunk 都变成了一座孤岛这就是热词里提到的“知识割裂”。我早期踩过最狠的坑给一家制造业客户搭知识库单跳问题答得很好一到“这个设备的供应商以前还供过哪些料”这种涉及关系链路的问题答案就开始胡编。单纯增加文档量没用因为信息不在同一块文本里躺着而在彼此的关系中。RAG 系统不是只存储文本它得能表达“设备和供应商”“项目和负责人”之间的连接。3.2 Ontology RAG用本体做知识约束本体OntologyRAG 的思路是先把领域的实体类型和关系定义成 schema比如“研发项目”“产品线”“供应商”“里程碑”再规定它们之间的属性关系。然后做文档解析时用 LLM 把实体和关系抽出来存成三元组。查询时不只是在向量库里盲搜而是可以先做本体过滤把检索范围限定到相关的实体分支上再去精准捞内容。举个例子用户问“XX 项目负责人是谁”。传统 RAG 是拿整句去向量库碰运气ontology RAG 会先解析出“XX 项目”这个实体沿“项目→负责人”这个关系路径去定位再去指定文档片段里确认细节。命中率更高也更容易解释结果是怎么来的。本体设计的原则是“先轻后重”。别一上来就定义几十个实体、上百种关系你会被自己的 schema 拖死。先划出 5 到 8 个核心实体把最关键的关系链建起来跑一批真实问题看哪些检索还失败再逐步扩展。3.3 GraphRAG图谱与社区摘要GraphRAG 是另一个方向核心做法是构建文档的实体关系图谱然后在图上跑社区检测把关系紧密的实体聚成一个社区再对每个社区生成一层摘要。查询时先定位相关社区拿社区摘要做全局层面的理解再回到底层图谱检索具体证据。它在全局性问题上尤其有优势比如“我们公司的全部业务范围有哪些”这种问题散落在几百篇文档里传统向量检索很难聚合全但图谱社区摘要可以快速给出全局视角。值得注意的是GraphRAG 不是银弹。构建图谱的抽取环节依赖 LLM成本非常高而且抽出来的三元组质量参差不齐。我接手过一个项目图谱里近三成三元组是错的社区摘要于是全部建立在错误信息上修正成本极高最后不得不推倒重建。如果数据量不大问题又偏“单文档内查询”完全没必要上 GraphRAG先做 ontology 过滤或者干脆把文档分组管理性价比高得多。3.4 场景判断到底要不要上图谱我给个相对可执行的判断标准知识库少于 500 个文档问题大多是“这份文档里怎么说”的单跳查询不需要图谱。问题开始出现明确关系链比如“某个分支机构隶属于哪条业务线、负责人是谁”先考虑 ontology 做轻量建模。问题的确需要跨大量文档做全局聚合且预算充裕、有专门人力维护图谱质量GraphRAG 才值得上。知识组织这个分水岭本质是让 RAG 从“文本堆”进化成“知识网”。它解决的不是“找得到一句话”而是“找得到一句话和另一句话之间的关系”。这一步做扎实了很多看起来需要换模型的问题实际上改改结构就解决了。4. 分水岭四Agentic RAG——从单轮到多步推理4.1 单轮检索解决不了的问题传统 RAG 的默认流程是“一次检索、一次生成”这个模式对单跳问题很合适但遇到“去年我们在西南地区的电力项目有多少个”这类问题就吃力了——它要先拆出“西南地区有哪些电力项目”再逐个确认年份和状态最后数个数。一次检索就算抓回一堆文档里面混着不同项目、不同年份的信息LLM 很难从中准确数出结果。Agentic RAG 的思路是把检索过程交给一个智能体来控制。它不再“问一次就答”而是先分析问题判断要不要检索、要怎么检索再拆成一系列动作先做一个粗查询看结果缺什么再补一个过滤查询必要时调外部工具算平均值最后收齐证据才生成回答。你可以把普通 RAG 想象成“搜一下就交卷”把他想象成“带着检索计划逐步找齐线索再作答”的调研员。4.2 Agent 的决策循环长什么样实现上最通用的是类 ReAct 模式thought分析现状→ action选一个工具→ observation观察工具返回→ 重复直到信息足够。工具列表通常包括向量搜索、图谱查询、文档结构查询、计算器、以及最终回答器。用 LangGraph 这类状态机框架落地时核心是定义好状态转移。伪代码结构是这样的from langgraph.graph import StateGraph def analyze(state): # LLM 决定下一步动作比如是否需要额外检索 return {next_action: search, sub_queries: [西南地区电力项目有哪些]} def search(state): # 执行一次检索把结果追加进已有的上下文 return {context: state[context] retrieved_chunks} def finalize(state): # 所有信息收齐生成最终答案 return {answer: llm.generate(state[context], state[question])}每一轮循环都是一次 LLM 调用所以 agent 流程的延迟和成本都会成倍上升。我见过没做控制的项目一个简单问题跑了 9 轮工具调用用户都等睡着了。合理做法是加一个门槛先让一个轻量判断模型或者简单规则决定“这个问题需不需要 agent”如果是单跳 FAQ直接走传统单轮检索只有命中“需要多次查询”“跨主题对比”“信息不足”这几个信号才启用 agent 循环。4.3 成本、延迟与可观测性Agentic RAG 上线前你必须把每一步决策都暴露出来。每个循环里的 thought、action、observation 都要记录到日志否则问题来了你根本不知道是哪一轮检索误导了模型。我项目里最典型的 caseagent 在第二轮引用了一份过期版本的制度文档后续所有推理都基于错误前提最后答案自然全错。没有日志我只会看到“生成的答案不对”有了日志一眼定位是检索优先级排序问题。另外工具调用要考虑失败重试和超时。向量库临时不可用、图谱查询返回空结果这些情况都要 agent 能感知到并做出备用选择否则就会卡死在一个失败的循环里反复消耗 token。成本控制上我习惯给 agent 设置最大轮次上限3 到 5 轮是常见阈值超过直接转人工兜底或者放弃回答。5. 分水岭五多模态——知识库不只是文本5.1 图片到底能不能进知识库几乎每个做知识库的人都会遇到这个问题客户的资料里一半信息在图片里操作手册有截图PPT 有架构图PDF 里有数据图表。经典的 RAG 管线对图片是完全无感的——要么丢弃要么只保留一个毫无信息量的文件名。于是用户问“这个设备的面板指示灯含义”系统答不上来因为那张截图压根没被入库。这的确是个大问题但解决方案并不玄乎按成本和效果分几个层次来选。5.2 分级的实现方案最轻量OCR 转文本。截图、扫描件里的文字先用 OCR 识别出来以文本形式入库。面板截图上的“电源”“故障”“运行”这几个字跟文档正文放在一起检索到文字就等于定位到了对应的图。这个方案零模型成本实现最快。常见做法图片生成描述文本再入库。用多模态模型给每张图生成一段 caption比如“图中展示了设备背板接口布局从左到右依次为电源接口、网口、串口”把这段描述作为图中的检索单元。LLM 生成答案时看到描述文本就能知道图里有什么。需要给用户呈现原图时在描述文本旁边存好图片路径或 ID检索到描述就把它对应的图片链接一并返回。进阶方案图像向量直接入库。用 CLIP、SigLIP 或 Qwen-VL 这类多模态模型把图片编码成向量放进和文本相同的向量空间。查询时用文本 query 直接匹配图片向量做到“文字搜图”。回答阶段如果 LLM 本身具备视觉能力比如 GPT-4o、Qwen-VL就把图片直接传给它如果 LLM 只能读文本还是把图片描述拼进 prompt。专项方案图表结构化。数据图表不要只转图片最好把图表背后的数据抽出来转成 Markdown 表格或 JSON让 LLM 看到精确数值而不是看图猜数。这一步对“检索发票金额”“分析季度趋势”这类问题特别关键。5.3 表格与图表的专项处理表格在 RAG 里的尴尬程度仅次于图片。整页塞进一个 chunk表头和正文混在一起检索和生成都会困惑拆碎了又丢了表头字段含义。我的做法是每张表单独成一个 chunk表头信息和通用上下文作为这个 chunk 的引文前缀然后再把表格转成 Markdown 或键值对文本。数据密集型表格优先走 Camelot 或 TableTransformer 做结构化抽取别把整页 PDF 拿 OCR 硬扫。多模态这一关不一定要上多重的方案。我的经验是80% 的图片需求靠“OCR 描述文本入库”就能满足先别急着上图像向量检索成本和维护难度都高出一个量级。只有当你真实遇到“用户拿文字描述找一张没文字的架构图”这种场景时图像 embedding 才真正值得投。6. 分水岭六评测与优化闭环——没有评测就是盲人摸象6.1 三层指标别只盯着一个数字很多人问 RAG 的瓶颈在哪我见过最多的瓶颈不在模型在“没有评测体系”。优化全靠感觉今天调个 chunk 大小好像变好了明天换个 embedding 又说变差了没有一个客观标准来裁决整个项目就在反复试错的迷雾里打转。要搭评测闭环至少要盯三层指标检索层Hit RateK、MRR、NDCG。回答“正确答案有没有被捞回来、排得够不够靠前”。生成层忠实度faithfulness、答案相关度answer relevance、上下文相关度context relevance。回答“答案是否忠于给定资料、是否真的回应了问题”。端到端层人工匿名评分或者 LLM-as-judge 打分。回答“用户体感到底怎么样”。每一层都要独立看。我惯用的排查套路是答案不对先看 Hit Rate。如果 hit rate 低那就是检索层的问题去改解析、切分、混合检索、重排如果 hit rate 已经很高但答案还是不对那才是生成层的问题去改 prompt、换生成模型、加幻觉检测。这个先后顺序能帮你避免最无效的优化操作——明明是检索漏了却花一整天调 prompt。6.2 用 Ragas 搭建评测脚手架市面上有不少评测工具Ragas 是比较好上手的一个。构造一批带标准答案的问题集跑一遍评测脚本就能得到基础指标from ragas import evaluate from ragas.metrics import faithfulness, answer_relevancy, context_relevancy result evaluate( dataseteval_dataset, metrics[faithfulness, answer_relevancy, context_relevancy] ) df result.to_pandas()评测集不是随便攒 20 条问题就完了。我建议至少覆盖这六类单跳问答、多跳聚合、否定类问题“哪些项目没有通过验收”、术语改写表述、跨文档查询、无答案问题知识库里根本没这信息系统应该诚实说不清楚而不是编。这六类覆盖不住评测结果会严重失真。我见过只测 30 条简单单跳问题的项目指标高得吓人一上真实用户问题立刻现原形。6.3 建立 Badcase 回归机制评测不是为了跑一次拿个漂亮数字而是为了把每一轮的优化结果固化下来。我建议把每次用户反馈的失败案例做成 Badcase 库每一条记录三样东西原始问题、模型给出的回答、期望的正确答案来源指出错在哪一步。然后每一版优化之后都把 Badcase 库全量回归一遍观察哪些问题被修复了、哪些还是错的、有没有引入新的问题。用 LLM 做自动评测时要注意顺序偏差。同一个答案排在另一个答案前面和后面的得分可能不一样评测脚本里最好做顺序随机化并且定期抽样人工复核纯靠自动打分容易被系统性的偏差带偏。我在一个项目里发现自动评分 90 分人工一看答案里引用了完全不存在的文件号忠实度这一项必须结合实体级校验来做不能完全信任大模型自评。6.4 从指标反推优化路径评测闭环建起来之后优化路径就清晰了Hit Rate 低到无法接受 → 先查解析是否完整再审视切分粒度然后加混合检索和重排必要时微调 embedding。Hit Rate 合格但忠实度低 → 生成模型在自由发挥了收紧 prompt 的“只能使用给定资料”约束压低 temperature或引入“找不到就明说”的兜底逻辑。检索结果里全是相似但不相关的 chunk → 多半是 embedding 模型区分度不够或者知识库文档之间存在大量重复表达考虑做去重和聚类。RAG 优化是一个循环不是一次性交付。评测体系决定你在这个循环里走得多快、多准。没有它所有调参都是盲人摸象有了它每一次改动都能用数字说清楚“到底变好了没有”。写到这里我最想表达的是RAG 的入门门槛确实低到烂大街了但真正把一个 rag知识库 做成能扛业务压力的系统分水岭从来都在细节里——解析切分有没有认真做、hit rate 有没有被量化、知识有没有被组织成结构、控制流够不够灵活、多模态有没有被遗漏、评测闭环有没有建立起来。这六处每补一处你都会感受到明显的质变补到后面你会发现很多前期架构上的问题会反过来逼迫你重构——这不是坏事说明你正在从流水线思维走向真正的工程化思维。按我个人的习惯如果你现在要新起一个 RAG 项目我不会让你先选模型或者搭框架。我的建议是先收集 100 道贴近真实业务的问题做出第一版可以跑通的基线把 hit rate 打出来再沿着这六处逐项排查、逐项优化。等这六个地方都有了量化数据你对整个系统的掌控力就完全不一样了——到那时候再有人跟你说“RAG 烂大街”你大概也只能笑笑烂大街的是流水线分水岭从来都在这些没人愿意细抠的地方。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑