资讯详情

大模型闭卷必挂?从零搭建RAG检索增强生成系统的实战指南

📅 2026/10/21 5:24:16 | 华诺云谱 👁 阅读
大模型闭卷必挂?从零搭建RAG检索增强生成系统的实战指南
说实话最开始看到“闭卷考试”这个说法的时候我还觉得只是大家调侃大模型的一个梗。直到有一次我拿自己写的一份私有技术文档去问当时最火的几个大模型回答得那叫一个自信——连版本号都能给你编出来但跟文档里的真值差了十万八千里。那一刻我意识到这事不能只当段子看。大模型的“闭卷考试”翻车翻的不是智商而是机制。它脑子里的知识是训练时压缩进去的“旧闻摘要”不是可以随手翻开查阅的数据库。想让它答好自己知识范围之外的问题唯一靠谱的思路就是让它“开卷答题”——这也正是RAG检索增强生成真正被大规模应用起来的原因。这篇文章我不会从概念PPT讲起而是直接带你走一遍大模型为什么闭卷必挂、RAG开卷的原理是什么、怎么从零搭一个能用能测的RAG系统以及我在实操里踩过的那些坑。不管是刚入门的开发者还是想在公司内部快速落地知识问答的你照着这篇的链路走至少能少走两三个星期弯路。1. 大模型“闭卷考砸”的真相幻觉不是bug是机制很多人第一次遇到大模型一本正经地胡说八道第一反应是“这模型是不是坏了”。其实没有坏它只是在一个本质上不适合“准确回忆细节”的机制里被逼着给一个概率最大的回答。1.1 模型脑子里的“知识”到底是什么如果你把大模型理解成一个压缩过的知识库虽然是简化但方向是对的。训练阶段它看过海量文本然后把其中的统计规律、词与词之间的关联模式都压缩进了神经网络的上百亿个参数里。问题在于这种压缩是有损的。它记住的不是“XX产品V3版本最大并发是5000”这种精确记录而是一种“大概齐”的语义倾向。你问它“写一段SQL的思路”它能给你写得有模有样因为这类通用模式在训练数据里反复出现规律性强压缩起来不费劲。但如果你问的是某个内部系统的字段含义、某份合同条款的确切表述、或者昨天刚上线的新品参数它就完全没有可靠来源可依据了。这时候模型会做什么它不会老老实实说“我不知道”因为它没有“自我觉察”的能力。它只会继续顺着语言模式往后“编”把看起来合理的词串起来。这就是幻觉的直接来源。我把闭卷考试的场景化描述为——它脑子里装着一本几周前印刷的百科词典还是缩印版你问它词典里写得清清楚楚的细节它只能凭印象给个差不多。1.2 什么时候你必须放弃“闭卷”转向“开卷”不是所有问题都需要RAG。你问“怎么写一个Python装饰器”模型闭卷答得比你还标准完全没有检索的必要。但当问题满足下面三个特征里的任意一个闭卷模式就撑不住了私有知识知识不在公开语料里只在你们公司的文档、代码库、数据库里。模型压根没看过闭卷就只能编。实时信息训练截止时间是硬的之后发生的事情模型一概不知。你问它昨天发布的产品价格它只能给你一个“参考价”而且不会主动告诉你这只是编的。长尾细节公开语料里虽然有但太过冷门训练压缩时根本没保留足够权重。比如某份早期协议里的例外条款、某个小众开源项目的配置项。我见过很多团队非要用“闭卷”硬扛这些场景结果就是做一个看起来有问必答的ChatBot实际上就是一台高级瞎话生成器。内部测试时大家讲个笑话就过了真上了生产环境一线业务人员拿着它答错的结果去处理客户问题那后果就完全不搞笑了。所以RAG的思路很朴素既然模型背不住那就让它现查。你给它配一本实时更新的“参考书”它答题的时候先翻书再照着书上的内容组织语言。这就是“开卷答题”。2. RAG的“开卷”原理一次查询的完整旅程先别急着写代码把下面这个流程在脑子里走一遍之后每一行代码你都能看得懂在干嘛。2.1 三步主链路检索、增强、生成一个最简RAG系统回答一个问题只需要三步。检索把用户的问题转换成一种机器好比较的形式去你那堆“参考资料”里捞出最相关的几段文本。增强把捞出来的文本连同原始问题一起拼成一段结构化的提示词Prompt塞给大模型。相当于把参考书翻开的页码直接摆到考生面前。生成大模型基于这段“参考资料问题”来生成回答。它不再是凭记忆编而是以你给定的材料为事实依据进行归纳和总结。这三步看着简单但每一步都有不少工程细节。尤其是第一步“检索”的质量直接决定了整个系统是“开了个假卷”还是“真的翻到了那一页”。2.2 Embedding的直觉把文字变成可计算的坐标检索的核心不是“关键词匹配”而是“语义匹配”。你问“最近有没有什么省钱的办法”文档里写“如何降低日常开销”两者字面不一样但意思接近。要机器理解这种接近程度就得靠嵌入模型Embedding。嵌入模型做的事是把每段文本变成一个几百维甚至上千维的浮点数向量。这个向量是模型训练出来的语义坐标语义越相近的文本向量在多维空间里的距离越近。你跟用户的问题也各自转化成一个向量然后用“余弦相似度”或者“点积相似度”来度量距离取最相近的top-k个片段作为参考材料。这一步我做个类比你不再去图书馆的书架上按标题翻书而是把每本书的内容都抽象成一张“气味卡”读者进门之后递给检索员一张“气味卡”检索员进库房把所有气味相近的书全部抱出来给你。这样即使书名完全对不上也能检索到真正相关的章节。2.3 向量检索之外为什么还提到“混合检索”和“重排序”实际做RAG只用向量检索一种手段往往不够。原因很简单向量模型对语义相近的段落召回效果好但对精确的关键词匹配反而容易漏。比如某份配置文档里的参数名“max_retry_count”你用向量检索可能召回一堆讲重试机制的泛泛内容但恰恰没召回那条写满具体参数定义的记录。所以生产环境里我通常建议做混合检索向量检索找语义相近的关键词检索BM25找字面精确匹配的两者结果合并再用一个重排序模型把合并结果重新打分排序。重排序模型更精细能理解“用户要的是参数定义不是重试理论”这种细粒度差别。如果你还在起步阶段可以先只做向量检索把整条链路跑通。等发现“明明有相关内容但答案就是差那么点意思”的时候再上混合检索和重排序。后面我会专门讲这个优化节奏。3. 从零搭一个能跑的RAG系统实操链路与代码骨架下面这部分是硬核实操。我以“给一份私有产品说明文档搭建问答助手”为场景带你写一个mini的RAG流程。为了让你看见全貌又不会被无关依赖绑住代码会用可替换的接口骨架你换成自己熟悉的依赖就能跑。3.1 第一步文档准备与切分Chunking很多新手直接拿整篇PDF丢进向量数据库结果检索时召回的是“整个文档”不仅塞爆上下文窗口答案还稀碎。正确的做法是先切块。为什么要切块因为嵌入模型一次能编码的文本长度有限而且太长的文本向量化后语义会被稀释——一段5000字的文档向量最后变成了一团“五味杂陈”的平均值跟任何具体问题的相关性都会被摊平。反过来切得太碎也不好比如把一句话切成一块上下文信息断裂检索到了也读不懂。我常用的配置是每块256到512个token相邻块之间重叠50个token左右。重叠是为了避免关键信息刚好被切刀的边界劈开。代码长得这样def split_text(text, chunk_size500, overlap50): 按字符数切块保留overlap避免上下文断裂。 实际生产可以按章节、段落、句子做结构化切分效果更好。 chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) # 下一次起始位置要回溯overlap个字符 start end - overlap return chunks如果你的源文档本身就是结构化的有标题、有章节强烈建议先解析出结构再按“章节优先字符兜底”的方式切分。这比纯按字符硬切强太多了。比如Markdown文档可以先把二级标题拆出来作为大块再在每个大块内部按段落切。这样切出来的每一块都有相对完整的逻辑边界检索命中率会显著提升。3.2 第二步向量化与入库文档切好之后要分批送给嵌入模型转成向量然后写进向量数据库。这个阶段的注意点有两个一个是批处理大小别设太大免得触发嵌入服务的限流或者内存溢出另一个是原始文本必须跟着向量一起存因为向量是给人看的坐标用户最终要读的是原始段落检索结果最终还是要用原始文本拼Prompt。from embedding_client import get_embedding_model # 示意替换为你的嵌入服务 from vector_store import VectorStore # 示意替换为你的向量数据库客户端 def build_index(chunks, metadata_list, store): 把chunks批量向量化并写入向量数据库。 metadata里至少保留来源文档名、页码/章节号方便溯源。 model get_embedding_model() batch_size 32 for i in range(0, len(chunks), batch_size): batch chunks[i:ibatch_size] vectors model.encode(batch) metas metadata_list[i:ibatch_size] store.add(vectorsvectors, textsbatch, metadatametas)入库完成之后你就有了一本“语义索引字典”。这里有一个特别容易被忽略的点向量数据库的索引类型和相似度算法要跟你选的嵌入模型匹配。有的嵌入模型训练时用余弦相似度你入库时却选了内积看起来差不多但检索排序结果可能差很多。我建议统一用余弦相似度兼容性最好。3.3 第三步查询端的检索与生成用户输入问题之后查询端的代码逻辑是这样的def retrieve(query: str, store: VectorStore, k: int 4): 把问题向量化检索最相关的k块文本。 model get_embedding_model() q_vec model.encode([query])[0] hits store.search(q_vec, top_kk) # hits里每个元素包含text和metadata按相关性从高到低排列 return hits def generate_with_rag(query: str, store: VectorStore, llm, k: int 4): 检索增强生成主函数。 hits retrieve(query, store, kk) context \n\n---\n\n.join( f[来源: {h.metadata[source]}]\n{h.text} for h in hits ) prompt f你是一名技术问答助手。请严格依据下面提供的参考材料回答问题。 如果参考材料中没有足够信息请直接说“根据当前资料无法回答”不要自行编造。 参考材料 {context} 用户问题 {query} answer llm.generate(prompt) return answer, hits这一步最核心的是Prompt的写法。你必须在Prompt里明确告诉模型三件事一是“你的答案要以参考资料为准”二是“资料不够时承认不知道”三是在生成时尽量引用来源。我试过好几种Prompt风格最省心的就是上面这种直白写法既不绕弯又能显著压低幻觉。3.4 跑一次完整测试从闭卷翻车到开卷正常我用一份虚构的“某产品V3版本技术说明”做了个对照实验问题问的是“V3版本支持的最大并发连接数是多少”。这个数据在文档里明确写了5000但模型训练数据里根本没有这份私有文档。测试方式模型回答是否正确闭卷不加检索直接问根据架构设计推测最大并发连接数可能为1000到2000左右建议进行压测验证。错且语气极其自信开卷接RAG链路根据参考资料“某产品V3版本技术说明”第2.3节V3版本最大并发连接数为5000同时支持水平扩展。对且给出来源位置这个对比我每次做分享都会拿出来用。它非常直观地说明了一件事RAG的价值不是让模型变聪明而是让模型的输出有据可依从“猜一个看起来合理的答案”变成“归纳一段真实存在的资料”。4. 实测中绝不省心的那些坑检索质量与幻觉残留跑通Demo只要半天真正花时间的全在优化细节上。下面这几个坑我基本都踩过一轮直接给你避雷。4.1 上下文塞得越多回答越散新手最容易犯的错是top-k恨不得设成20想把所有相关片段都塞给模型让它“全面一点”。实际效果往往是灾难性的上下文里混进来好几段相关性不高又不互斥的内容模型最后只能东拼一句西凑一句回答又长又没有重点。我的经验是针对大部分知识问答场景top-k设为3到6之间最合适。检索质量高时甚至3就够检索质量一般时最多到6。不要试图用上下文长度去弥补检索的不足检索不到就该先优化检索而不是暴力堆料。另外塞进去的片段最好在Prompt里明确标注“优先级从高到低”并告诉模型“优先采用排序靠前的资料”。这能有效避免模型把次要背景信息当成重点来答。4.2 切片边界把答案劈成了两半这是一个特别隐蔽的问题。我遇到过一次用户问“导出功能支持哪些格式”文档里其实写了“支持PDF、Word、Excel其中Excel导出仅限企业版”。但我的切片策略刚好在这一句中间切开了第一块只到“推荐使用PDF或Word”第二块开头是“Excel导出仅限企业版”。结果就是模型拿到两块碎片第一块相关性打分还更高它就直接答成“支持PDF和Word”把Excel给漏了。这就是典型的切片边界问题。解决办法有两层第一层是切分时尽可能按语义边界段落、列表项、表格行来切而不是纯字符长度硬切第二层是在检索时把相邻几块文本一起召回比如命中第7块时把第6、8块也带上给模型一个完整的局部上下文。这两个方案可以叠加用效果很稳。4.3 模型还是偶尔“自由发挥”如何用带引用对抗幻觉即使检索成功了模型在生成时还是可能为了“把话说顺”而添油加醋。对抗这个问题的第一道防线是Prompt里强制加“引用来源”的要求我一般这样约束每一句关键结论后面都要用“来源文档名章节号”标注。如果回答里出现了参考资料没有的信息整段标记为“推测内容”并在开头明说“以下为推测”。这个做法的妙处在于一旦模型被要求每条结论必须关联到具体来源它就没有空间去编造作者、版本号、参数值这类具体事实了因为编出来的来源对不上时一眼就能看出来。如果你连“带引用式生成”都压不住幻觉那就要回到检索端找原因——大概率是召回的内容不够直接命中问题模型只能靠推理来弥补信息缺口。这时候优化重点不是Prompt而是嵌入模型、切片粒度、重排序这些检索链路环节。4.4 别拿感觉当评估给RAG系统做次“体检”我见过很多团队在“感觉不错”的时候就宣布项目完工结果换一批问题就问崩了。RAG系统必须有一份自己的评估基准。我的做法是为当前场景手工准备50到100个真实问题每个问题标注预期答案和答案所在的源文档位置。然后分两个维度做评测检索命中率正确答案所在的文档片段是否出现在召回的top-k里。这个指标衡量的是“书有没有翻到正确页码”。生成准确率生成的最终答案里关键事实是否与源文档完全一致。这个指标衡量的才是“答案答没答对”。下表是我整理的一份简化评测记录评估维度评测方法判定标准我的建议阈值检索命中率人工核对top-k片段里是否包含答案来源包含即命中不低于90%答案准确率人工比对最终回答与源文档事实关键事实一致不低于95%顽固幻觉率回答中出现源文档不存在的事实每10题不超过1次越低越好这个评测集最好能沉淀下来之后每次调整切分策略、换嵌入模型、改Prompt都跑同一份评测集用数字对比效果而不是靠“感觉这次好了点”。我吃过这个亏——有一次优化后主观感觉变好了跑评测才发现检索命中率反而掉了8个百分点就是因为加了太激进的文本清洗规则把关键参数行给删了。5. RAG之外的选择什么时候微调什么时候混着用写了这么多RAG的妙处但我不想把它吹成万能药。在实际项目里“给模型开卷”和“让模型背熟”从来不是二选一而是看问题类型分工合作。5.1 RAG比微调强在哪又弱在哪RAG的强项非常突出一是知识更新零成本文档变了索引重新跑一遍就行模型本身不用动二是可溯源回答的每句话都能指向某个来源这对企业合规和信任建设非常重要三是门槛低不需要训练GPU资源只要会调嵌入接口和模型接口就能上手。但它也有明显的短板。开卷答题要求“考场里必须有书”如果用户问的东西你的知识库里压根没有RAG再强也无能为力。而且每次回答都带着一坨检索结果延迟和token成本都比闭卷要高。微调则刚好相反。它适合的场景是模型的“表达风格”和“行为模式”需要固定比如你要模型始终用某种行业黑话回复、严格按照某种JSON结构输出、或者在特定领域的语言习惯上要更像“自己人”。微调是把这些行为倾向刻进模型的参数里它回答起来自然流畅不需要每次带外部材料。5.2 按职责分工的混合架构在比较完整的系统里我习惯这样分工事实型问题参数、价格、流程、政策条款、代码报错信息→ 交给RAG答案必须有来源。风格型问题欢迎语、邮件写作、公文润色、固定格式报告→ 交给微调过的模型保持风格统一。混合型问题比如“按这份故障报告帮我写一封给客户的安抚邮件”→ 先用RAG把故障处置结论捞出来再由微调过的模型组织成符合公司风格的对外文本。这样一个系统里RAG负责“肚子里有货”微调负责“说话有范儿”各司其职效果比单押一边好很多。做技术选型时你先问自己一个问题这个场景的核心矛盾是“知识不够”还是“表达不对”前者找RAG后者找微调两个矛盾都在就两边都上。另外一个成本提醒RAG的工程复杂度并不低知识库维护、质量评估、检索调优都是持续投入。如果只是给一百份固定文档做个内部问答RAG是性价比之王如果目标是训练一个懂你们行业黑话的对话助手那先把微调提上日程更实际。最后再分享一点个人体会。跑通一个RAG demo真的不难难的永远是检索质量、评估集和持续迭代。我建议你第一版先用最朴素的流程手写一遍把每个环节都摸透再去看那些成熟的编排框架。自己亲手实现过一遍检索和增强的拼接逻辑之后你会对“为什么框架要这么设计”“为什么这么配置”有完全不一样的理解。开卷答题这件事本质上就是把知识的存取权交还给使用者大模型不再是学习者留下的一个静态倒影而是一个随时能查阅最新资料的研究员。这个思路接下来还能扩展成多路召回、Agent式多轮检索、结构化知识图谱增强等等但万变不离其宗——先把“翻书”这一步翻好后面一切都好说。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑