零基础理解 RAG:从文档分块、向量化到相似度检索,带你搞懂检索增强生成的底层核心逻辑

发布时间:2026/6/1 19:38:46

零基础理解 RAG:从文档分块、向量化到相似度检索,带你搞懂检索增强生成的底层核心逻辑 零基础理解 RAG从文档分块、向量化到相似度检索带你搞懂检索增强生成的底层核心逻辑前言今天下午天空放晴。一缕暖洋洋的斜阳洒在我的原木书桌上。我的小狗 Token 正趴在脚边打着小呼噜。在这个惬意的时刻我想和大家分享大模型领域中听起来很神秘但原理其实特别温柔的技术——RAG检索增强生成。简单来说大模型就像是一个参加“闭卷考试”的学生。他虽然读过很多书、非常聪明但有些细节时间久了就会记错甚至开始胡言乱语幻觉。而 RAG 技术就是允许这个学生在考试时“翻书查资料”。今天我带大家彻底抛开复杂的学术公式用最直白的大白话和最简单的 Python 源码带你彻底搞懂 RAG 系统的三大核心支柱。一、底层原理1.1 RAG 的三大步骤RAG 全称是 Retrieval-Augmented Generation检索增强生成。它的运作可以比作我们在图书馆找书并写报告的过程。graph TD A[本地参考文档 (如 戚风蛋糕菜谱)] --|第一步: 课本切片 (Chunking)| B[独立知识卡片列表] B --|第二步: 贴上位置坐标 (Embedding)| C[向量特征库] D[用户提问: 为什么蛋糕会塌陷] --|转化为查询向量| E[查询坐标] C E --|第三步: 翻书对比 (Similarity Search)| F[找出最相近的前 2 张卡片] F --|作为背景上下文| G[拼装完整的 Prompt 喂给大模型] G -- H[大模型输出温馨且准确的回答]三大支柱环节文档分块 (Document Chunking)一本十万字的书大模型是一下子看不过来的。我们需要把它切成一两百字的小段落我们叫知识卡片。向量化 (Embedding)把文字转换为代表语义坐标的数字向量。在数字空间里“戚风蛋糕”和“烘焙面点”会被排在很近的坐标上。相似度检索 (Similarity Search)当用户提问时我们把问题也变成坐标在库里找出离问题坐标最近的几个小段落把它们贴在问题后面送给大模型。1.2 效果对比体验维度纯大模型直接回答 (无 RAG)开启 RAG 翻书辅助回答回答准确度中等 (容易胡编乱造不存在的知识)极高(句句有依据全部来自本地参考书)实时时效性差 (无法知晓今天发生的最新消息)极强(只要把今天的新闻放入知识库即可)隐私保护度差 (可能需要把机密数据发送到云端训练)极好(私密文档保留在本地大模型仅做整理)开发成本极高 (需要重新微调训练大模型烧钱)极低(普通电脑上运行几十行代码即可搭建)二、快速上手手写一个最简文档切片器切片时如果直接粗暴切断很容易把一句话从中间切开。我们需要设置“重叠度Overlap”让相邻的卡片之间有一点重复的字保证意思连贯。def 极简文档切片器(原始文本: str, 块大小: int 50, 重叠大小: int 15) - list[str]: 将一整段长文本按照固定大小和重叠字符进行物理分块 知识卡片列表 [] 起止索引 0 文本长度 len(原始文本) while 起止索引 文本长度: # 计算当前卡片的结束位置 结束索引 min(起止索引 块大小, 文本长度) # 截取文本块 文本块 原始文本[起止索引:结束索引] 知识卡片列表.append(文本块.strip()) # 每次向前移动块大小减去重叠大小保留部分重复内容 起止索引 (块大小 - 重叠大小) # 避免陷入死循环 if 结束索引 文本长度: break return 知识卡片列表三、核心 API 与深水区3.1 纯手写特征相似度算法在把卡片做成向量后我们如何用 Python 计算它们和用户提问的吻合度呢下面我们用数学里的夹角余弦算法实现一个检索核心。import math def 模拟提取文本特征(文本: str) - list[float]: 模拟文本特征提取。实际开发中我们直接调用 Embedding 接口。 这里通过统计几个核心敏感词出现的频次生成一个 3 维特征向量。 关键词 [蛋糕, 温度, 塌陷] 特征向量 [0.1, 0.1, 0.1] # 给予微小的底噪值 # 统计词频映射为高维坐标 for 索引, 词 in enumerate(关键词): if 词 in 文本: 特征向量[索引] 1.0 return 特征向量 def 计算余弦夹角(向量甲: list[float], 向量乙: list[float]) - float: 计算两个特征向量的余弦夹角值。越接近 1 代表语义越贴近。 点积 sum(x * y for x, y in zip(向量甲, 向量乙)) 模长甲 math.sqrt(sum(x * x for x in 向量甲)) 模长乙 math.sqrt(sum(y * y for y in 向量乙)) if 模长甲 0 or 模长乙 0: return 0.0 return 点积 / (模长甲 * 模长乙)四、实战演练现在我们把切片、特征提取和匹配组合起来。模拟一本关于“戚风蛋糕烘焙技巧”的知识库输入问题“为什么我的蛋糕塌陷了”def 运行RAG测试(): # 模拟一份关于烘焙的本地参考文档 菜谱课本 ( 戚风蛋糕需要上下火 150 度烘焙 50 分钟。如果在烘焙中途频繁打开烤箱门 会导致烤箱内部温度骤降热胀冷缩会引起蛋糕顶部严重塌陷。同时如果 蛋白霜打发不足内部气孔结构太弱出炉后没有及时倒扣也会发生塌陷。 另外烤箱的摆放位置要平稳避免震动。 ) print( 步骤 1正在给课本进行切片分块...) 卡片库 极简文档切片器(菜谱课本, 块大小60, 重叠大小20) for 索引, 卡片 in enumerate(卡片库): print(f [知识卡片 #{索引1}]: {卡片}) # 2. 将所有卡片转化为模拟特征向量 向量库 [模拟提取文本特征(卡片) for 卡片 in 卡片库] print(\n 步骤 2收到用户发问...) 用户提问 为什么我的戚风蛋糕会塌陷缩腰 提问特征 模拟提取文本特征(用户提问) print(f 提问: {用户提问} | 转化为特征: {提问特征}) # 3. 相似度检索 相似度结果 [] for 索引, 向量 in enumerate(向量库): 得分 计算余弦夹角(提问特征, 向量) 相似度结果.append((得分, 卡片库[索引])) # 按得分从大到小排序 相似度结果.sort(keylambda x: x[0], reverseTrue) print(\n 步骤 3翻书查阅完毕匹配出的最相关卡片:) print( * 60) print(f [匹配分: {相似度结果[0][0]:.4f}] 内容: {相似度结果[0][1]}) print( * 60) print(我们会把这段内容作为背景贴在问题后面送给大模型整理。) if __name__ __main__: 运行RAG测试()运行输出 步骤 1正在给课本进行切片分块... [知识卡片 #1]: 戚风蛋糕需要上下火 150 度烘焙 50 分钟。如果在烘焙中途频繁打开烤箱门 [知识卡片 #2]: 频繁打开烤箱门会导致烤箱内部温度骤降热胀冷缩会引起蛋糕顶部严重塌陷。同时如果 [知识卡片 #3]: 热胀冷缩会引起蛋糕顶部严重塌陷。同时如果蛋白霜打发不足内部气孔结构太弱 [知识卡片 #4]: 蛋白霜打发不足内部气孔结构太弱出炉后没有及时倒扣也会发生塌陷。另外烤箱的摆放位置要平稳避免震动。 步骤 2收到用户发问... 提问: 为什么我的戚风蛋糕会塌陷缩腰 | 转化为特征: [1.1, 0.1, 1.1] 步骤 3翻书查阅完毕匹配出的最相关卡片: [匹配分: 0.9852] 内容: 热胀冷缩会引起蛋糕顶部严重塌陷。同时如果蛋白霜打发不足内部气孔结构太弱 我们会把这段内容作为背景贴在问题后面送给大模型整理。你看AI 在后台仅仅通过极简单的特征比对就帮我们把含有“塌陷”和“蛋糕”关联最深的那一张知识卡片卡片 #3精准挑了出来五、避坑指南在亲自动手写 RAG 的过程中有几个小边界一定要守好5.1 块大小 (Chunk Size) 设得过大或过小⚠️问题表现如果每个卡片切成 2000 字过大大模型阅读时容易漏掉中间的细节如果切成 5 个字过小大模型就会因为缺乏上下文比如只读到“导致塌陷”四个字根本不知道是在说蛋糕还是桥梁而无法理解。✅最佳实践在中文字符下每个卡片的长度一般推荐设在 150 到 400 字之间重叠度设在 10% 到 20% 之间体验最温柔稳定。5.2 盲目选择不支持中文的 Embedding 模型⚠️检索失效有些开发包默认使用英文的向量模型对中文的语义距离计算完全不准。输入“番茄”和“西红柿”它们算出的向量距离相隔十万八千里。六、总结把知识拆碎分类存档在需要的时候温柔唤醒。RAG 的精髓其实就是给聪明的大模型递上一本打开了的参考书。用最简单的逻辑把这些底层环节想明白你会发现大模型技术也并没有那么遥不可及。好啦窗外夕阳已经落下 Token 已经乖乖衔着它的饭盆看着我了。祝大家今天也收获了新的知识我们下期见

相关新闻