在真正把RAG项目推上线之前,我一直觉得这东西没什么门槛:OpenAI刚出那阵子,接个向量库,调一调Embedding,能对着几篇文档有问必答,就已经够唬人了。直到我先后在三家不同行业的公司,把三个企业级RAG系统从Demo一路推到生产,并且每个项目都踩出一堆状况之后,“Demo方案和落地是两码事”这句话,算是刻进骨头里了。现在跟团队定调的时候我经常直说:90%的Demo方案,扛不住生产环境。今天就把这三个项目的教训,以及最后沉淀下来的工程方法全盘抖出来,给正在做或者准备做RAG上线的朋友当个参考。
1. 三个企业级项目的复盘:开场都是“栗子”,上线全是“事故”
1.1 客服知识库:文档切分让我第一次明白什么叫“召回灾难”
第一个项目是某制造企业的售后客服知识库。客户诉求很朴素:把产品手册、维修指南、常见故障排查文档接进大模型,客服人员直接问“这台设备报E-15错误怎么办”,系统给答案。Demo阶段我们做得非常快,拿十来篇手册,手工挑了几个标准的段落切好,再配上Prompt,效果惊艳得不像话,客户当场就拍了照片发到工作群里。
但一进生产就翻车。数据从十来篇变成几千篇之后,问题完全不在模型,而在切分。我们最初用的是固定长度切分,按500个字符硬切,手册里的表格被拆得七零八落,维修步骤被拦腰截断,“步骤3”的内容跑到了另一段里,大模型回答时一本正经地告诉我“步骤3是断电检查”,实际上那只是另半张表的表头。更麻烦的是,文档里大量图片是扫描件,PDF解析出来全是空白,OCR没做,等于把一堆失效数据丢进了知识库。后来我们统计过,那段时期线上检索的Hit Rate不到50%,客服试了几次就再也不用了。
这个项目给我的教训特别深刻:切分不是什么高深技术,但它决定了检索的起点。生产环境里文档格式混乱、表格图片密集、术语五花八门,如果不在数据进入索引前把解析和切分做扎实,后面调什么模型都救不回来。我们最后重做了整套管道:按文档标题层级感知切分,表格单独走结构化识别,扫描件先过OCR,再对特殊段落做人工校准,召回率才慢慢爬回来。
1.2 企业内部制度问答:权限隔离不做,差点酿成“知识泄漏”
第二个项目是某集团公司的内部制度与流程问答。几百份人事、财务、行政制度文档,员工可以问“年假怎么休”“差旅报销标准是多少”。Demo阶段依然很顺,我们把文档全量灌进一个索引,查询时直接召回,回答得像模像样。
生产上线前,业务方突然问了一句:这些制度里有些是部门内部文件,比如财务部的报销细则,其他部门的人能不能看到?我一开始想当然:反正RAG只是辅助,把文档片段给大模型,模型生成的答案是综合过的,问题不大。直到我们做了一个测试,用一个普通员工身份问“财务部报销的特殊审批流程是什么”,系统直接把财务部内部文件的原文片段拼了出来,那一刻所有人都沉默了。
这就是生产环境的隐性门槛:安全底线不在模型,而在数据访问控制。Demo阶段我们根本没考虑文档权限,可企业知识库里如果对不同角色、部门、职级的人开放同样的检索范围,稍有不慎就是数据安全事故。最后我们做得比较重:每个文档入库时必须打上权限标签,包括部门、保密级别、可见角色;检索阶段先从用户身份推导出允许访问的文档集合,再用元数据过滤条件压到检索引擎里,确保任何一段上下文都不可能来自用户无权访问的文档。模型侧反倒不用太操心,只要喂进去的内容本身是合规的,回答基本不会越界。
1.3 运维排障助手:延迟和实时性直接决定能不能用
第三个项目是一个偏运维场景的故障排查助手。团队希望一线运维人员能快速问“Pod一直CrashLoopBackOff怎么排查”“这个报错码是什么意思”,答案来自历史工单、故障手册和实时日志。这次我们吸取了前两个项目的经验,切分做了、权限做了、检索也优化了,以为稳了,结果被另一个维度打趴下:数据时效和工程性能。
故障手册可以周更,但历史工单和日志是持续产生的。一开始我们的数据管道每天凌晨全量重建一次索引,看起来够用,可白天的新故障完全没有记录,助手回答不了当天上午刚发生的问题,运维同学立刻没了信任感。另外,线上并发一起来,向量检索加上LLM生成,单次响应经常飙到二三十秒,用户等得直接关页面。后来我们把全量重建改成增量同步,新工单一落库就异步触发Embedding和索引写入;检索链路加了Redis缓存,热门Query直接命中,不用每次都查向量库;大模型响应改成流式输出,第一屏token快速返回。这套组合拳打下去,P95延迟从25秒降到了6秒左右,助手才真正在工单系统里活下来。
2. 为什么Demo效果很好,生产环境就拉胯
三个项目做完,我最大的体会是:Demo证明的是“这条技术路径可能有效”,而生产环境要求的是“整个系统在数据质量、性能、权限、稳定性上同时合格”。很多Demo方案的问题并不是某个环节明显错误,而是多个容易被忽视的小缺口叠在一起,最后把整体拖垮。
2.1 指标只凭“感觉”,Hit Rate天然失真
Demo阶段大家最容易犯的错,就是拿两三个精心挑选的问题验证效果,回答对了就欢呼,回答错了就换Prompt再试。这种验证方式在样本量极小的时候,完全看不出系统真实水平。比如你拿“年假怎么休”问系统,它回答得特别完美,不代表它能正确处理“产假期间遇法定节假日怎么计算”这种边界问题,更不代表它能从两千份文档里准确捞出“驻外人员租房补贴申请流程”这种长尾知识。
生产环境必须把“感觉还行”变成可量化的指标。我现在的习惯是:上线前先建一个评估集,至少几十条真实问题,每条问题标注出期望答案所在的标准文档或片段,然后跑指标,比如Hit Rate、Recall@K、MRR,以及生成侧往往用RAGAS框架里的Faithfulness和Answer Relevancy来打底。有了这套指标,你才能回答一个最基础的问题:这次改动到底是变好了还是变差了。
我在第二个项目里吃过不懂评估的亏。当时为了提升效果,无脑加了一段Prompt指令,自测了两个问题觉得不错,结果评估集上一跑,Faithfulness直接从0.8掉到了0.6,LLM开始自己编文档里没有的内容。如果没有评估集,这种负优化上线后根本发现不了,只会等用户骂了才追悔莫及。
2.2 通用Embedding遇到领域词汇就失灵
Embedding模型的选择,是Demo和生产分歧极大的另一个点。Demo阶段用OpenAI的text-embedding-3-small,或者国产的bge-m3,效果都还行,因为测试问题通常是大白话。但一到垂直领域,通用模型的词汇覆盖能力就开始露怯。
举一个很典型的例子:运维文档里通篇写的是“OOM”“CrashLoopBackOff”“ETIMEDOUT”,但用户提问可能说“内存溢出”“容器反复重启”“连接超时”。通用Embedding对同义词和领域缩写的语义映射能力是有限的,明明知识库里有正确答案,向量检索就是召不回,因为Embedding之后,“OOM”和“内存溢出”的向量距离没有想象中那么近。
针对这个问题,我们的做法分两步。第一步是建设领域词典和同义词表,检索前对Query做标准化改写,把口语和中文术语映射成文档里常用的英文缩写和标准说法。第二步是积累一批领域内的Query-Document对,用它们微调Embedding模型,让模型真正学懂这个词在这个行业里长什么样。微调确实有效,但成本不低,所以如果预算有限,我建议先把Query改写做好,性价比高得多。
2.3 向量检索不是万能的,混合检索和Rerank才是标配
Demo里很多人只用一个组件:向量检索。把文档切块、Embedding、存入向量库,Query进来也向量化,然后做近似搜索,看起来一切都顺理成章。但生产环境里,纯向量检索的短板会暴露得非常明显:它擅长语义相似,却对精确匹配极不友好。比如文档编号“DBA-102”,设备型号“FS-3000”,报错码“E-15”,这些值基本上属于“关键词精确命中”的范畴,靠语义向量去召回,经常排到老后面。
我在第一个项目里就遇到这个问题。用户问“FS-3000的E-15怎么处理”,纯向量检索召回的片段居然先返回了“FS-5000”的文档,因为它们的语义描述太像了,而真正包含精确型号和报错码的片段反而排在后面。后来我用了混合检索:向量召回和BM25关键词召回并行,再把两个结果合并,这条路径在精确匹配场景下提升非常明显。
比混合检索更重要的,还有Rerank。我们系统里跑过一组对比实验:不加重排,正确答案进Top3的比例约62%;加了一个开源的Cross-Encoder重排模型后,正确答案进Top3的比例涨到了84%。Rerank的本质是拿Query和候选片段逐对做精细的相关性打分,替代向量相似度那种相对粗糙的距离度量,这一步在Demo里往往被省掉,但在生产环境几乎不能省。
3. 撑得住生产环境的RAG,到底该怎么搭
前面讲了问题和教训,下面说说我现在搭生产级RAG时用的工程框架。这套框架不是灵光一现,而是从三个项目里一点点磨出来的。
3.1 数据管道:从“一堆文件”到“干净索引”
数据管道是整个RAG的地基,Demo可以手工处理几十个文件,生产必须自动化处理成百上千个来源。我现在一般把管道拆成六步:接入、清洗、解析、切分、Embedding、写入索引。
接入环节要考虑数据源的类型:数据库、文件服务器、外部API、网页都可能成为知识来源,每类来源要有独立的采集器。清洗环节要干掉重复文档、无效页面、敏感信息残留。解析环节最重要,HTML标签要剥离,PDF要分文本型和扫描型,扫描型必须走OCR,表格要么转成Markdown表格保留结构,要么单独存入结构化字段。切分环节,我现在更推荐“标题感知切分”加“父子切分”的组合:先用文档结构把层级理顺,切成有语义边界的块,同时把一小块一小块的“子块”用于检索,再把它们所属的大块“父块”送给LLM做上下文,这样既能精准定位,又不会让模型只看一个碎片而失去上下文。
Embedding和写入环节要考虑批量和幂等。批量是为了效率,几百个文件一次性送进Embedding接口时,要处理限流和失败重试;幂等是为了增量更新,每个文档或片段都要有稳定的ID,内容变了要能识别出来重新Embedding,内容没变就不动,避免每次同步都全量重建。生产运维场景里还要注意时区、字符编码这些细节,我曾经因为一个UTF-8 BOM头导致一批文档标题乱码,检索质量直接崩了,查了半天才发现是编码问题。
3.2 检索链路:性能、缓存、并发一个都不能少
数据库选型上,Demo随便用个免费向量库没问题,生产要结合数据规模和部署条件。我按常见的几个选项给过同事一张对照表:
数仓熟悉PostgreSQL且数据量在百万级以内,pgvector很顺手,能和业务数据放一起,减少一套基础设施。数据量几百万到千万,需要混合检索,Qdrant比较省心,性能稳定,自带Hybrid Search能力。大规模和复杂部署,Milvus是主流选项,但需要K8s和运维资源,成本高一些。如果公司原本就有Elasticsearch,也可以直接用它的KNN功能,团队不需要学新东西。关键不是追新,而是看自己有没有能力养这套组件。
索引参数方面,如果用HNSW,M值一般取16到32,efConstruction取100到200,查询时efSearch取64到256,这几个值越大召回越准、延迟越高,要结合QPS预算去调。我习惯先设一组保守参数上线,再根据监控数据慢慢把efSearch调大。
缓存是生产环境最立竿见影的性能手段。用户问“年假怎么休”,第一个问完把答案缓存起来,第二个用户再问,直接从缓存返回,根本不用走向量库和LLM。缓存不能只看完全相同的Query,还要做语义缓存:把Query向量化,在缓存库里找相似度高的历史Query,命中后直接返回对应的缓存答案。这个方案对重复性问题效果很恐怖,能砍掉大部分重复计算。
并发和延迟控制方面,OpenAI这类接口都要做限流和超时控制。流式输出是必须的,宁可让用户先看到第一个字,也不要让用户盯着空白页面等十秒。还可以对检索链路做降级:如果重排服务挂了,直接返回未重排的结果,保证服务不中断。
3.3 权限与多租户:检索之前就必须过滤
权限过滤不能放在拿到检索结果之后,必须放在检索之前。原因是向量检索基于语义距离,如果查询本来只应该覆盖员工A有权限的文档,但你把全库都检索了,就会有两个问题:第一,无权限的文档可能被检索出来,存在泄漏风险;第二,有些文档离Query近,但由于权限被过滤掉,Top N结果直接少了一批,剩下的大概率不是最优的,更常发生在多租户系统里。
生产上我用的方案是文档级ACL加元数据过滤。每个文档或片段在入库时,除了内容向量,还要写清楚它的可见范围,比如部门、角色、保密级别。用户在查询时,系统先根据用户身份计算出他有权访问的文档集合,再把过滤条件传到向量库检索的Filter参数里,确保候选集只包含有权限的内容。如果用的是Elasticsearch,这个过滤条件可以走Post Filter或者布尔查询的Filter子句,很多支持SQL的向量库也有类似能力。
这里的关键点在于:权限模型是业务系统的一部分,不能单纯让RAG自己管。最理想的做法是,RAG的知识库元数据直接来自业务系统的权限中心,或者至少保持同步,否则一个员工调岗后,知识库里的权限标签还是旧的,那就会出现过期权限或误放权限。
3.4 可观测性:没有日志和评估集,优化等于盲人摸象
生产系统必须能回答三个问题:一次查询走了哪些步骤、每一步耗时多少、哪个环节丢了答案。我在每个RAG项目里都强制加了一套检索链路日志,记录用户Query、改写后的Query、召回的候选文档ID和得分、重排后的顺序、最终送给LLM的上下文片段、LLM的回答。这样任何一个线上反馈“答案不对”的Case,我都能顺着日志倒推问题出在哪个环节:是召回没召到,还是召到了但排序不对,还是模型理解跑偏了。
监控指标里,我比较关注四个:平均检索耗时、总响应耗时、空结果率和Badcase率。空结果率代表知识库里根本没有相关内容,说明知识覆盖有坑;Badcase率需要人工抽样或用户反馈来喂,虽然不能全自动化,但积累到一定量后非常有用。
评估集不是建完就完了,要持续回流线上Badcase。每次用户反馈说“答错了”,我们都会把这个Query加进评估集,然后跑回归。我见过太多团队上线当天测得很好,一个月后知识库换了、模型版本升了,效果慢慢退化,但因为没评估集,谁也说不清是不是退化了。
3.5 Agentic RAG可以上,但必须带护栏
最近Agentic RAG讨论很多,热词里也有。我的态度是:生产环境可以用,但别当成银弹。简单RAG是一次检索、一次生成,Agentic RAG则是让模型在回答过程中自己判断要不要检索、要检索什么、是不是需要多步查询,甚至调用外部工具。比如用户问“服务Pod反复重启,帮我查一下最近的工单里有没有类似案例”,Agent可以先把它拆成三步:先查日志关键词、再搜故障知识库、最后汇总答案。这种体验确实比一次检索强。
但Agent带来的问题是可控性下降:模型可能在检索和工具调用之间反复横跳,导致延迟飙升;也可能为了“完成任务”编造检索结论;还可能陷入死循环。生产环境里,我建议给Agent加上硬性护栏:限制最大调用步数,超时强制返回;每步检索都要带可验证的上下文来源;最终答案要求引用来源片段。如果业务没有强烈要求,我更倾向先用一个轻量版的Query改写加意图路由,而不是一上来就上全自由度的Agent框架。
4. 从Demo到生产,我最后总结的几条实操建议
4.1 先建评估集,再谈优化
这句话我在内部反复讲:没有评估集的RAG优化都是玄学。具体操作分三步,第一步收集真实Query,从历史客服聊天记录、工单系统、搜索日志里捞一批用户真正在问的问题,别自己编。第二步让业务方标注参考答案,比如“年假怎么休”,对应文档和片段是什么。第三步定义指标,先看Hit Rate和Recall@K,再看Faithfulness和Answer Relevancy。评估集建好后,每一次切分调整、模型换代、Prompt修改都要跑一遍回归,跑完才能决定要不要上线。
4.2 数据预处理和切分参数参考
中文场景下我常用的切分参数是:递归字符切分,chunk_size控制在500到800之间,overlap设50到100。这个组合对大部分制度、手册类文档效果不错,但表格和列表文档要单独处理,不能一刀切。如果是技术手册,强烈建议用标题感知切分,先保留文档层级结构再切,切出来的块语义更完整。父子切分也推荐优先试点,子块做检索、父块做上下文,能明显减少碎片化问题。
“RAG知识库能存图片吗”这个问题我经常被问到。答案是能,但绝大多数场景下不建议直接把图片作为检索对象。更靠谱的做法是先把图片里的文字用OCR提出来、图片内容用视觉模型转成文字描述,存成文本片段参与检索和生成。除非你的知识库纯是设计稿或监控截图,而且专门训练过多模态Embedding,否则直接对图片做向量化检索,效果一般很差,还容易把延迟拖高。
4.3 一个小工具和一个很难查的坑
工具方面,我最近在Java项目里用了LangChain4j的Easy RAG模块,它在Spring生态里接向量库和文档解析比较省事。但不管用什么框架,都要警惕框架把底层细节黑盒化,出了问题反而更难看。
还要分享一个很难排查的坑:数据库里明明有知识,检索也召回了,但LLM的回答就是不对。后来发现Prompt把系统消息写得太长,把“先回答知识库内容”的指令埋没在了一堆无关规则里,模型根本“没看到”最重要的约束。这种问题用评估集一测就暴露,但如果在线上,你可能会先怀疑检索、再怀疑模型、最后才检查Prompt,兜一大圈。所以,日志里一定要把最终送进LLM的完整Prompt记下来,出了Case直接看输入,别猜。
4.4 备份和恢复意识要提前有
因为做运维场景项目,我还被问过“生产库没备份,误删了一个用户的所有表怎么恢复”这种问题。这不是RAG本身的事,但凡是接触生产库的团队,我都建议先把恢复演练做起来。我们做RAG知识库时,向量库和元数据库一样要备份,虽然重建索引不复杂,但全量重建往往要数小时,这期间系统不可用,业务一定跳脚。所以,不要把向量库当成普通缓存,它有状态,必须有备份、恢复和容灾方案,就像对待一个正式数据库一样。
5. 写在最后的几点体会
这三个项目一个比一个复杂,但做完之后我反而越来越谨慎。以前看到新出的RAG框架和工具,总想赶紧接进来试试,现在第一反应是:它有没有破坏权限边界?会不会让延迟恶化?评估集能不能证明它真的更好?技术选型的热闹很多,但生产环境要的是稳定、可观测、能兜底。
我现在养成了一个特别土的习惯:每个RAG项目上线前,都要拿一个真实用户场景,从头到尾手动跑一遍,然后故意问一些边界问题、权限外的问题、知识库里没有的问题,看看系统是不是能得体地承认“不知道”。这个习惯帮我拦下了不止一次事故,也希望它能帮到你。