资讯动态

RAG实战指南:从原理到最小实现,搭建检索增强生成系统

发布时间:2026/10/22 8:26:24 来源:尧图企业网站定制
如果最近你在捣鼓大模型应用身边一定有人在聊 RAG。它全称是 Retrieval-Augmented Generation检索增强生成解决的问题一句话就能说清让大模型开口回答之前先去你的资料库里查一圈再基于查到的内容说话。这篇就是写给想动手搭第一个 RAG 系统的人。我会按照自己做一遍的顺序从为什么需要它、系统怎么拆、关键环节怎么选到最小可运行版本怎么落地再到实际运行常见的坑一次性讲清楚。无论你是做后端、做数据、还是产品想了解原理这篇都能帮你少走几晚弯路。1. 为什么先做 RAG而不是微调模型1.1 一句话说清 RAG 解决的问题大多数把 RAG 搬进生产环境的团队最初遇到的场景都很朴素公司内部有一堆手册、合同、工单、产品文档领导说“你们做个客服机器人要能根据我们自己的资料回答问题”。直接让大模型硬答问题很明显。模型训练时根本没见过这些内部资料你问它“咱们这个 API 的限流策略是什么”它只能凭类似经验编一个。编出来的东西往往格式工整、语气自信但数字和信息全是错的。这就是大模型幻觉最典型的来源。RAG 的思路是把“知识”和“说话能力”拆开。知识存在外部资料库里说话能力由大模型提供。每次用户提问系统先去资料库检索最相关的几段内容再把“资料原文 用户问题”一起塞给大模型让它看着资料回答。这样回答有出处、可追溯知识更新时不用重新训练模型改索引就行。一句话概括RAG 是给模型外挂了一个可随时增删改查的动态知识库。1.2 RAG 和微调不是互斥关系很多人第一次了解 RAG 时会反问那我直接微调模型把它训练成本公司专家不行吗行但要分场景。微调适合改变模型的表达风格、输出格式、固定办事流程。比如要求模型每次都以 JSON 结构化输出或者严格模仿某种客服口吻这类“行为层”的问题微调更合适。但它不适合承载频繁变动的业务知识。假设你每周更新一次价目表每次更新都微调一遍成本和时间都扛不住。RAG 适合知识密度高、变动频繁、需要引用的场景。资料库更新只需要重新写入新的文档片段模型本身不用动。代价是每次回答都要多一步检索耗时更长还要维护一套向量索引和查询链路。在实际项目里两者经常组合使用。先用微调让模型学会“怎么说话”和“按什么格式输出”再用 RAG 决定“说什么内容”。对第一次搭 RAG 系统的人来说不要一上来就把微调也加进来否则问题排查时变量太多定位不到根因。1.3 第一个系统不追求指标我见过不少朋友搭第一个 RAG 系统目标定得特别大要支持十万份文档、要回答准确率 95%、要支持流式输出、要能引用多级来源。结果搭了三天还没跑通。第一个 RAG 系统的目标应该收敛成一句话给定一个小型资料目录系统能根据用户提问返回一段有依据、不像在胡扯的回答。哪怕只有几百个片段哪怕回答偶尔不完美只要整条链路能通就已经赢了一大半。后续优化指标、扩展规模都是在这个最小闭环上做加法。先保证“数据能进、检索能出、模型能用”再谈准确率。2. 动手前先把整条管线画明白2.1 RAG 系统由哪几部分组成RAG 表面上就是“检索 生成”但拆开看至少有六段文档加载、文本清洗、分块、向量化、向量存储、检索与生成。文档加载负责把 PDF、Word、HTML、Markdown 等格式读成纯文本。这步看起来简单实际坑最多。PDF 可能是扫描件Word 里可能有图片和表格HTML 里混着大量导航和广告文本。第一个系统可以先只用 Markdown 和纯文本文件避开格式解析的大坑。文本清洗是把加载出来的噪声去掉。表格转为缩进排列、多余空行删掉、页眉页脚剔除。很多人忽略这步导致向量库里有大量“联系电话xxx”和“第 3 页共 12 页”之类的碎片检索时容易浑水摸鱼。分块是决定检索上限的关键动作后面单独讲。向量化是把每块文本变成一串数字。向量存储负责把大量向量组织成可快速检索的索引。检索与生成是问答接口侧的逻辑把用户问题变成向量、在索引里找最相似片段、拼成提示词交给大模型。用图书管理员类比资料库像藏书、向量索引像目录卡片、大模型像前台客服。客服不能背下整座图书馆但能根据目录迅速翻到相关几页再照着念给你听。2.2 技术选型的三条取舍线选型不需要一步到位。第一个系统建议遵循一个原则能本地跑的就不上分布式能先用简单方案就不急着引入重组件。第一条取舍线是向量数据库。轻量级方案把索引存在本地文件或内存里适合百万级以下向量部署简单重量级方案适合团队协作和数据量持续增长但运维成本和复杂度明显更高。第一个系统用轻量级就可以后面数据量上来再迁移。第二条取舍线是向量化模型。有通用嵌入模型也有针对中文语料优化过的模型。如果你的资料全是中文优先选中文表现更好的模型如果是中英混杂再考虑通用模型。不要只看榜单分数拿自己业务里的几段文字实际试一下检索相关性比任何评测数字都直观。第三条取舍线是大模型。商业化模型 API 效果稳定、省事但数据会出公网对隐私敏感场景不友好本地部署开源模型保密性好但需要 GPU 资源效果也通常弱一些。第一个系统如果只是为了验证流程用哪个都行关键是先跑通。2.3 提前准备一组验收问题搭系统之前先准备几组测试问题。这一步被很多人跳过但我觉得这是最值钱的前置工作。从资料目录里挑几个有明显答案的问题比如“退货政策是什么”“超时时间是多少秒”“鉴权有哪几种方式”。再准备几个资料里完全没有的伪问题比如“我们公司什么时候上市”假设资料里没有。验收标准也要简单一点资料里有的问题系统能不能答出关键信息资料里没有的问题系统能不能老老实实说“资料中未找到”而不是硬编一个答案。这两条达到了最小闭环就算成功。这套测试问题集后面还能复用改参数、换模型时用同一组问题对比效果比拍脑袋判断靠谱得多。3. 决定 RAG 效果好坏的四个关键环节3.1 Embedding 模型怎么选Embedding 模型负责把文本变成语义向量它直接决定了“检索这一步能找到什么”。如果模型没理解“退款手续费”和“退款扣款”是同一个意思那检索环节的分再高后面生成也是无米下锅。选型时看三个点。第一是否覆盖你的语言。中文资料多就选中文语料训练充分的模型通用英文模型在中文语义上的表现往往比较飘。第二向量维度。维度高通常表达能力更强但存储和计算开销也大。第一次做不要追求高维大模型中等规模的嵌入模型足够验证流程。第三近期有没有更新。嵌入模型也在迭代新模型在长文本和细粒度语义上普遍更好。我实际用下来的经验是不要直接信某一个模型的公开评测拿自己的五六个典型问题去测。把资料库里对应的正确片段混进几十个不相关片段里看排序后到底排在第几位。这个动作半小时就能做完但能帮你避开很多无效调参。另外特别注意查询端的文本和资料端的文本最好用同一个嵌入模型做向量化。如果查询用模型 A、资料用模型 B向量空间不对齐检索结果会非常随机而且这种 bug 肉眼很难发现。3.2 分块策略是第一个深水区分块就是把长文档切成适合检索的小段。这步看起来像体力活实际是决定 RAG 效果最关键的环节之一。切太大会有两个问题。一是块里包含太多无关信息检索时看起来命中了但回答时信息被噪声稀释二是超出模型上下文窗口需要截断。切太小则容易把一句话的逻辑拦腰截断比如把“如果不满足条件则不退款”切成“如果不满足条件”和“则不退款”两段单独检索哪段都缺上下文。比较稳妥的起步参数是块大小 500 到 800 字重叠 50 到 100 字。重叠的意思是相邻两个块共享一部分文本用来保住边界处的语义连贯性。这个参数不是拍脑袋定的要和嵌入模型的能力匹配。模型一般能理解 300 到 500 字左右的完整语义太长它会“记不住”重点。除了按字数切还要尽量保留文档结构。如果能把每个块和它所属的标题、章节绑定检索时就能带上更丰富的语义。比如“售后政策”下面切出来的块即使块里没写“售后”两个字标题信息也能帮它被正确召回。3.3 检索参数Top-K 和相似度阈值检索阶段常见的参数就两个返回多少个候选片段以及相似度最低多少才认为是相关片段。Top-K 太小可能把一个正确信息漏掉太大会把一堆不太相关的内容塞进上下文大模型容易分不清重点。第一次做先取 3 到 5 就行。如果资料片段的粒度小可以适当增加到 8如果单块已经很长3 个就够。相似度阈值是用来挡住明显不相关内容的。比如用户问“今天天气怎么样”资料库里全是产品手册理论上不应该返回任何片段。但向量检索永远会返回“最相似”的 Top-K哪怕相似度只有 0.2。这时候没有阈值过滤模型就会被垃圾上下文带偏。阈值的具体数字和嵌入模型强相关没有通用值。有的模型相似度普遍在 0.7 到 0.9有的普遍在 0.3 到 0.5。我的建议是先不设阈值跑一批问题统计命中的相似度区间然后取一个“能覆盖大多数正确结果、又能挡住明显跑题”的值。3.4 生成环节的提示词决定下限检索做得再好提示词写不对回答照样会崩。生成环节的任务是告诉大模型把什么当依据、按什么格式说话、遇到资料外的问题怎么办。我的提示词模板一般包含四部分角色、资料正文、用户问题、边界约束。角色告诉模型“你是客服助手只能根据给定资料回答”。资料正文要放在问题前面并且明确说“以下是资料”。边界约束最关键明确要求“如果资料中没有相关信息请直接回答未找到不要自行推断”。模板大概是这样的你是一名严谨的资料问答助手。请根据下面提供的资料回答用户问题。 如果资料中没有任何相关内容请直接回复“资料中未找到”不要编造。 资料 {context} 问题 {question}这里有个容易被忽略的点资料里如果包含多条候选片段要用明显的分隔符隔开并标记来源编号。比如“资料片段 1”“资料片段 2”。大模型对并列文本的注意力分配有限不加分隔的话可能只盯着前面或最后一段看。上下文里的不相关内容也要控制。给模型的上下文越精炼它的注意力越集中在有效信息上。很多人习惯把 Top-K 检索到的所有片段全部塞进去结果模型被无关片段干扰回答了错误内容。4. 实操从零搭一个最小可运行版本4.1 先确定自己的技术边界开始写代码之前先确认自己手上有哪些零件。你需要三个基础能力读文档的代码、把文本变成向量的模型接口、调用大模型生成文本的接口。这三个零件都有很多成熟方案不同方案代码略有差异但整体逻辑完全一致。下面的骨架代码用占位函数表示每个环节你可以按需替换成你自己环境里对应的接口。我建议第一次做的时候资料先用少量 Markdown 或纯文本文件放在一个目录里能省掉大半解析问题。还要想清楚数据量级。第一个系统我强烈建议控制在一百段以内跑通为主。数据量一大分块策略和索引参数都会影响效果排查时很难判断是配置问题还是数据问题。4.2 文档加载与清洗先给文本洗澡加载文档就是把目录里所有文件读成字符串。如果你用的是 Markdown 或纯文本这一步非常简单。如果将来要支持 PDF需要额外处理扫描件 OCR 和版式解析那是一个独立的大话题。清洗阶段至少要做三件事删除空行和连续空白、去除明显的页眉页脚、把表格手工转换为“字段值”的文本形式。表格如果直接按原始排版切进去很容易变成一串没有语义的字符碎片。下面是一段示意代码def load_and_clean(file_path): with open(file_path, r, encodingutf-8) as f: text f.read() # 去掉页眉页脚等噪声 text remove_header_footer(text) # 多行空白压缩成一行 text re.sub(r\n\s*\n, \n, text) return text.strip()清洗完成后最好每处理一个文件就打印一段抽样文本出来人工检查。这一步不花多少时间但能避免下游所有环节被格式噪声带偏。4.3 分块把长文档切成可检索的片段分块函数要同时保存“文本”和“来源信息”。来源信息至少包含文档名和块序号以后回答带引用时全靠它。按字数切是最容易上手的方案。实现时注意两个细节一是尽量不在单词或标点中间硬切二是利用重叠保证边界语义连续。你可以先按句子或段落拆再把过长的段落继续切成小块。一段示意代码def split_into_chunks(documents, chunk_size600, overlap50): chunks [] for doc in documents: text doc[text] start 0 idx 0 while start len(text): end min(start chunk_size, len(text)) chunks.append({ text: text[start:end], source: doc[source], chunk_id: idx, }) idx 1 if end len(text): break start end - overlap return chunks如果你发现固定字数切出来的块经常截断表格或代码就改成“按小标题先分段再对超过上限的段继续切”。我自己的经验是技术文档按标题层级切效果普遍优于纯按字数切。4.4 向量化与入库把文本变成索引向量化就是把每一块文本送给嵌入模型拿回一个数字数组。调用时有两个实操要点批量调用而不是逐条调用以及处理限速。批量调用能大幅缩短索引时间。但嵌入接口通常有单次请求的文本条数限制超出会报错所以要用循环分批。限速问题在文档量大时尤其明显需要加指数退避重试。入库时除了向量本身还要保存原始文本和 metadata。很多嵌入向量库只要求你传向量和 ID但如果你不把原文一起存进去检索命中后还得重新根据 ID 回源查文档多一道维护负担。正确的做法是将“向量、原文、来源文档名、块序号”一并存入库中。示意代码vectors [] metas [] for chunk in chunks: vec embedding_model.encode(chunk[text]) vectors.append(vec) metas.append({ text: chunk[text], source: chunk[source], chunk_id: chunk[chunk_id], }) vector_store.add(vectors, metas)入库完成后写一个简单查询函数验证拿一个查询文本做向量化再调检索接口看返回片段是不是你预期的那几段。如果这一步就不对先回去检查清洗和分块别急着调大模型。4.5 在线问答把检索和生成串起来在线问答流程是问题进来向量化向量库检索 Top-K把片段拼成上下文连同问题一起发给大模型拿到回答后附带返回来源信息。这一段是整个系统的核心入口。先看完整骨架def answer_question(question, top_k5, similarity_threshold0.0): q_vec embedding_model.encode(question) hits vector_store.search(q_vec, top_ktop_k) valid_hits [h for h in hits if h.score similarity_threshold] if not valid_hits: return 资料中未找到相关内容。, [] context \n\n.join( f资料片段 {i 1}\n{h.metadata[text]} for i, h in enumerate(valid_hits) ) prompt f你是一名严谨的资料问答助手。 请仅根据下面资料回答用户问题。如果资料中没有相关内容 请直接回复“资料中未找到”不要编造。 {context} 问题{question} answer llm_generate(prompt) sources [(h.metadata[source], h.metadata[chunk_id]) for h in valid_hits] return answer, sources这里两个参数很容易被忽略。相似度阈值初始可以设很低甚至为 0先跑一批问题收集分数分布再慢慢提高。Top-K 建议从 5 开始看回答质量再调整。还有返回来源信息这一步从第一天就要做否则后面任何回答错了你都很难定位是检索问题还是生成问题。4.6 三个测试问题验证闭环系统搭完用前面说的验收问题跑一遍。我建议从这三个角度各选一题简单事实题、资料缺失题、同义改写题。简单事实题用来验证基本链路比如“退货需要满足什么条件”。资料缺失题用来检验提示词约束比如“我们没有收录的价格表”。同义改写题最容易被忽略比如资料里写“退款”问题里说“退钱”看系统能不能给到正确片段。跑通之后不要急着美化。先看两个地方检索返回的片段是否真的相关模型生成的回答是否基于片段。如果片段相关但回答偏了改提示词如果片段本身就不相关改嵌入、分块或 Top-K。系统化排查比碰运气调参数高效得多。5. 常见问题与排查技巧实录5.1 召回结果像是随机抽取相关性很差这是第一个 RAG 系统最高频的问题。优先怀疑三件事嵌入模型和查询端是否一致、分块策略是否把语义切碎、文档清洗是否带着大量噪声。排查步骤可以固定成一套取一个典型问题单独看检索返回的前 5 段肉眼判断相关性。如果前 5 段里连一段都不沾边问题是检索链路如果前 5 段里相关片段排在第 3 位以后问题通常是相似度计算或阈值如果前 5 段都很相关但回答不好问题在生成环节。还有一个容易踩的坑资料目录里混入了大量重复的导航文案比如每页都有相同的“欢迎联系我们”导致检索结果被这些重复碎片霸榜。清洗阶段把它们去掉症状立刻缓解。5.2 资料里明明有答案但系统说找不到最常见的原因是查询表述和资料原文差异太大。用户问“怎么退钱”资料写的是“退款申请流程”。嵌入模型虽然对语义有一定理解但差异过大时依然会失手。对应的常规做法是混合检索。在向量检索之外再加一层关键词匹配然后把两种结果合并排序。比如用户问题里的“退钱”“申请”“流程”这些词直接在文本里匹配能把文档里的相关段落找出来然后与向量相似度得分加权融合。另外检查一下分块大小。如果某些块只有一两个短句语义信息不足嵌入模型很难给出有效向量。我会建议把短段落相邻的几段合并成一个稍大的块效果往往立竿见影。5.3 检索片段相关大模型却没有按资料回答这是生成阶段的典型问题。先看提示词是否明确约束了“只能根据资料回答”。很多模型默认会自行补充常识性知识提示词里不写清楚它就会放飞。再检查上下文长度。如果 Top-K 取到 5每段 800 字上下文就有 4000 字其中真正相关的可能只有一段。大模型看到太长的不相关内容时注意力会被稀释。调低 Top-K或者先把检索片段用重排序器重新排一遍只留最相关两三段给模型。我的一个固定习惯是提示词末尾加一句“回答中不要出现资料中没有的信息”。这句话简单直接能明显减少幻觉输出。5.4 向量库内存和索引体积涨得飞快向量库的体积和嵌入维度、数据量强相关。假设一亿参数级别的嵌入模型输出 768 维向量一万段文本算下来存储量不小内存里做全量检索会越来越吃力。第一个系统不急着换分布式方案。先做两件事一是定期清理向量库中的重复片段和旧版本内容二是把向量索引持久化到磁盘每次启动从磁盘加载而不是在内存里重算。如果数据继续增长到几十万段以上再考虑把索引升级为支持分片和服务化的方案。到那一步之前轻量方案完全够用换架构的成本远大于收益。5.5 中文场景的几个特有坑中文文本没有天然空格分词清洗和分块时很容易把一句话切在语义边界外。按字符数硬切时最好在句子结束符“。”之后再尝试找边界而不是固定字符位置生切。中文标点也是坑。英文嵌入模型可能在处理全角标点时表现不稳定所以尽量让文本统一使用全角标点不要混入半角逗号。这个细节看起来小实际检索时影响不小。表格类中文内容建议单独处理。把表格按行读出来拼成“第一列标题对应值”的格式。否则表格被切碎后每一块只有孤立的数字和字段名谁也没法理解它在说什么。6. 扩展路线与个人踩坑体会6.1 后续可以从三个方向做加法第一个系统跑通后我会建议按顺序考虑三个扩展方向加重排序、加引用溯源、加知识更新机制。重排序是性价比最高的优化。先用轻量方法粗召回 20 到 30 个候选片段再交给更强的排序模型重新精排最后只用前 3 个片段生成。它能把“相关但排位靠后”的正确答案捞起来面对大规模资料库时效果尤其明显。引用溯源是让 RAG 变成可信系统的关键。每个答案后面附带引用来源用户能直接跳转到原文。实现并不复杂只要在入库时把来源文档名、页码、块序号作为 metadata 保存回答时拼到输出里就行。知识更新则是从“能用”走向“好用”的分水岭。你需要定义文档的版本、过期时间、替换规则。否则同一个问题在不同时间可能检索到互相矛盾的旧版内容第一个系统还发现不了等到真实用户来用问题就会爆发。6.2 我实际动手后的几点体感如果你只记住一件事我建议记住这个把索引、检索、生成三段链路分别记日志。第一次跑系统时问题几乎一定不会出在你预期的地方。有了日志你能清楚看到“片段是否召回正确”“模型是否用了正确上下文”五分钟定位问题没有日志你可能调一个晚上都不知道在调什么。分块参数不要迷信固定值。500 字是常见起点但你的资料是说明书、合同还是技术博客最优块大小差异很大。我自己最开始用统一参数处理混合文档效果惨不忍睹后来按文档类型分开切分明显改善。最后说一点心态层面的体会RAG 看着简单但每个环节都值得较真。你从“跑通 demo”到“回答稳定可用”之间隔着大量细节打磨。这不是坏事。正因为它可以把问题拆成独立环节你才能一步步定位、优化、验证最终搭出一套真正靠谱的知识问答系统。希望这篇经验能让你少踩几个我已经替你踩过的坑。

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

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

免费获取报价 →
↑