上一篇介绍了RAG的由来、角色与作用和工作流程:http://40452.oa22.cn
今天通过代码详细讲一讲RAG的工作流程
学习目标:
掌握分块策略
理解向量与Embedding的作用与实现原理
Naive RAG遵循一个传统的处理流程,包括索引Indexing、检索Retrieval和生成Generation三个阶段 。
索引阶段(Indexing)都做什么工作?
流程是:原始文档 → 文档解析 → 文档分块 → 向量化 → 存入向量库。
1. 文档分块
1.1 按照句子来切分
以完整句子为最小分割单元,按「句号 / 问号 / 感叹号 / 换行」等句子结束符拆分文本,保证每个分块都是完整的语义句子。
import retext = "检索增强生成(Retrieval-Augmented Generation,RAG)是一种结合检索和生成技术的模型。它通过引用外部知识库的信息来生成答案或内容,具有较强的可解释性和定制能力,适用于问答系统、文档生成、智能助手等多个自然语言处理任务中。RAG模型的优势在于通用性强、可实现即时的知识更新,以及通过端到端评估方法提供更高效和精准的信息服务.RAG 包含三个主要过程:检索、增强和生成.通过这一过程,RAG模型能够在各种自然语言处理任务中发挥作用,如问答系统、文档生成和自动摘要、智能助手和虚拟代理、信息检索以及知识图谱填充等。同时,RAG模型具有及时更新、解释性强、高度定制能力、安全隐私管理以及减少训练成本等优点。与微调相比,RAG是通用的,适用于多种任务,并且能够实现即时的知识更新而无需重新训练模型。整个 RAG 系统由两个核心模块组成:检索器和生成器。检索器从数据存储中搜索相关信息,生成器生成所需内容."sentences = re.split(r'(。|?|!|....)', text)chunks = []for sentence, punctuation in zip(sentences[::2], sentences[1::2]): chunks.append(sentence + (punctuation if punctuation else ''))print(chunks)for i, chunk in enumerate(chunks): print(f"块 {i + 1}: {len(chunk)}: {chunk}")
1.2 按照字符数来切分
完全按固定字符数量硬切分文本,不考虑语义边界(比如切到一半的句子、单词),是最简单粗暴的切分方式。
text = "检索增强生成(Retrieval-Augmented Generation,RAG)是一种结合检索和生成技术的模型。它通过引用外部知识库的信息来生成答案或内容,具有较强的可解释性和定制能力,适用于问答系统、文档生成、智能助手等多个自然语言处理任务中。RAG模型的优势在于通用性强、可实现即时的知识更新,以及通过端到端评估方法提供更高效和精准的信息服务.RAG 包含三个主要过程:检索、增强和生成.通过这一过程,RAG模型能够在各种自然语言处理任务中发挥作用,如问答系统、文档生成和自动摘要、智能助手和虚拟代理、信息检索以及知识图谱填充等。同时,RAG模型具有及时更新、解释性强、高度定制能力、安全隐私管理以及减少训练成本等优点。与微调相比,RAG是通用的,适用于多种任务,并且能够实现即时的知识更新而无需重新训练模型。整个 RAG 系统由两个核心模块组成:检索器和生成器。检索器从数据存储中搜索相关信息,生成器生成所需内容."def split_by_fixed_char_count(text, count): chunks = [] for i in range(0, len(text), count): chunks.append(text[i:i + count]) return chunkschunks = split_by_fixed_char_count(text, 100)for i, chunk in enumerate(chunks): print(f"块 {i + 1}: {len(chunk)}: {chunk}")
1.3 按固定字符数 结合overlapping window
在「固定字符数切分」的基础上,增加重叠窗口(Overlap) —— 后一个分块会复用前一个分块末尾的 N 个字符,既保证分块大小可控,又减少语义断裂。
-关键参数
- chunk_size:每个分块的最大字符数(比如 500)
- chunk_overlap:相邻分块的重叠字符数(比如 50)
text = "检索增强生成(Retrieval-Augmented Generation,RAG)是一种结合检索和生成技术的模型。它通过引用外部知识库的信息来生成答案或内容,具有较强的可解释性和定制能力,适用于问答系统、文档生成、智能助手等多个自然语言处理任务中。RAG模型的优势在于通用性强、可实现即时的知识更新,以及通过端到端评估方法提供更高效和精准的信息服务.RAG 包含三个主要过程:检索、增强和生成.通过这一过程,RAG模型能够在各种自然语言处理任务中发挥作用,如问答系统、文档生成和自动摘要、智能助手和虚拟代理、信息检索以及知识图谱填充等。同时,RAG模型具有及时更新、解释性强、高度定制能力、安全隐私管理以及减少训练成本等优点。与微调相比,RAG是通用的,适用于多种任务,并且能够实现即时的知识更新而无需重新训练模型。整个 RAG 系统由两个核心模块组成:检索器和生成器。检索器从数据存储中搜索相关信息,生成器生成所需内容."def sliding_window_chunks(text, chunk_size, stride): chunks = [] for i in range(0, len(text), stride): chunks.append(text[i:i + chunk_size]) return chunkschunks = sliding_window_chunks(text, 100, 50) for i, chunk in enumerate(chunks): print(f"块 {i + 1}: {len(chunk)}: {chunk}")
1.4 递归方法(RecursiveCharacterTextSplitter)
这是「智能切分」的核心策略,也是 LangChain 等框架的默认推荐策略,逻辑是:
先按**大粒度分隔符**(段落符、换行符)切分;如果切出的分块超过`chunk_size`,再按**中粒度分隔符**(句子符:。!?)切分;如果还超,最后按**小粒度分隔符**(逗号、空格)切分;若仍超,才按字符硬切分;全程可搭配`overlap`保证语义连续。
from langchain.text_splitter import RecursiveCharacterTextSplittertext = """检索增强生成(Retrieval-Augmented Generation,RAG)是一种结合检索和生成技术的模型。它通过引用外部知识库的信息来生成答案或内容,具有较强的可解释性和定制能力,适用于问答系统、文档生成、智能助手等多个自然语言处理任务中。RAG模型的优势在于通用性强、可实现即时的知识更新,以及通过端到端评估方法提供更高效和精准的信息服务.RAG 包含三个主要过程:检索、增强和生成.通过这一过程,RAG模型能够在各种自然语言处理任务中发挥作用,如问答系统、文档生成和自动摘要、智能助手和虚拟代理、信息检索以及知识图谱填充等。同时,RAG模型具有及时更新、解释性强、高度定制能力、安全隐私管理以及减少训练成本等优点。与微调相比,RAG是通用的,适用于多种任务,并且能够实现即时的知识更新而无需重新训练模型。整个 RAG 系统由两个核心模块组成:检索器和生成器。检索器从数据存储中搜索相关信息,生成器生成所需内容."""splitter = RecursiveCharacterTextSplitter( chunk_size=50, chunk_overlap=10, length_function=len,)chunks = splitter.split_text(text)for i, chunk in enumerate(chunks): print(f"块 {i + 1}: {len(chunk)}: {chunk}")
模块下载 pip install langchain
RecursiveCharacterTextSplitter 是一个用于将文本分割成较小块的工具。
它特别适用于需要递归地按字符拆分文本的场景,例如处理超长文档或嵌套结构的文本
chunk_size = 分割长度
chunk_overlap = 重叠长度
length_function = 固定写法(用于定义如何计算每个文本片段的长度)
2.向量与Embedding
2.1 向量
在数学中,向量(也称为欧几里得向量、几何向量),指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。
将文本转成一组浮点数:每个下标 i,对应一个维度
整个数组对应一个 n 维空间的一个点,即文本向量又叫 Embeddings
向量之间可以计算距离,距离远近对应语义相似度大小
在计算机中我们用数组来表述向量
import numpy as npv = np.array([3, 4]) magnitude = np.linalg.norm(v) unit_vector = v / magnitude angle = np.arctan2(v[1], v[0]) * 180 / np.pi print(f"大小: {magnitude}")print(f"单位向量 (方向): {unit_vector}")print(f"与x轴角度: {angle:.1f}°")
2.2 Embedding
Embedding(嵌入) 本质是:把文本、图片、音频等「非结构化信息」,转换成计算机能理解的、低维度的数字向量(一串数字) ,且这个向量能反映原始信息的语义 / 特征。
具体作用拆解
把文本块变成可计算的向量:计算机无法直接比较「文本块 A」和「问题 B」的语义相似度,但能计算两个向量的余弦相似度;
实现语义检索:不是关键词匹配(比如搜「猫咪」找不到「小猫」),而是语义匹配(「猫咪」和「小猫」的向量接近,能精准召回);
降维优化效率:原始文本是高维离散数据,Embedding 转换成低维连续向量,大幅降低向量库存储和检索的成本。

它们是「分工协作」的关系:Embedding 负责「转译」,向量负责「承载和计算」,共同解决「机器看不懂自然语言,但能算向量」的核心问题。
2.3 向量间的相似度计算
常用的相似度计算方法包括:
余弦相似度Cosine:计算两个向量之间的夹角余弦值,只关注「方向是否一致」,不关注向量长度(完美适配 Embedding 语义匹配)。
计算公式: 两个向量的乘积 / 两个向量范数的乘积 A×B / ∥A∥ × ∥B∥
范数: l1(绝对值), l2(勾股定理), l无穷
欧式距离L2:计算两个向量在多维空间中的直线距离,距离越小,相似度越高。
点积:直接计算两个向量的点积(对应维度相乘后求和),仅当向量已归一化(模长 = 1)时,等价于余弦相似度。。
vec_question = [0.12, 0.34, -0.21, 0.56, 0.09]vec_chunk_similar = [0.11, 0.33, -0.20, 0.55, 0.08]vec_chunk_dissimilar = [0.89, -0.45, 0.67, -0.32, 0.78]import numpy as npfrom sklearn.metrics.pairwise import cosine_similaritydef calculate_similarity(): cos_sim_similar = cosine_similarity([vec_question], [vec_chunk_similar])[0][0] cos_sim_dissimilar = cosine_similarity([vec_question], [vec_chunk_dissimilar])[0][0] euclid_similar = np.linalg.norm(np.array(vec_question) - np.array(vec_chunk_similar)) euclid_dissimilar = np.linalg.norm(np.array(vec_question) - np.array(vec_chunk_dissimilar)) vec_q_norm = vec_question / np.linalg.norm(vec_question) vec_c_norm = vec_chunk_similar / np.linalg.norm(vec_chunk_similar) dot_sim = np.dot(vec_q_norm, vec_c_norm) print("===== 余弦相似度(越接近1越相似) =====") print(f"问题 vs 相似文档块:{cos_sim_similar:.4f}") print(f"问题 vs 不相似文档块:{cos_sim_dissimilar:.4f}") print("\n===== 欧式距离(值越小越相似) =====") print(f"问题 vs 相似文档块:{euclid_similar:.4f}") print(f"问题 vs 不相似文档块:{euclid_dissimilar:.4f}") print("\n===== 点积相似度(归一化后等价于余弦) =====") print(f"问题 vs 相似文档块:{dot_sim:.4f}")calculate_similarity()
总结:
2.4 二次理解向量与Embedding
它们是「分工协作」的关系:Embedding 负责「转译」,向量负责「承载和计算」,共同解决「机器看不懂自然语言,但能算向量」的核心问题。
步骤 1:Embedding(转译过程)—— 把「人类语言」翻译成「向量语言」
LLM/RAG 中,Embedding 的核心作用是「语义转译」:
输入:人类能懂的自然语言(比如文档块「猫咪每天喂 2 次猫粮」、用户问题「小猫该怎么喂食」);
处理:通过 Embedding 模型(如 text2vec、OpenAI Embedding),基于「语义」把文本转换成向量;
输出:带语义属性的 Embedding 向量(比如「猫咪喂养」→ [0.12, 0.34, -0.21],「小猫喂食」→ [0.11, 0.33, -0.20])。
👉 核心价值:Embedding 保证了「语义相似的文本,转换成的向量也相似」,这是后续检索的基础。
步骤 2:向量(承载 + 计算)—— 让「语义匹配」变成「数学计算」
向量的核心作用是「承载 Embedding 的结果,并支持相似度计算」:
承载:把 Embedding 生成的语义向量存入向量数据库(而非传统数据库);
计算:通过向量的数学运算(余弦相似度、欧式距离),衡量两个 Embedding 向量的「语义相似度」;
👉 核心价值:向量的可计算性,让机器能快速找到「和用户问题语义最相关的文档块」。
3、向量数据库
3.1 向量数据库
思考: 我们这个向量的数据存在哪里, 存mysql MongoDB?我通过一张表格来对比一下传统数据库与向量数据库的区别:
| 维度 | 传统数据库(MySQL/PostgreSQL/MongoDB) | 向量数据库(FAISS/Milvus/Chroma/Pinecone) |
|---|
| 存储对象 | 结构化数据(数字 / 字符串 / 日期)、半结构化 JSON | 高维向量(Embedding 输出的一串数字) |
| 核心能力 | 精准匹配(=、>、<、like)、事务、索引(B 树) | 相似度检索(余弦 / 欧式距离)、近似最近邻(ANN) |
| 检索逻辑 | 基于「关键词 / 规则」的精确匹配 | 基于「语义」的模糊匹配 |
| 适用场景 | 电商订单、用户信息、日志统计 | LLM 语义检索、图片 / 音频相似匹配、RAG |
| 性能特点 | 亿级数据精准查询快,但相似度检索极慢 | 亿级高维向量相似度检索毫秒级响应 |
| 核心痛点 | 无法计算「语义相似」,仅能关键词匹配 | 不支持事务,精准匹配能力弱 |
通俗举例
3.2 常见的向量数据库:
Pinecone
重复检测:帮助用户识别和删除重复的数据
排名跟踪:跟踪数据在搜索结果中的排名,有助于优化和调整搜索策略
数据搜索:快速搜索数据库中的数据,支持复杂的搜索条件
分类:对数据进行分类,便于管理和检索
去重:自动识别和删除重复数据,保持数据集的纯净和一致性
Milvus
毫秒级搜索万亿级向量数据集
简单管理非结构化数据
可靠的向量数据库,始终可用
高度可扩展和适应性强
混合搜索
统一的Lambda结构
受到社区支持,得到行业认可
Chroma
Faiss
3.3 如何选型向量数据库
在选择适合项目的向量数据库时,需要根据项目的具体需求、团队的技术背景和资源情况来综合评估。以下是一些建议和注意事项:
向量嵌入的生成
延迟要求
开发人员的经验