
现在打开任何一个技术社区搜 RAG蹦出来的教程十有八九是同一副面孔加载文档、切块、做 embedding、丢进向量库、召回 topK、拼 prompt、交给大模型。从代码路径上看这套流水线确实已经烂大街了任何人照着教程都能在半天内跑出一个 demo。但做过真实项目的人心里都清楚同样一条流水线A 团队能做出来客服问答直接上线B 团队做出来的东西一问就胡说差距从来不在“有没有跑通”而在流水线之外的六个地方文档拆解、检索策略、上下文编排、知识结构化、评测体系和工程化落地。这篇文章不打算再教一遍流水线怎么搭只把这几处真正拉开差距的分水岭掰开讲。适合谁看已经用 LangChain、LlamaIndex、Dify、LangChain4j 之类的框架跑通过 demo、想在效果和稳定性上再上一个台阶的人。如果你刚入门也能从中看到后面要补的功课在哪里而不是被那些“十分钟跑通 RAG”的教程误导以为跑通就是做好。1. 文档拆解决定 RAG 上限的“隐形变量”很多人把 RAG 效果不好归结于模型不够聪明其实第一个分水岭在文档进入向量库之前就出现了。拆解环节做得糙后面检索、重排、生成做得再好都是在烂数据上雕花。1.1 无脑按固定长度切块为什么总会栽跟头流水线默认的切块策略通常是一句话splitter.split_text(content)默认参数可能是 500 字、200 字重叠。这个默认值掩盖了一个事实文本天然有结构而固定长度结构是随机的。我见过一个真实案例一份设备操作手册里某个表格刚好被切在两个 chunk 中间表头在第一块数据在第二块。用户问“这台设备的最高转速是多少”向量检索命中了第二块但第二块里只有一堆数字没有“最高转速”这个字段名。模型对着数字猜了个答案理所当然地错了。固定长度切块还会遇到两种典型问题块太小上下文丢失。比如一段 200 字的 chunk 里只包含某个步骤的后半句前面的条件分支丢了答案看起来像是对的但适用范围完全错了。块太大语义被稀释。一个 2000 字的 chunk 里什么都有embedding 向量被平均成“一锅粥”检索时跟用户问题的相关性排序就会很怪。所以切块的第一原则是跟着文档结构走而不是跟着字符数走。标题层级、段落边界、列表项、表格行这些才是天然的语义单元。1.2 表格、代码和图片不是所有内容都适合“切成方块”文档类型直接决定拆解策略这一条最容易被人忽略。先说表格。按行切会把表头丢到上一块按列切会把字段关系拆散。正确做法是先做版面分析识别出表格结构转成 Markdown 表格或“键: 值”对再把表头和相关正文作为一个整体进索引。这样用户问“A 型号的保修期是多久”检索到的才是“A 型号 - 保修期 - 2 年”的完整对应关系。再说代码。代码按行数切切出来的“函数碎片”对检索没有意义。更好的是用 tree-sitter 这类语法解析器按函数、类、方法边界切块并保留函数名、参数列表、注释这些元数据。这样开发者问“这段代码里 timeout 参数怎么配置”命中的是完整函数而不是某一行注释。图片这块很多人追问“RAG 知识库能存储图片吗”。能但别把图片二进制直接丢给向量库就算完。文本向量模型根本看不懂图片多模态 embedding 模型可以把图片和文本映射到同一个向量空间但检索回来之后大模型读图还有链路成本。更务实的做法是对带文字的截图、扫描件先做 OCR把文字作为 chunk 存入知识库对复杂图表先用多模态模型生成一段结构化描述和图片路径一起存如果用户必须看原图才能确认信息再把图片文件作为附带回传。记住向量库存什么取决于你希望检索到什么。存图片本身不是目的目的是让下游模型拿到可用的信息。1.3 本地拆解工具怎么选如果你正在搜“有没有本地的 RAG 文本拆解工具”答案是确实有而且不需要把文档传到任何外部服务。常用的开源组合我总结一下工具擅长处理适合场景PyMuPDFPDF 文本、目录、页面坐标轻量级 PDF 解析UnstructuredHTML、PDF、Word、邮件等多格式多格式清洗与切块MinerU复杂 PDF、公式、表格论文、技术手册MarkItDownOffice 文档、PDF、音视频转文本微软生态文档我现在的习惯是先用版面解析工具拿到“块级”结构再按业务规则二次加工。拆解结果不要只存 content至少要带一套结构化元数据{ chunk_id: doc_123_chunk_45, doc_name: 设备操作手册V2.1, section_hierarchy: [第三章, 3.2 参数设置, 3.2.1 转速], table_id: table-7, source_page: 12, content: 最高转速3000 r/min持续运行不超过 2 小时。 }这套元数据后面有大用。权限过滤、标题追问、表格还原、版本管理全都依赖它。拆解阶段偷懒后面每个环节都要加倍还债。2. 检索策略向量不是唯一答案召回质量才是分水岭拆解决定知识库的上限检索决定用户问题能不能碰到这个上限。很多团队把全部精力放在调 embedding 模型上但实际项目中检索策略的架构问题往往比模型选择更关键。2.1 纯向量的三个困境向量检索不是不好它擅长的是语义相似但语义相似不等于事实命中。第一个困境是“形似但神不似”。用户问“余额不足怎么处理”向量召回的可能是一堆“如何充值”的段落因为两者在语义空间里靠得很近但用户要的是“扣款失败后的退款流程”。第二困境是字面匹配缺失。向量库对“A320-200”和“A320-200neo”这类高度相似但不相同的字符串毫无办法embedding 会把它们映射到相近位置但业务上这是两个完全不同的型号。第三个困境是 topK 截断。无论向量库召回 10 条还是 50 条都只来自一次“全局相似度排序”如果相关知识在不同文档里被拆成了多块单次检索很难全部找齐。所以我不建议任何生产项目只跑一路纯向量检索。2.2 混合检索与 RRF 融合更稳的做法是混合检索一路用 BM25 做字面匹配另一路用向量做语义匹配再把两路召回结果做融合。BM25 在“型号、人名、编号、报错码”这类精确匹配场景下非常强向量在“我想问问怎么搞”这类语义模糊场景下更强两者互补。融合方式推荐 RRFReciprocal Rank Fusion公式简单到离谱score(doc) sum(1 / (k rank_i(doc)))把文档在各路召回里的排名倒数相加k 通常取 60。RRF 的好处是不需要对各路分数做归一化直接拿排名算抗不同分布干扰的能力很强。在实现上如果你用的是 Elasticsearch 或 OpenSearch原生支持 BM25 和向量的混合检索如果只用 Milvus、Chroma 这类专用向量库就在应用层跑两路召回再合并。两路 topK 各取 50RRF 后取前 10效果通常比单路 topK50 好得多。2.3 重排最容易被忽略的性价比改造混合检索之后很多团队就直接把 top10 塞给大模型了。但 top10 里可能有一半是噪声。这时候需要加一个重排层。重排的本质是换一个更聪明的模型对“用户问题 候选文档”这个组合做精细打分。向量 embedding 是双塔结构问题和文档各自编码后再算相似度快但粗重排模型是交叉编码器把问题和文档拼在一起过 Transformer慢但准。一个常见误区是“我已经把 topK 调大了不需要重排”。恰恰相反topK 调大只会把更多噪声带进上下文窗口真正要做的是先宽召回再精排最后只留 top3-5。比如召回 50 条重排后取 5 条生成质量通常会明显提升。开源方案里可以选 bge-reranker 或者同类交叉编码模型部署成本不高。如果项目里没有重排层我建议优先加上这是整个 RAG 链路里性价比最高的改造之一。3. 上下文编排从“拼 Prompt”到“路由与规划”流水线最后一步看起来最简单把检索结果拼成 prompt问大模型。但真实场景里这一步才是“RAG 智能体”和“RAG 玩具”的分水岭。3.1 查询改写与意图路由用户不会按照知识库的组织方式提问。他们会问“那个功能怎么弄”“有没有什么办法让我不花钱”甚至会把多个问题揉在一句话里。直接拿原始 query 去检索效果往往不稳定。所以编排层第一件事是查询改写。用一个轻量模型调用把用户问题改写成更适合检索的形式补全指代、去掉口语、拆出多个子问题。比如“A 和 B 哪个更适合我们这种小团队”可以拆成“A 的适用场景”“B 的适用场景”“A 与 B 的对比维度”三个检索项分路召回后再统一汇总。第二件事是意图路由。不是所有问题都需要走“全量检索 长上下文”。简单 FAQ 走一次检索就够对比类问题要先分别检索再比较需要计算的走工具调用知识库没有的问题干脆说“不知道”别硬答。路由做好了既能提升准确率又能省 token 成本。3.2 多跳检索与智能体式规划“A 型号的保修政策和 B 型号有什么区别”这类问题一次检索很难答好因为答案分散在两个不同的 chunk 里甚至两个不同的文档里。这时候要用多跳检索先定位到“保修政策”相关的候选文档再读取文档里涉及 A、B 的部分然后二次过滤、三次确认。更复杂的场景还会用到递归检索。比如先看目录结构找到“第三章”再展开第三章下面的子块。这种检索节奏本质上就是智能体在“规划下一步去哪找”。像 LangChain、LlamaIndex 里的 agent 框架能做这件事但关键是不要为了用 agent 而用 agent。单跳能解决的问题别硬拆多步多一步就多一次模型调用和一次失败机会。这里也回应一下“流水线中的冲突”当“答案完整”和“答案忠实”这两个目标冲突时固定流水线经常顾此失彼。比如你既想回答得全面又要求每个细节都有出处一次检索往往不够。把流程拆成“先检索后综合”和“先综合后补检”两种模式按问题类型选择才算真正把 RAG 用活了。3.3 上下文压缩和引用来源检索回来的 top10即使重排后也有部分信息是冗余的。上下文窗口变大不等于效果变好反而会稀释注意力。业界很早就发现大模型对长上下文中的“中间部分”关注度偏低所以编排层要主动做信息压缩按重排分数截断、按元数据过滤、把长文本压缩为要点。同时一定要保留引用来源。不只为了用户信任更多是为了排查问题。当答案出错时如果没有“这句话来自哪个 chunk”的映射你就只能盲调。每一段返回内容都带上 chunk_id、文档名、页码这个习惯能从根上加快后面要说的评测与迭代。4. 知识结构化Ontology RAG 解决的是多跳和一致性问题把文档切成块、做好向量索引这已经能覆盖很多场景。但当知识之间是强关系型结构时比如设备故障、医疗指南、法规条款、供应链规则光靠文档块就不够了。4.1 从“文档块”到“实体与关系”普通 RAG 的粒度是“段落”Ontology RAG 的粒度是“实体 关系”。本体Ontology做的事情是定义领域里有哪几类概念、每类概念有哪些属性、概念之间有什么关系。举一个设备维修的例子实体类型设备型号、故障现象、维修操作、备件、技师等级关系故障 caused_by 某个部件维修操作 repair_by 某个技师等级备件 compatible_with 某型号。有了这层结构用户问“这台设备报错 E-301哪些备件能用”系统先通过实体链接把“E-301”映射到故障实体再沿关系找到对应维修方案和备件清单最后把这段知识作为上下文交给大模型。这个能力纯靠关键词和向量很难稳定做到。4.2 图谱查询和向量检索怎么结合Ontology RAG 不是要用知识图谱替代向量库两者是配合关系。我在实战中比较常用的链路是先用向量检索定位用户问题涉及的候选文档和实体再用知识图谱做关系扩展或多跳路径查询最后把图谱路径上的实体属性、关联文档片段一起作为上下文。比如先通过向量找到“E-301 故障代码”的段落再在图谱里查出“E-301 关联的备件 SKU”再针对这个 SKU 去文档库里找这些备件的安装说明。这样既保留了向量检索对模糊语义的宽容度又用图结构保证了业务关系的完整性。注意图谱本身不需要很复杂最开始只要把“实体类型 - 关系 - 实体类型”定义清楚别急着把每个文档里的每一句话都抽成三元组成本太高且噪声很大。4.3 什么场景才值得上 Ontology不是所有 RAG 项目都该上知识图谱。我的判断标准是三条实体关系密集比如“故障 - 原因 - 维修 - 备件”这类强关联多跳问题频繁出现用户经常问“如果 X 发生会不会影响 YZ 有没有备件”答案一致性要求高同一个实体在不同文档里叫法还不能完全统一。如果只是做一个“公司制度问答”“产品介绍问答”文档块级别的 RAG 通常够了。Ontology 是有代价的本体设计和维护需要业务人员深度参与实体抽取和关系对齐也不是一次性能做完的活。上了一堆图谱但没人维护最后的价值可能还不如干净的文档块。5. 评测体系不能量化的 RAG 项目调参只是碰运气很多团队卡在“效果不好但不知道哪里不好”的状态这是 RAG 项目的普遍瓶颈。模型换了一个又一个参数调了一遍又一遍却始终没有一套自己的评测集所有改动都只能靠“感觉”。5.1 离线评测集的建法评测的第一步是有一份高质量的离线测试集。至少准备 100 条问题每条问题带上标准答案和支持文档问题来源最好是真实使用场景客服聊天记录、用户工单、销售反馈、常见问题汇总。不要只放“好回答”的题目还要放边界题和反例。比如知识库里没有答案的问题、需要对比多个文档才能回答的问题、同一问题在不同文档里有矛盾说法的问题。这些才是生产环境里真正让你翻车的题型。评测集的维护和代码仓库一样要版本化。每次改拆解策略、换 embedding、调重排阈值都在同一份测试集上跑一遍把指标变化记录下来。否则你根本不知道是哪次改动让效果变好或变坏。5.2 五个核心指标RAG 的评测指标不能只看“大模型回答对没对”要看整条链路的健康度。我常用下面这几个维度指标衡量什么观测方式召回率 (RecallK)正确文档有没有进候选集检查 topK 里是否包含标注文档精确率 (PrecisionK)候选集里有多少是噪声人工抽检 topK 相关性忠实度 (Faithfulness)答案是否严格基于上下文对比答案和上下文逐句核对端到端准确率最终答案是否正确人工评定或 LLM as Judge引用准确率出处是否张冠李戴核对 citation 与答案内容小团队别贪多先盯两个就够了召回率和忠实度。召回率低说明检索层有问题忠实度低说明生成层或上下文编排有问题。前者是“找不到”后者是“找到了但没用对”。5.3 Bad Case 归因先别急着换模型模型答错之后第一反应不应该是“换个更大的模型”。先把 bad case 归因到具体环节按下面的思路排查正确文档没进 topK问题在拆解、embedding、混合检索先看 chunk 大小和召回策略。正确文档进了 topK 但没被选进上下文问题在重排和上下文截断调重排分数或压缩策略。文档选进来了但模型没用对问题在 prompt、上下文顺序、或模型指令遵循能力。引用的文档本身就不支持答案这是知识库内容问题可能是文档过期、缺失、本身写错。这套归因逻辑看起来简单但我见过太多团队在没做归因的情况下反复换模型最后问题还在原来的拆解环节。没有评测体系一切调优都是碰运气。6. 工程化落地本地部署、知识更新与成本控制最后一个分水岭是把 RAG 从“能跑”变成“能稳定跑、能维护、能控成本”。这一块对于企业项目尤其关键。6.1 框架选型从零基础本地 Demo 到生产级先说零基础最关心的问题本地跑一个简易 RAG 知识库要多久答案是很快。Ollama 拉起一个 7B 参数模型再配一个本地 embedding 模型向量库用 Chroma 或者 pgvector文本拆解用 PyMuPDF 或 Unstructured一个晚上就能通。如果你还想省事Dify 这类平台把知识库流水线封装成了可视化工作流拖拖拽拽就能建库、检索、接模型。但框架选型不是越方便越好。Dify 的封装很适合快速验证但一旦你需要特殊拆解规则、自定义重排逻辑、细粒度权限过滤就会发现自己被框架的默认行为束缚住了。反过来LangChain、LlamaIndex 这类库灵活但灵活也意味着你要自己组装、自己测试、自己兜底。Java 团队如果不想引入 Python 技术栈LangChain4j 的 Easy RAG 模块也能快速起一个 Spring Boot 项目直接接本地向量库和 OpenAI/本地模型。选型逻辑只有一个团队的语言栈、部署环境、维护能力决定框架框架决定不了你的效果上限。别因为某个框架热门就去重写项目这是性价比最低的决策。6.2 知识库增量更新与权限隔离企业 wiki 类知识库接入 RAG真正麻烦的不是第一次全量导入而是后续更新。文档改了索引没同步模型还在拿旧版本回答用户这是生产事故的常见来源。所以知识库要有一套“文档指纹”机制。每次拉取文档时先算哈希跟已有文档比对没变的跳过变了的删除旧 chunk 并重建索引删掉的文档也要同步从索引里移除。如果文档有版本号尽量把版本号写进 chunk 元数据用户在问答时能看到“这是 V2.1 的内容”还是“V1.3 的过期内容”。权限隔离也必须在检索层做不能在生成层靠 prompt 提示“不要回答越权内容”。正确做法是每个 chunk 带权限标签检索时按当前用户角色过滤能看到哪一级就只召回哪一级。等答案生成出来再做权限控制往往已经泄露出去了。6.3 缓存、限流和成本优化RAG 服务上线后成本和稳定性是并行的两个问题。检索层可以做缓存对相同或高度相似的 query在设定时间内直接返回上次结果和答案能省掉大量重复的 embedding 和 LLM 调用。生成层也能做缓存尤其是在 FAQ 场景高频问题答案完全没必要每次重新生成。成本估算要按 token 算而不是按“回答次数”算。单次回答成本大致是成本 (检索后上下文 token 系统 prompt token 输出 token) × 模型单价topK 从 5 调到 10token 消耗可能翻倍但答案质量不一定提升。重排层加上之后可以用 topK50 召回、重排取 5 的方式既提高了效果又不至于把 50 段全塞进上下文。限流和降级也不能缺。知识库检索超时时要有降级话术大模型服务不可用时要能从缓存里兜底。线上系统首先是稳定其次才是聪明。最后说点个人体会。RAG 项目的效果从来不是靠某一个模型或者某一个向量库撑起来的而是由一条完整链路里每一个小选择共同决定的。我在实际项目中见过太多团队卡在同一个地方demo 很漂亮一上真实数据就露馅然后急着换大模型、换框架。其实大多数问题出在最朴素的切块、检索融合和评测集上。如果你刚跑完一条流水线建议先别追新架构按这六处分水岭逐个自检一次大概率能发现两三个立刻可以改进的点。少折腾多测量这个方向永远亏不了。