资讯详情

RAG检索增强生成实战:从切分、向量化到最小系统搭建

📅 2026/10/21 14:11:59 | 华诺云谱 👁 阅读
RAG检索增强生成实战:从切分、向量化到最小系统搭建
1. 先把“查资料再回答”这五个字说透最近不止一个做后端的朋友问我大模型生成的回答总是“飘”要么答案是编的要么是过时的旧闻。让他们去读几十万字的企业文档模型又读不进去。这时候我通常都会建议一条路——别急着重新训练模型先把 RAG 这套思路搭起来。RAG 全称是 Retrieval-Augmented Generation中文叫检索增强生成。翻译成大白话就是大模型在开口回答之前先去一个外部资料库里翻资料把相关资料找出来贴在答案里一起看然后照着这些材料组织回答。你把它理解成“开卷考试”就行一般的模型是闭卷全靠脑子里的记忆硬答RAG 是开卷手里有一摞参考资料不知道的就去翻。这套思路看起来简单但背后解决的问题相当实际。大模型的知识来自训练语料训练完就固定了之后你问它它只能凭记忆猜。这带来两个头疼的问题一是幻觉它不知道答案时也会一本正经地编一个显得自信满满二是信息滞后半年前的事件它根本不知道。RAG 的价值在于把知识的存储从模型参数里挪出来挪到外部数据库里。数据随时能更新模型也不用重新训练生产环境改动成本很低。适合谁学我的答案是只要手里有 Python 基础、跑过几个大模型 API 调用的程序员都可以上手。这个技术栈不需要你懂复杂的机器学习理论核心就是“切文档、算向量、做检索、拼提示词”四件事。你不需要自己训练模型只需要调用现成的接口把链路串起来。我见过不少刚接触 NLP 的初学者一个周末就能把最小版本跑通。不过先泼一盆冷水RAG 不是万能钥匙。它解决的是“信息缺失”和“答案不准”的问题解决不了“逻辑太差”“推理太浅”这类模型本身能力的问题。它适合做知识库问答、企业文档检索、私有数据问答但如果你想让模型输出更复杂的分析报告光靠 RAG 还不够。把这层边界弄清楚后面踩的坑会少一大半。2. 四段式流水线拆解切分、向量化、检索、合成RAG 的完整流程听上去高大上其实拆开看就是一条流水线。我习惯把它分成四段切分、向量化、检索、合成。下面逐一讲清楚不画什么花里胡哨的架构图就把每段到底在干什么、为什么要这么干说明白。2.1 切分先把厚资料变成能翻的“书签”模型能一次性阅读理解的内容有限所以第一步是把原始文档切成若干个小块。这一步有点像把一本很厚的书拆成一页一页的笔记每一页都能单独查找。切分看似简单但粒度很讲究。切得太粗比如直接扔进去几万字检索结果里混着一大堆不相关内容模型也抓不住重点切得太细比如一句话切一段上下文又丢了语义支离破碎。实践中我一般从 500 到 800 字的切分粒度开始试并且让相邻块之间有一部分重叠一般是 50 到 100 字。重叠的目的是保留上下文衔接比如某个技术概念可能在前一段开头定义、在后一段详细展开如果硬切概念就被割裂了。你可以在代码里看到这个操作几乎不需要复杂逻辑一个按字符数切分的函数就行。def split_text(text: str, chunk_size: int 600, overlap: int 80) - list[str]: chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) if end len(text): break start end - overlap return chunks很多人一上来就想用所谓“语义切分”或者机器学习模型来切其实新手阶段完全没有必要。先按固定字符切跑通链路看到结果以后再换更聪明的切法你才知道哪个环节真正影响效果。2.2 向量化把文字变成能用距离比较的数字切完的每一段文字还不能直接拿去搜索。计算机不会理解字符串含义它理解的是数字。向量化就是把一段文字压缩成一个固定维度的向量比如 384 维或者 1536 维。这个向量捕捉的是文本的语义信息意思是越相近的文本向量在空间里离得越近。你可以这样理解把“今天天气不错”和“今天阳光很好”这两句话分别变成向量它们之间的距离会比较近而“今晚吃什么”跟这两句的距离会远很多。所以向量化本质上是在给文本建立一套“语义坐标”。具体操作上你需要一个嵌入模型接口。现在的做法通常是调用大模型服务商提供的 Embedding 接口或者使用开源嵌入模型在本地跑。不管选哪种流程都是一样的把所有切好的段落逐条变成向量然后和文本一起存进向量数据库里。def build_index(texts: list[str]): ids [fchunk-{i} for i in range(len(texts))] vectors [] for text in texts: vec embed(text) # 调用你选用的嵌入服务 vectors.append(vec) vec_db.upsert(idsids, vectorsvectors, textstexts)我之前在某个知识库问答 Demo 里就是这么实现的全流程写下来不超过 50 行代码。只要嵌入接口稳定这一步基本不会出幺蛾子。2.3 检索从资料海里快速捞出最相关的段落用户提一个问题系统第一步要做的是把问题也变成向量然后去向量数据库里找距离最近的几个段落。这里“距离”变相衡量的是相关度。向量数据库内部会做近似最近邻搜索即使库里有几十万条记录也能在几十毫秒内返回结果。这一步需要注意一个问题检索是召回的思路不是精排的思聪。也就是说我们要尽量把“可能相关”的段落多捞回来一点而不是只捞那一条最像的。捞得越多模型看得越全但也不能无限多毕竟上下文窗口有限太多无关内容反而干扰模型判断。一般我建议先取出 top_k 4 到 top_k 6 个段落看到评测结果再微调。很多小白在这里会误解一点认为向量检索是“模糊匹配关键词”。其实不完全是。向量检索擅长捕捉同义表达和语义相近但它在处理精确术语、代码编号、型号名称这类内容时反而不如传统的关键词检索靠得住。比如你搜“TS-100 型号的故障码”向量库可能更关心“故障”这个词义而漏掉精确匹配。这一点放到后面的升级章节再展开。2.4 合成把参考材料和问题一起塞给模型拿到检索出来的段落以后剩下的事就交给大模型来做了。我们需要构造一个固定的提示词模板把用户问题、检索到的若干段落组装进去然后请求大模型接口返回答案。合成这一步看着简单但提示词写得好不好对最终回答质量的影响往往比检索还大。我的常用模板差不多是下面这个样子你是一名严谨的助手。请根据给出的参考资料回答问题。 如果参考资料中找不到答案请直接说“资料中没有相关信息”不要编造。 参考资料 - 资料1{chunk_text_1} - 资料2{chunk_text_2} 用户问题{question}注意最后那句“不要编造”很重要。大模型本质上是一个续写引擎如果没有任何约束它会在资料不足时自动用训练记忆脑补。加上这句话以后至少能显著减少错误输出。如果你的业务要求答案严格来自资料还可以在解析阶段强制要求模型在回答末尾输出参考资料编号。到这里一条完整的 RAG 调用链就闭环了。接下来我带你从头到尾写一个最小可运行版本让你直观感受一下整套流程怎么转起来。3. 手把手搭建一个最小可运行版本前面讲了原理接下来是实操。我会用最容易复现的方式逐步搭出一个最小系统。这里不引入太重的东西重点是把链路跑通让你知道每一行代码在干什么。3.1 准备工作你需要的东西其实很少整套系统需要三样东西Python 3.10 或更高版本一个嵌入口能把你选用的模型接口封装成embed(text)函数一个向量数据库客户端可以本地跑也可以内存跑如果这些你都没有选一个你熟悉的轻量级开源向量库就够了。我自己最早学习时用小规模文本文件直接在内存里跑连数据库部署都省了。先把逻辑弄明白再迁移到正式的向量服务。3.2 准备语料并切分假设我们有一个简单的文本文件里面写了某个产品的手册说明。我一般先读取整个文件然后按前面写的split_text函数切分并把结果打印出来看一眼。from pathlib import Path raw_text Path(manual.txt).read_text(encodingutf-8) chunks split_text(raw_text, chunk_size600, overlap80) print(f切分出 {len(chunks)} 个片段) print(chunks[0])这一步是纯文本处理没有任何模型参与。所以跑起来极快也方便你随时调整参数。我建议每换一种切分粒度都打印前几个片段出来读一读确认没有把重要的表格或标题切开。3.3 生成向量并写入索引接着调用嵌入接口给每个片段生成向量。新手常犯的一个错误是把整个文档一次喂进去然后请求返回一个向量。这样会把几百个不同主题的内容揉成一个向量检索的时候就完全分不清了。一定要按“片段”来向量化一个片段对应一个向量。vectors [embed(chunk) for chunk in chunks] doc_ids [fdoc-{i} for i in range(len(chunks))] vec_db.upsert(idsdoc_ids, vectorsvectors, textschunks)向量化可能需要一点时间取决于你选择的嵌入服务和文本总量。为了调试方便我习惯在这里加一个简单日志每隔 50 条打印一次进度。如果遇到某一段文本因为超长而报错说明切分粒度还是太粗需要调小 chunk_size。3.4 实现查询链路查询时用户输入一个问题我们把问题向量化然后查库取回相似度最高的 top_k 个片段。question 产品无法开机怎么办 q_vec embed(question) results vec_db.query(q_vec, top_k5) for score, text in results: print(score, text[:80])查出来的结果可以顺手打印一下这一步也是调试的重点如果打印出来的片段跟问题明显不相关说明切分、向量化或者嵌入模型选型有问题。如果相关但排序不理想再调 top_k 或者后续加重排。3.5 组装提示词并请求大模型最后的合成环节用前面提到过的提示词模板把问题与片段拼起来然后丢给大模型接口。def ask_with_context(question, results): context \n.join([f- {text} for _, text in results]) prompt ( 你是一名严谨的助手。请根据给出的参考资料回答问题。 如果参考资料中找不到答案请直接说“资料中没有相关信息”不要编造。\n\n f参考资料\n{context}\n\n f用户问题{question} ) return call_llm(prompt) # 调用你接入的大模型接口到这里你的 RAG 最小闭环已经完成。你可以试着问几个不同角度的问题看模型是照着资料答还是偷懒编答案。如果发现后者多数情况下是提示词约束不够硬或者检索回来的片段根本没有正确答案。4. 新手期最容易翻车的五个细节写完最小版本只是第一步真正让它变好用得跨过几个坑。下面这几个问题是我在带新人时几乎每次都会碰到的写出来给你提前排雷。4.1 切分粒度不对检索效果直接崩很多人图省事把整篇文档作为一个索引单元丢进去。结果就是检索时几乎每个候选片段里都包含大量无关信息模型看完以后抓不住重点。比如一篇产品手册里有安装、配置、故障排查三大部分如果你整个塞进去问“怎么配置网络”返回的片段却把安装和故障也带上了模型自然答得模模糊糊。反过来切得过细也有问题。每个片段只剩一两句话表面上很精确但模型缺少上下文不知道这句话是在什么场景下说的。比如“请重启服务”这句如果前面没有“修改配置之后”的上下文模型可能直接建议用户重启逻辑完全错了。我的经验是先从 500 到 800 字切起段间重叠 10% 到 20%然后拿几个典型问题做测试。回答问题看起来“缺上下文”就增大 chunk_size看起来“太杂”就减小 chunk_size。这是一件需要手动多试几次的事。4.2 嵌入模型的长度上限超了就报错或被截断所有嵌入模型都有单次输入的最长长度限制通常是 512 个 token 或 1024 个 token。中文的 token 换算大约是一个字约等于一个到两个 token。如果你切分出 2000 字的片段死直接拿去调用嵌入接口要么超时报错要么后端静默截断。静默截断比报错更坑。因为程序不报错但向量内容只剩前面一半后面的关键信息全丢了检索时怎么都不准。所以代码里一定要做一层保护调用嵌入接口前检查字符长度超了就强制切成更短的块或者抛异常提醒你调小 chunk_size。我一般会在切分函数里加一个断言超过预设阈值直接报错这样能第一时间发现问题。assert len(chunk) 1000, f片段过长: {len(chunk)}4.3 top_k 不是越大越好很多初学者觉得返回的候选片段越多模型看到的资料越全答案应该越准。实际恰恰相反。片段一多模型要在这些内容里找重点反而容易受无关信息干扰。我见过有人把 top_k 设成 20回答质量肉眼可见地变差。比较稳妥的起点是 top_k 4。如果检索回来发现正确片段排在第三第四位再把 top_k 调到 6 或者 8。如果你的场景是综合分析题需要看多个角度再考虑提高到 10 以上。这个参数没有绝对最优只能根据评测结果调。另外很多向量数据库还支持相似度阈值过滤低于某个分数的片段自动丢弃。新手别一上来就加这个过滤因为不同嵌入模型的分数取值范围差异很大0.7 在某模型下可能是优秀换个模型可能只是普通。先看检索结果的实际分数分布再决定阈值。4.4 中文语料里那些藏着的小麻烦中文处理有一些英文环境不存在的问题。最典型的是编码问题文档读取时忘记指定 UTF-8或者拿到的数据是 GBK 编码打印出来全是乱码向量化出来的东西自然也是错的。我见过不止一个人在这个问题上花了半天时间。还有标点符号和断句的问题。中文文档经常用全角标点切分时如果按“。”和“”提前断句可以保留更完整的语义。英文按空格切句的办法在中文上不好使。纯按字符数硬切就会出现很多半句话检索时模型理解起来很费劲。如果你手头的资料是 PDF注意直接从 PDF 里抽取出来的文本经常有换行乱序问题表格内容更是重灾区。我的建议是先做一道清洗步骤把多余换行替换成空格把乱码字符过滤掉再进切分环节。这步不做后面一切都是“脏数据进来脏结果出去”。4.5 提示词里只写“根据资料回答”远远不够我见过很多教程提示词里写着“根据资料回答”然后就没有然后了。模型确实会参考资料但它也会自由发挥。你有多少次看到模型回答得有板有眼结果最后一句完全是从训练记忆里捡来的至少要加三样约束第一明确告诉模型资料中找不到就直说找不到第二禁止把资料没有的信息补充进来第三要求回答中尽量引用资料原文作为支撑。对于初学者而言前两条几乎是必须的。如果你做的是客服问答还要再加上“不要说多余的话直接给结论”。5. 从“能跑”到“好用”四个升级方向跑通最小版本后你已经超越“入门”了。但离真正生产可用还有距离。以下四个方向我按成本从低到高排列你不一定全做按业务需要挑着来。5.1 给片段打上元数据先过滤再检索最小版本里所有片段混在一起用户问任何问题都在全库里找。但如果你的知识库分了很多主题比如“产品手册”“售后政策”“技术支持”全部混在一起检索会让效果变差。这时候应该给片段打上元数据标签先按条件过滤再在子集里检索。vec_db.upsert( ids[doc-1], vectors[vec], texts[chunk], metadatas[{category: 售后, date: 2025-01-01}] ) results vec_db.query(q_vec, top_k5, filter{category: 售后})这个改动小、收益大。尤其当知识库覆盖多个业务线时强制过滤能显著减少跑偏检索。你需要做的只是给每个片段在上传时打上正确的标签。5.2 关键词召回与向量召回混合使用前面提过向量检索擅长语义相似但精确匹配能力弱。如果你的资料里充满型号、编号、专业术语单靠向量检索很容易漏。比如用户搜“TK-200 驱动下载”向量库可能把“TK-200”这个关键词忽略掉只抓住“驱动下载”这个语义结果返回一堆其他型号的驱动说明。在这种情况下混合检索是个不错的方向同时用传统的关键词检索BM25 一类算法和向量检索把两边的结果合在一起再去重、再打分。实现起来不算太难你可以先分别查出两组结果然后按位次简单合并。合并的方式可以从最粗暴的“两边各取一半”开始后续再考虑加权。你不需要一开始就理解 BM25 的全部细节只需要知道它擅长精确匹配和向量检索形成互补。很多团队做 RAG 时这一步带来的效果提升甚至比换个大模型还明显。5.3 召回结果二次重排检索阶段返回的排序通常基于向量距离但这个排序不一定最符合最终需求。比如用户问“如何解决蓝屏”向量相似度最高的可能是“什么是蓝屏”这段科普而不是真正的解决方法。如果能用一个更精细的排序模型对召回结果重新打分把真正的答案顶上去效果会好很多。重排的标准做法是先靠轻量检索拉回 top_k 20 到 50 条候选再用交叉编码器模型对每个候选片段与问题做精细相关度打分取出最高的 5 条给模型。交叉编码器可以同时看问题和片段的全文字面匹配打分比向量距离更准确但速度较慢所以只对精简单结果集用。这一步需要额外引入一个模型稍微有点技术门槛但收益很直接。如果你已经跑通混合检索再加重排召回质量会上一个台阶。5.4 用一组固定问题做回归评测做 RAG 有个很常见的问题今天调了切分参数手测几个问题觉得不错明天改了提示词又觉得变差了但说不清到底哪次改得好。所以从最开始就要建立一份评测集合。我一般准备 20 到 30 个有标准答案的问题覆盖知识库中的不同主题用一个脚本批量运行整条链路输出每次回答的最终结果。test_cases [ {question: TK-200 怎么安装驱动, expected: 下载驱动后运行安装程序}, {question: 保修期多久, expected: 自购买日起一年}, ] for item in test_cases: result run_rag_pipeline(item[question]) print(result)评测不用做得多复杂先人工逐个看一遍给“好”“一般”“差”三档评分。把改动前后的评分放一起对比你就能客观知道哪个调整真正有效。等数据集稳定了再考虑用自动打分函数替代人工。这一步一开始可能让你觉得麻烦但它几乎决定了你的系统能不能长期稳定迭代。6. 我现在还在用的三个小习惯最后聊聊我自己在维护 RAG 系统时逐渐形成的习惯都不是什么高深技巧但确实帮我少走了很多弯路。第一每次改动只动一个变量。无论是调 chunk_size、top_k还是改提示词一次只改一项记录下改动前后的评测结果。如果你同时改了三个地方效果变好了也搞不清是哪个变量起的作用。第二把中间结果落盘保存。我习惯把切分片段、向量化结果、每次检索的前几名都打印到本地日志里甚至存成文件。一旦线上回答出了问题可以顺着日志找到是检索这一步就错了还是模型合成时编造了。没有这些中间日志排查问题只能靠猜。第三定期检查“不知道”类回答的比例。如果用户抽样测试中发现大量回答都写着“资料中没有相关信息”未必是坏事反而说明模型不幻觉了但没有正确资料可答。这个信号比“答案看起来很顺滑”可靠得多因为后者很可能是在一本正经地编。我也会定期往知识库里补新资料让“不知道”比例降到一个合理区间。我接触 RAG 这几年最大的感受是它的核心壁垒其实不在算法而在于你对数据是否足够敏感。批量切分之前先读一读原始文档调整参数之后立刻看检索结果上线之前准备一份扎扎实实的评测题集——这些实实在在的细节比任何花哨的技巧都更能决定最终效果。如果这篇文章看完你决定动手搭一个最小版本那我的建议很简单今天就找一个几百行的文本跑通那四段链路。从“能跑通”到“能用好”中间的路你只有亲自走一遍才体会得到。
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑