RAG从Demo到可用:六个决定成败的工程细节
发布时间:2026/10/5 9:18:10 锦皓数字建站

最近老有人问我RAG 是不是烂大街了你要是只看网上那批教程——ollama 拉一个本地模型embedding 往向量库里一塞再套一段 LangChain 或 LangChain4j Easy RAG 的检索链能回答几个 PDF 问题就觉得 RAG 不过如此那确实烂大街了。但真正在业务线上跑过 RAG 的人大概率会给你另一个答案烂大街的只是那条流水线真正决定项目生死的是六个容易被忽略的分水岭。这六个分水岭不藏在任何 demo 里。你在 GitHub 上随便搜一个 rag 实战项目看到的都是同一套骨架加载文档、切成 chunk、embedding、存向量库、相似度检索、丢给大模型生成。骨架一模一样但为什么有人做出来的东西“能用、敢用、天天用”有人做出来只能发个朋友圈截图差别就在流水线的每个接缝处。本文把这六处按工程落地优先级逐一拆开每处都给你现场踩坑后的选择逻辑不藏私。适合正在从 rag 教程走向 rag 项目、准备把 rag 知识库推到生产环境的人也适合那些已经跑通 demo 但总被老板问“检索结果为什么不准”的同学。1. 索引前的最后一道工序你的分块方式决定 RAG 的天花板1.1 为什么“按字符硬切”是第一个分水岭大多数 rag 项目的起点是 300 字或 512 字切一个 chunk。这个数字哪来的很多是从 OpenAI 早期的 embedding 模型上下文长度里抄来的。你问他为什么选 300他说“大家都这么切”。这就是流水线的典型特征步骤在但逻辑没跟上。按固定字符切块的问题在于它把文本当成字符流而不是语言结构。一句业务规则恰好被一刀切成两半前半段在 chunk A 末尾后半段在 chunk B 开头检索时无论命中哪一半语义都是残缺的。我实测过一个场景一份设备维护手册里写着“禁止在设备运行状态下拆卸防护罩”用 256 字硬切后这条规则被拆成“禁止在设备运行状态下”和“拆卸防护罩”两段。用户问“设备运行时能不能拆防护罩”向量检索把两段都召回了但大模型面对两个互相冲突的半句话直接开始胡编。这不是 embedding 模型的问题是分块策略的问题。分块不是简单物理切割而是要尽量保证每个 chunk 是一个语义闭合单元。哪怕做不到完美闭合也要至少保证“一个完整句子不跨块”。1.2 按结构切分Markdown 标题、表格、PDF 版面与父子块真正可以落地的切分方案要顺着文档自己的结构走。我目前常用的路径是分级切分先识别文档结构边界Markdown 的标题层级、Word 的段落样式、PDF 的页面布局和表格区域。有标题就按标题切让同级内容成一个块。在一个“章节”内部再考虑长度。如果这个章节太长就用段落和句号作为次级边界继续切而不是无脑按字符数截断。遇到表格要特殊处理。表格一旦被切碎成纯文本流行列关系全丢。以“产品参数表”为例检索“某型号最大功率”时如果表格被切成散行检索往往只命中一行“最大功率 2.5kW”但丢了型号列上下文不完整。我会把完整表格作为一个独立 chunk 存起来同时把表格的每一行做一个“瘦 chunk”行内包含表头字段名用于检索命中后再用父块召回完整表格。这里有一个很实用的技巧父子块切分。子块做精细切分用于向量匹配父块是子块所属的更大章节用于给大模型提供上下文。检索时先用小窗口的子块找到精确位置再沿“父子关系”把整个父块取回来喂给模型。这样既能提高召回精度又不会因为块太小而丢失上下文。这个方案在 LangChain 里有现成实现但你完全可以用自己习惯的框架手写一遍逻辑并不复杂。注意PDF 切分是有极大坑的领域。很多 PDF 的文本层是乱的段落跨页、表格被拆成图片、页眉页脚混入正文。在没做版面分析之前直接按页切块等于把脏数据倒进索引里。至少要先用版面识别工具把标题、正文、表格、页眉页脚区分开再决定哪些内容进索引、哪些内容直接丢弃。1.3 图片和扫描件别以为切分只处理“文本”有朋友问“rag 知识库能存储图片吗”能但你要想清楚存的是什么。如果只是把图片文件丢进知识库检索时模型看到的是 URL 或文件名没有任何语义等于没存。真正要让图片参与检索有两条路线走 OCR先把扫描件、截图的文字提取出来变成文本 chunk 再走标准流程。这是成本最低、最容易落地的方案大多数“带图片的文档”用这条路线就够了。走多模态 embedding用 CLIP 这类模型把图片和文本映射到同一向量空间用户输入文字直接检索到语义对应的图片。这条路效果上限高但对部署资源和模型选型要求也高本地跑起来不轻松。我对大多数知识库场景的建议是先用 OCR 把图片里的文字提出来把图片作为“附属于某个文本 chunk”的资源保存。比如设备手册中一张接线图OCR 提取出接线说明文本检索命中这段文本的同时把接线图一起返回给用户。这个方案兼顾了效果和工程成本不需要引入多模态模型就能解决大部分需求。2. 元数据才是检索精度的隐形控制开关2.1 向量只负责语义元数据负责现实筛选纯向量检索最大的问题是它对“现实约束”无感。你的知识库里如果有 2022 版和 2024 版两份制度文件用户问“请假流程是什么”向量检索把两份内容都召回了大模型把新老流程一混合输出一个谁也没执行过的流程。这个锅不该由 embedding 背而是你没有在检索前告诉数据库“只要 2024 版”。这就是元数据的作用。它是在索引之外给每个 chunk 额外附加的结构化标签。向量相似度负责“哪段内容在语义上相关”元数据过滤负责“哪些内容在现实条件下合法”。我的经验是一份正经的 rag 知识库每个 chunk 至少要挂这几个标签——来源文档 ID、文档标题、版本号或更新时间、所属章节路径、文档类型制度/手册/公告/聊天记录、权限级别、适用业务范围。2.2 chunk 富化不是一个文件切完就完事元数据挂载这件事很多人以为是给文档起个文件名就行。实际上要做的是“chunk 富化”在切分阶段把能标识这个 chunk 身份的信息都写进去。实操中我是这样做的文件入库时先跑一遍“文档画像”识别标题、层级、发布日期、作者、密级。有些能从文件名和目录里拿到有些要从正文头部解析。切分时把当前 chunk 所在的文档路径拼进去。比如“三级制度 员工考勤管理制度 第二章 请假类型 2.1 事假”这个路径会直接拼进 chunk 文本检索时即使只命中“事假”两个字它也知道自己处在整个制度体系的什么位置。实体标签按需抽取。如果知识库里涉及大量产品名、项目代号、人名用规则或小模型把实体打上标签检索时可以把这些标签当成虚拟关键词补足向量语义匹配的盲区。2.3 检索时怎么用元数据过滤索引建好之后检索不能只有一个“向量相似度排序”裸奔。标准做法是先做粗筛再做精排。粗筛阶段就是把元数据条件加进去时间范围过滤只看 2024 年后的、版本过滤只看生效版、业务域过滤销售问题不查售后文档、权限过滤未登录用户只看公开手册。这一步相当于提前划掉一大片不需要参与排序的内容把更精准的结果留给向量去竞争。不少 rag 框架里 metadata filter 都是硬编码在检索 API 里的实测中最大的坑是过滤条件过猛。你直接把“2024 年”写死如果库里确实只有 2022 年的文档检索结果直接为空。更合理的做法是先做查询意图判断决定要不要过滤、过滤哪些字段保留一个宽松的兜底策略。能把这种细节控好的团队检索质量的稳定性会好一个档次。3. 别急着怼 embedding先看看你的 query 怎么进的检索3.1 查询改写一字之差召回完全不同不知道你有没有碰到过这种情况用户问“这月报销截止日”向量检索效果很好用户问“发票什么时候要交到财务”效果崩了。这两句话语义其实一样但 embedding 理解不了这种同义转述的稳定性。这就是 query 侧的问题——你只做了文档索引没做查询加工。查询改写是 rag 智能体落地里被严重低估的一个环节。正规 pipeline 里用户原始 query 不会直接进向量检索而是先经过一个轻量的改写模型把口语转书面、补充省略主语、把指代还原成完整实体、把问题拆成几个独立子问题。比如用户问“它什么时候截止”就没有足够语义去做向量匹配改写后变成“2024 年第三季度报销截止时间”整个检索效果立竿见影。改写这件事的工程实现有几种方案。轻量一点用大模型写一个固定 prompt 做重写输入原始问题输出 2-3 个候选检索词。这个方案成本低适合本地部署。重量一点做 query 意图分类判定用户是问制度、问数据、还是问流程给不同类型配上不同的检索策略和提示词模板。我个人的体会是哪怕只加一个简单的改写步骤把“提问的随机性”压低检索召回率会明显上升。3.2 多路召回加合并为什么单路检索永远不够大部分 rag 教程只教你“把问题 embedding 一下去向量库 top_k”。但真实用户的问法是很复杂的同一个问题有时适合用关键词精确匹配有时适合用向量语义匹配。比如“PM2.5 检测仪校准规范”这种问题关键词“校准规范”本身就极有区分度向量召回反而会被近义表达带偏。再比如“有 CNAS 资质的第三方检测机构”向量检索可能召回“第三方检测”“CNAS 认可”等一堆相关但不精确的内容而 BM25 精确匹配反而能找到包含原词的那份清单。所以成熟的 rag 项目基本都走多路召回一路向量检索一路 BM25 关键词检索甚至再来一路基于规则的术语表匹配。各路召回结果做并集给每条结果打一个“来源分数”然后进入下一层排序。这比单路检索在“精确术语类问题”和“语义泛化类问题”上的表现都要稳。RRF 是一种简单的合并策略把多路结果按排名取倒数融合不涉及调权重工程上很好落地。3.3 当 query 要“走两步”才答得上来——多跳与 Agentic RAG还有一个常见场景用户问的问题不是一个检索动作能解决的。比如“对比 A 型号和 B 型号的耗电量差异”你要先分别检索两份产品说明书提取出耗电量参数再对比生成。这需要拆分成两个独立检索任务再合并处理每一步的检索条件相互独立但结果要汇聚。这就是 wiki 和 rag 经常被一起讨论的原因——知识跨度大、需要跨文档推理时单轮检索完全不够用。现在社区里把这个叫 Agentic RAG大模型不只做生成还做“检索规划”。先生成一个检索计划第一步查 a第二步查 b第三步汇总。每查一轮把结果喂回模型判断是否满足需求不满足就继续查。这个方向很热但我的建议是别一上来就做 full agent。先做固定规则的多跳拆解比如用 prompt 把问题拆成子问题列表逐个检索后再合并效果已经能覆盖大半场景。只有当你确实遇到“检索计划本身不确定”的需求时再上 agent 的调度逻辑。注意多跳方案里最常翻车的地方是“子问题的上下文断裂”。拆成两个子问题后第二个子问题往往依赖第一个子问题的检索结果。如果子问题之间是有关联的检索前要把上一轮结果中的关键实体拼进下一轮 query。忽略这步你的多跳就是两跳各自为战。4. 你对“相关性”的理解可能只在阈值之外——重排序的必要性4.1 为什么 top-k 从向量库捞出来不能直接用向量检索返回的 top-k 是“粗略候选”不是“精确排序”。embedding 模型在高维空间里把语义相近的内容聚在一起但这个“相近”是宏观的。你的 chunk 是“项目管理流程中关于变更审批的描述”用户问“变更审批需要几个环节”返回结果里可能混着变更申请单、变更执行记录、变更回滚预案这些内容都和“变更”相关却不是用户想要的答案。如果你直接把 top-k 丢给大模型模型就会在这些噪声里硬找答案结果自然是拼凑、含糊、甚至幻觉。重排序的作用就是把召回阶段拿到的几十条粗结果重新做一次精细的相关性排序把真正回答用户问题的内容顶到前面去。它解决的不是“召回有没有”的问题而是“排序对不对”的问题。很多 rag 项目召回明明没问题但用户说“答案不对”症结就在这里。4.2 重排序模型怎么选cross encoder 的优势重排序的主流方案是 cross encoder 模型。vector embedding 的常规做法是把 query 和文档分别编码成两个向量再算余弦相似度这种“双塔”结构的优点是快但失去了 query 和文档之间细粒度的交互信息。cross encoder 则直接把 query 和文档拼在一起送进模型让它们在每一层 Transformer 里充分交互最后输出一个相关性分数。代价是慢因为你不能预计算文档向量每条候选都要实时跑一次模型。工程上常用的有 bge-reranker 系列。如果你的预算有限本地部署一台普通 GPU 机器也能跑起来。实测里重排序能把“语义相关但回答不了问题”的噪声大量压下去top-5 的准确率提升非常明显。如果连模型都跑不动也可以先用规则做粗排比如前面提到的元数据过滤和来源加权至少把明显不符合条件的候选取掉再交给大模型。4.3 混合检索加重排的标准配方我目前在生产环境里比较常跑的组合是BM25 向量检索多路召回取 top-50把 50 条候选送进重排序模型取交叉编码分数最高的 top-8再把这 8 条拼成提示词上下文。50 这个数字不是拍脑袋——重排序模型一次只能处理有限长度50 条候选每条几百字刚好在延迟和精度之间平衡。你如果取 10 条就做重排召回阶段的漏网之鱼没有机会被救回来取 200 条重排耗时暴涨但精度收益收敛。另外要提一个容易被忽略的操作重排后的分数要用起来而不是只看顺序。给重排分数设一个阈值低于阈值的检索结果不要喂给大模型直接回答“知识库中没有找到相关信息”。这个“拒绝回答”的动作是减少幻觉最有效的开关之一。很多 demo 的坏毛病就是不管检索结果多牵强都强行生成一个看起来头头是道的答案。敢于说不知道你的 rag 项目才算有了一点点可信度。5. 评测没有评价指标的 RAG demo都是自嗨5.1 别再用“感觉变好了”来验收 rag你可能遇到过这种场景改了一版切分参数找几个业务同事试了试都说明显更准了然后高高兴兴上线。过了一周线上反馈检索结果越来越怪。为什么因为“感觉更准”没有可复现性。你调了一个参数究竟是切分变好还是用户问的问题恰好变了没有基线没有指标你根本说不准。RAG 评测可以借用 RAGAS 这套指标框架来搭。它定义了几个核心维度faithfulness答案是否忠于检索上下文有没有自己编、answer relevancy答案是否切题、context precision检索上下文里的信息有没有用上、context recall回答所需的信息有没有被检索出来。这几个指标各有各的算法底层都依赖大模型做打分裁判。虽然自动评估不可能完全替代人工判断但至少它能给你一条可以对照的基线。5.2 评测集怎么构造别拿文档标题当问题构建评测集是 rag 工程里最花心思的环节也是绝大多数 rag 教程不提的环节。太小的评测集没有统计意义太大的评测集标注成本又高。我个人的做法分三步第一从真实用户 query 日志里抽一批。真实问题往往口语化、带错字、省略主语这些才是系统要面对的。第二为每个 query 标注标准答案并标注“答案源自哪几个 chunk”。这一步是为 context recall 服务的没有标准答案你不知道召回是不是全的。第三再补一批“对抗性问题”。故意问库里没有答案的问题看系统是老老实实说不知道还是开始给你编。这个维度最容易暴露幻觉。一个合格的评测集至少 100 到 200 条才能让指标波动不要太玄幻。你拿着这套评测集跑每次参数调整跑完记录指标再决定改动留还是不留。没有这个流程你对 rag 系统的所有“调优”都是掷骰子。5.3 上线后的回归badcase 要回流评测不是一锤子买卖。知识库每个月都有新文档进来用户问法也在漂移今天跑得好不代表下周还好。我的习惯是每周固定时间把线上新产生的 badcase 捞出来人工看一眼确认是召回漏了还是排序错了然后丢进评测集做回归。这个动作看起来不酷但它是让 rag 项目长期稳定在 80 分以上而不是从 90 分掉到 60 分的保险丝。有些团队会维护一个“badcase 库”每个 badcase 都记录原始 query、检索到的上下文、模型回答、人工判断的问题出在哪一环。这个库本身就是团队经验的沉淀比任何架构文档都值钱。你要做 rag 项目的长期维护这是绕不开的功课。6. 从存储层解决那些“别人没告诉你的脏问题”6.1 图片进知识库三种路线怎么选回到“rag 知识库能存储图片吗”这个热词。前面提过 OCR 和多模态 embedding这里说第三种路线图文混合索引。这种方案里图片先走 OCR 拿到文字图片自身再用一个视觉模型生成一句话描述文字和描述一起进入文本索引。用户搜索时命中描述文本返回时则把图片展示给用户。这种做法的好处是不引入复杂的多模态检索基础设施但对“这张图片有什么用”做到了语义化。对绝大多数企业内部知识库这个方案是性价比最高的。OCR 本身的坑也要单独说一下扫描件要跑版面分析不然表格线会把文字识别得乱七八糟手写体识别率再强的 OCR 也会翻车不要对老档案的扫描件抱太高期望。图片类知识如果占比超过三成要提前规划存储和带宽否则检索返回多张高清图的时候页面加载速度立刻暴露。6.2 增量更新与版本化切完了文档又改了怎么办RAG 项目上线后最头疼的问题是“文档更新了索引怎么办”。很多团队直接把旧文档删掉重新切一遍全量入库。文档少的时候看不出问题等知识库有几万篇文档每次全量重建跑几个小时更新期间检索服务还要停摆这就很痛了。我的经验是入库流程要早一点设计成“可增量”。每个文档入库时记录唯一的文档 ID 和内容哈希文档更新时拿哈希比对变了才重新切分和向量化没变的跳过。向量库里同一文档的不同版本建议做版本号标记检索时默认只召回最新版本并在元数据里保留一个开关供溯源。别等到库建大了才补这个机制那会是返工量很大的重构。6.3 当知识库需要“逻辑结构”而不是“文本堆”——ontology 与知识图谱有些 rag 项目跑着跑着发现用户的提问不是“这段文字讲了什么”而是“这个项目涉及哪些人和哪些事”。比如“某项目延期会影响哪些后续任务”“某违规事件涉及哪些责任部门”——这类问题本质上是在查关系网络不是查文本段落。这时候纯文本向量检索的极限就暴露了它永远无法回答“A 和 B 之间是什么关系”。这就是 ontology 和知识图谱介入 RAG 的时机。先建一个轻量本体层定义实体类型人员、部门、项目、设备、事件、实体属性和实体间的关系。文档切分时用规则或模型把文档中的实体和关系抽取出来写入图数据库或关系表。检索时先走图谱查询把“关系子图”作为上下文补充到 RAG 的文本检索里。“文本回答事实图谱回答关系”两者合并后再喂给大模型生成答案。这个体系一旦搭起来系统的回答广度和逻辑性会明显提高但也要清醒地认识到本体构建和维护是有成本的数据量没有到一定程度、没有强关系型查询需求时先用元数据过滤撑住就行不必强上图谱。6.4 本地部署ollama 这条方案到底能跑多远最后聊一下“ollama 简易本地 rag 知识库【零基础可复制教程】”这种路线的边界在哪里。Ollama 的价值是让本地部署大模型变得极简单一个命令拉起模型对个人学习、内部演示、离线环境来说都是好工具。配合本地 embedding 模型拼一套 RAG 链路零基础确实一天能跑通很多国产大模型和开源模型的本地部署也都是这个套路。但你要知道它的边界。轻量模型的语义理解能力、长文档推理能力、工具调用能力和云端旗舰模型比有明显差距。在本地 rag 里你会发现检索到的内容没问题但模型总结不好、比较不了细节、偶尔还在提示词里把两个文档的内容缝在一起。这属于生成端能力的短板不是 RAG 链路的问题。本地 rag 更适合的场景是数据不出内网、文档量级可控、对答案质量有容忍度、预算有限。如果业务明确要求高准确率但数据敏感那就需要更大的本地模型方案——比如双卡或四卡部署一个 70B 级别模型——这不是零基础教程能覆盖的。另外一个本地 rag 教程经常避而不谈的问题是本地模型跑检索流程时切分、重排、评测这些环节不会比云端简单。你只是省了 embedding 和推理的 API 费用索引和评测的工程量一分都少不了。建议是想清楚目标再选方案别因为“零基础可复制”就去搭一套你以为能直接上生产的基础设施。分水岭之外做完这六处rag 项目就能从“能跑”变成“能用”。但在我实际经历里真正拉开长期体验差距的还有一个没法在架构图里画出来的东西对“知识库”的常态化运营。人们总以为知识库是一次建成的实际上知识永远是活的。今天入库的文档下周可能过期用户的问题会跟着业务热点漂移检索质量会随着数据涨落而波动。没有持续的评测回归、badcase 回流、索引重建习惯再漂亮的分水岭工程也会慢慢淤积。你不一定要一次性把六个分水岭做满但至少要知道demo 到可用之间隔着的不是模型参数而是这一步一步的工程细节。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。