行业资讯

RAG系统核心原理:从文本切分到向量检索的完整技术栈解析

发布时间:2026/8/14 2:43:59
RAG系统核心原理:从文本切分到向量检索的完整技术栈解析 1. 项目概述从信息检索到智能问答的范式跃迁如果你最近在关注大模型应用尤其是如何让大模型“读懂”并回答你公司内部文档、知识库里的问题那么“RAG”这个词你一定不陌生。RAG全称检索增强生成它解决的核心痛点非常直接大模型虽然知识渊博但它不知道你私有的、非公开的、实时更新的数据。直接问它它要么胡说八道要么告诉你“我的知识截止到某年某月”。RAG的思路很巧妙它不试图把海量数据全部塞进模型里重新训练那成本太高了而是像给模型配了一个超级智能的“外接硬盘”和“搜索引擎”。当用户提问时系统先从这个“外接硬盘”你的知识库里快速找到最相关的几段信息然后把问题和这些信息一起“喂”给大模型让它基于这些“参考资料”来组织答案。这样一来答案的准确性、时效性和专业性都得到了质的提升。今天我们就来深入拆解这个“外接硬盘”和“搜索引擎”的核心工作原理。这不仅仅是调用几个API那么简单其背后是一套环环相扣的技术栈任何一个环节的疏漏都可能导致最终效果大打折扣。我们将聚焦五个最核心的概念Chunking文本切分、Embedding向量化、相似度计算、HNSW近似最近邻搜索算法以及多路召回策略。理解它们你就能真正看懂一个RAG系统是如何工作的也能在构建自己的系统时知道问题出在哪里以及如何优化。无论你是算法工程师、后端开发还是对AI应用感兴趣的产品经理这些概念都是绕不开的基石。2. 核心流程拆解RAG系统的“五脏六腑”一个典型的RAG系统工作流程可以清晰地分为两个阶段索引构建Indexing和查询与生成Query Generation。我们可以把它想象成图书馆的管理与借阅。索引构建阶段就是图书馆管理员系统对新到书籍你的文档进行加工入库的过程文本加载与清洗获取原始文档PDF、Word、网页等去除无关的格式、广告、页眉页脚。文本切分Chunking把整本书拆分成一个个有意义的“章节”或“段落”。这一步至关重要拆得太碎上下文丢失拆得太大检索精度下降。向量化Embedding为每一个“段落”计算一个高维向量一组数字这个向量就像是该段落内容的“数学指纹”语义相近的段落其向量在空间中的距离也相近。向量存储将所有“段落”的向量“指纹”及其对应的原始文本以一种支持快速查找的结构存入专门的数据库向量数据库。查询与生成阶段就是读者用户来图书馆查资料的过程问题向量化用户提出问题系统同样将问题转化为一个向量“指纹”。向量检索相似度计算 HNSW系统在向量数据库中快速找到与问题向量最相似的几个“段落”向量。这个过程依赖相似度度量如何定义“相似”和高效的搜索算法如HNSW。上下文组装将检索到的Top K个“段落”的原始文本拼接起来作为给大模型的“参考资料”或“上下文”。提示工程与生成将用户问题和组装好的上下文按照一定的模板Prompt组织成一段完整的指令发送给大模型如GPT、Claude等。答案返回大模型基于提供的上下文生成最终答案并返回给用户。可以看到Chunking、Embedding、相似度、HNSW是索引和检索阶段的核心技术而多路召回则是在检索环节提升效果的高级策略。下面我们就逐一深入。3. 基石一文本切分Chunking的艺术与科学Chunking是RAG流水线的第一步也是决定系统上限的基础。它的目标是将长文档分解为语义上相对完整、独立的小块以便后续嵌入和检索。如果块切得不好后续步骤再优秀也无济于事。3.1 常见切分策略及其适用场景没有一种切分策略是放之四海而皆准的需要根据文档类型和查询特点来选择。1. 固定长度重叠切分这是最简单、最常用的方法。设定一个固定的块大小如500个字符或token和一个重叠步长如100个字符。优点实现简单速度快能保证相对均匀的块大小。缺点可能粗暴地切断句子或段落破坏语义完整性。例如一个句子可能被拦腰截断前半句在一个块尾后半句在下一个块头。适用场景格式相对规整、语义单元长度波动不大的文档如技术手册、新闻稿。实操参数块大小通常选择256、512、1024等与Embedding模型的最大长度对齐。重叠长度一般为块大小的10%-20%用于缓解边界切断问题。2. 基于分隔符的切分利用文档中天然的分隔符进行切分如段落\n\n、标题#、句子.、!、?、Markdown结构##,-等。优点能更好地保持语义和格式上的完整性切分出的块更“自然”。缺点可能导致块大小差异极大一个标题可能只有几个词而一个长段落可能有上千字。适用场景结构清晰的文档如论文、带标题的Wiki页面、Markdown文档。实操技巧可以采用递归切分先用大分隔符如\n\n切如果块还是太大再用小分隔符如句子进行二次切分直到满足大小限制。3. 语义切分利用NLP技术如句子边界检测、文本分割模型识别文档中语义发生转换的边界。优点理论上能产生语义最完整的块质量最高。缺点计算成本高依赖额外的模型实现复杂。适用场景对检索质量要求极高且文档结构复杂、语义转换频繁的场景如小说、访谈记录、自由格式的长篇报告。4. 混合策略与智能切分在实际生产中单一策略往往不够。更常见的做法是分层切分或策略组合。例如先按章节/标题进行大块划分再在每个大块内根据内容类型是描述、列表还是代码选择不同的细粒度切分策略。注意Chunking的“黄金法则”切分的核心原则是让检索回来的“块”恰好包含回答用户问题所需的所有信息且尽可能少地包含无关信息。这要求我们根据预期的用户问题类型来设计切分策略。如果用户常问细节事实如“某产品的规格参数”块可以小一些、精确一些如果用户常问需要推理总结的问题如“对比A方案和B方案的优劣”块就需要大一些以保留足够的上下文。3.2 实操心得与避坑指南不要忽视元数据在切分时一定要保留块的来源信息如文件名、章节标题、页码等。当多个块被召回时这些元数据能帮助你理解块之间的关系甚至可以在组装Prompt时告诉模型“以下是来自《用户手册》第三章的内容”提升模型对上下文的理解。重叠不是万能的设置重叠可以缓解切断问题但也会引入冗余。如果重叠部分恰好包含关键信息它会在多个块中重复出现可能影响检索排名和模型判断。需要权衡重叠大小。动态块大小尝试对于质量要求高的项目可以进行A/B测试。准备几套不同策略如固定512字符、按段落切分、语义切分生成的索引用一批标准问题查询对比检索结果的相关性。这是找到最优策略的最可靠方法。处理特殊内容对于表格、代码块、数学公式要特别小心。尽量将它们保持在一个完整的块内避免切分。可以考虑用特殊的标记或格式来标识它们以便后续处理。4. 基石二嵌入Embedding—— 将文本映射为空间点如果说Chunking是把书拆成了段落那么Embedding就是给每个段落拍了一张“高维照片”。这张“照片”即向量不再是由文字组成而是由几百甚至上千个数字构成的坐标点。语义相似的段落它们的向量点在空间中的位置也彼此靠近。4.1 Embedding模型的选择与考量市面上有众多开源的、商用的Embedding模型如OpenAI的text-embedding-ada-002Cohere的Embed模型以及开源的BGE、E5、Sentence-Transformers系列等。选择时需权衡以下几点维度向量维数常见的有384、768、1024、1536等。更高的维度通常能承载更丰富的语义信息但也会增加存储和计算成本。不是维度越高越好要与模型能力匹配。上下文长度模型单次能处理的最大文本长度。如ada-002是8191个token。如果你的块超过这个长度需要截断可能损失信息。语义表示能力这是核心。好的Embedding模型应该在语义相似性任务上表现优异能将“狗”和“宠物”的向量拉近而将“狗”和“汽车”的向量推远。需要关注其在MTEB等权威榜单上的排名。语言与领域是否有针对特定语言如中文或特定领域如法律、医疗优化的模型通用模型在专业领域可能表现不佳。速度与成本本地部署的模型需要考虑推理速度调用API则需考虑费用和延迟。当前2024年的一个实用建议是对于中文场景可以优先考虑BGEBAAI General Embedding系列模型如BGE-large-zh。它在中文语义相似度任务上表现突出且完全开源可本地部署。对于多语言或中英混合场景text-embedding-ada-002或Cohere的模型仍然是稳定可靠的选择尤其是当你希望省去模型维护的麻烦时。4.2 Embedding的实践细节与调优输入规范化在将文本送入Embedding模型前进行适当的清洗和规范化非常重要。包括统一转换为Unicode去除多余空白符处理特殊字符等。对于某些模型在输入前添加指令前缀如BGE模型建议对于查询文本添加“为这个句子生成表示以用于检索相关文章”可以显著提升检索效果。批量处理当需要处理大量文本时务必使用模型的批量推理接口这比循环单条处理效率高出几个数量级。维度归一化许多相似度计算如余弦相似度在向量被归一化即转换为单位向量模长为1后更为高效和稳定。大部分向量数据库在存入时会自动做归一化但如果你自己计算相似度需要注意这一点。监控Embedding质量Embedding不是一劳永逸的。建议定期用一批标准查询语句检查其检索出的Top结果是否相关。如果发现效果下降可能是数据分布发生了变化需要考虑更新或重新训练Embedding模型。5. 核心引擎相似度计算与HNSW索引当我们有了海量的向量“指纹”后如何快速找到与问题向量最相似的那几个这需要解决两个问题如何定义“相似”相似度计算以及如何快速找到近似最近邻搜索。5.1 相似度度量如何定义“像”在向量空间中我们通过计算两个向量之间的距离或角度来衡量它们的相似度。常用的方法有余弦相似度Cosine Similarity计算两个向量夹角的余弦值。范围在[-1, 1]之间值越大越相似。这是文本Embedding最常用、效果通常最好的度量方式因为它只关注向量的方向而非长度对文本的“表达强度”不敏感。计算公式cos(θ) (A·B) / (||A|| * ||B||)点积Dot Product两个向量对应维度相乘后求和。当向量经过归一化后点积等价于余弦相似度。未归一化时向量的模长也会影响结果。欧氏距离Euclidean Distance计算两点间的直线距离。距离越小越相似。在某些特定的Embedding空间或模型中可能被使用。注意度量的选择与数据预处理强相关。如果你使用的向量数据库如Milvus, Pinecone, Weaviate默认使用余弦相似度那么你在生成Embedding时最好确保向量是归一化的或者数据库支持存入时自动归一化。混合使用可能导致不准确的结果。5.2 HNSW在十亿级向量中实现毫秒级检索暴力计算问题向量与库中所有向量的相似度在向量数量巨大时百万、千万级是完全不可行的。这就是近似最近邻搜索ANN算法登场的原因。HNSWHierarchical Navigable Small World是当前最流行、综合性能最好的ANN算法之一。你可以把HNSW想象成建立一个多层次的航空网络底层第0层包含所有向量点就像包含所有城市的公路网连接密集。高层第1、2...层是底层的一个随机子集层数越高包含的点越少连接越稀疏。这就像枢纽机场网络只有主要城市枢纽之间有直飞航班。检索过程类比找航班从最高层开始比如北京到悉尼先找国际枢纽。在当前层找到离目标点问题向量最近的那个枢纽点。跳到下一层以上一步找到的点为起点继续寻找更近的点。重复步骤2-3直到最底层。在最底层在局部区域内进行精细搜索找到最近的几个点。HNSW的优势查询速度快得益于“跳远”式的分层搜索它避免了遍历大部分无关节点。精度高在相同速度下其召回精度通常优于其他ANN算法如IVF。支持动态插入新的向量可以增量添加到索引中无需重建整个索引。HNSW的关键参数调优M每个节点在构造时建立的连接数。M越大图越稠密精度越高但构建和搜索速度越慢内存占用越大。通常设置在16-64之间。efConstruction构建索引时为每个节点寻找邻居的候选集大小。影响索引构建的质量值越大构建越慢但索引质量越高。efSearch搜索时的候选集大小。这是查询时最重要的参数。efSearch越大搜索越精细召回率越高但速度越慢。需要在精度和速度之间做权衡。实操建议对于大多数RAG应用可以从默认参数开始如M16,efConstruction200,efSearch50。上线后通过查询日志分析如果发现召回结果不相关可以适当调高efSearch如100如果查询延迟过高则可以适当调低efSearch。M和efConstruction一般在索引构建后就不再调整。6. 进阶策略多路召回与重排序基础的RAG使用单一的Embedding模型和ANN索引进行检索这被称为“单路召回”。但在复杂场景下单路召回可能不够可靠。多路召回策略旨在通过多种不同的方式或角度进行检索然后合并或筛选结果以提升召回内容的覆盖率和相关性。6.1 为什么需要多路召回Embedding模型的局限性没有哪个Embedding模型是完美的。它可能擅长理解语义关联但对精确的关键词匹配不敏感。查询的多样性用户的问题可能包含精确的实体名需要关键词匹配也可能是模糊的描述需要语义理解。缓解“语义鸿沟”用户提问用的词和知识库中文档用的词可能不同但表达同一意思。单一模型可能无法完全桥接。6.2 常见的多路召回策略1. 混合检索Hybrid Search这是最经典的多路召回结合了稠密检索和稀疏检索。稠密检索就是我们上面讲的基于Embedding向量的语义搜索。稀疏检索传统的关键词搜索如BM25算法。它基于词频、逆文档频率等统计信息擅长精确匹配。融合方式分别用两种方法检索出Top K个结果然后通过分数融合如加权求和、RRF得到一个最终排名。2. 多Embedding模型召回使用多个不同的Embedding模型如一个通用模型一个领域专用模型对同一查询和文档分别进行向量化和检索然后合并结果。这可以捕捉不同模型视角下的语义信息。3. 查询改写召回对原始用户查询进行改写或扩展生成多个相关的查询分别进行检索后合并结果。同义词扩展“苹果” - “苹果, Apple Inc., 水果”。问题分解“如何配置RAG的Chunking和Embedding” - 分解为“如何配置Chunking”和“如何选择Embedding模型”两个子问题。HyDE假设性文档嵌入让大模型根据用户问题生成一个假设性的理想答案文档然后用这个生成的文档去检索。这相当于用大模型“想象”了一下答案应该长什么样再用这个“想象”去搜索有时能取得奇效。6.3 重排序从“召回”到“精排”多路召回会产生一个更大的候选集例如每路召回10个3路合并后得到30个候选文档。直接把这30个都扔给大模型会浪费上下文窗口也可能引入噪声。因此需要一个重排序步骤从这30个中精选出最相关的3-5个。重排序模型通常是一个比Embedding模型更精细的、专门用于计算“查询-文档”相关性的模型如BGE-reranker,Cohere rerank。它会对“查询-文档”对进行深度交互计算给出一个更准确的相关性分数。虽然计算成本比Embedding高但因为它只对少量候选如30个进行计算所以总体开销可控却能显著提升最终上下文的质量。一个典型的多路召回重排序流水线用户查询 - [查询改写] - 多个查询变体 - 稠密检索主Embedding模型- 召回结果A - 稀疏检索BM25- 召回结果B - 稠密检索备用Embedding模型- 召回结果C - 合并去重A, B, C- 得到粗排候选集如30个 - 重排序模型对30个候选进行精排 - 得到Top 5最终上下文 - 送入大模型生成答案。7. 实战构建一个简易RAG检索系统的核心代码逻辑理解了原理我们来看一个高度简化的、聚焦于检索环节的Python伪代码示例它串联了上述几个核心概念。import numpy as np from sentence_transformers import SentenceTransformer from rank_bm25 import BM25Okapi from typing import List, Dict import jieba # 用于中文分词 class SimpleRAGRetriever: def __init__(self, embedding_model_name: str BAAI/bge-large-zh): # 1. 初始化Embedding模型 self.embedding_model SentenceTransformer(embedding_model_name) # 用于稀疏检索的BM25需要分词后的语料 self.tokenized_corpus [] # 存储原始文本块 self.chunks [] # 存储向量 self.embeddings None # BM25对象 self.bm25 None def build_index(self, documents: List[str]): 构建索引包括切分、嵌入、构建BM25和ANN此处简化 # 2. 文本切分 (这里使用简单的按句号切分实际项目需更复杂) self.chunks [] for doc in documents: # 简单按句号、问号、感叹号切分并过滤空字符串 sentences [s.strip() for s in doc.replace(。, .).replace(, ?).replace(, !).split(.) if s.strip()] self.chunks.extend(sentences) # 3. 为稠密检索准备生成嵌入向量 print(f正在为 {len(self.chunks)} 个文本块生成嵌入向量...) self.embeddings self.embedding_model.encode(self.chunks, normalize_embeddingsTrue, # 归一化用于余弦相似度 show_progress_barTrue) # 4. 为稀疏检索准备构建BM25索引 print(正在构建BM25索引...) # 中文分词 self.tokenized_corpus [list(jieba.cut(chunk)) for chunk in self.chunks] self.bm25 BM25Okapi(self.tokenized_corpus) # 5. 为稠密检索构建ANN索引此处简化实际需用FAISS, Milvus等 # 假设我们将向量存储在内存的numpy数组中后续用余弦相似度暴力计算TopK # 生产环境务必替换为HNSW索引如通过FAISS print(索引构建完成。) def hybrid_retrieve(self, query: str, top_k: int 5, dense_weight: float 0.7) - List[Dict]: 混合检索结合稠密和稀疏检索结果 # 6. 查询嵌入 query_embedding self.embedding_model.encode([query], normalize_embeddingsTrue)[0] # 7. 稠密检索简化版暴力计算余弦相似度 # 计算查询向量与所有块向量的余弦相似度 (因为已归一化点积即余弦相似度) dense_scores np.dot(self.embeddings, query_embedding) dense_indices np.argsort(dense_scores)[-top_k*2:][::-1] # 取2倍top_k用于后续融合 # 8. 稀疏检索BM25 query_tokens list(jieba.cut(query)) sparse_scores self.bm25.get_scores(query_tokens) sparse_indices np.argsort(sparse_scores)[-top_k*2:][::-1] # 9. 结果融合 - 使用简单的加权分数融合 # 先归一化两种分数到[0,1]区间 if len(dense_scores) 0: dense_scores_norm (dense_scores - dense_scores.min()) / (dense_scores.max() - dense_scores.min() 1e-8) if len(sparse_scores) 0: sparse_scores_norm (sparse_scores - sparse_scores.min()) / (sparse_scores.max() - sparse_scores.min() 1e-8) combined_scores {} for idx in set(list(dense_indices) list(sparse_indices)): dense_score dense_scores_norm[idx] if idx in dense_indices else 0 sparse_score sparse_scores_norm[idx] if idx in sparse_indices else 0 combined_scores[idx] dense_weight * dense_score (1 - dense_weight) * sparse_score # 10. 按融合分数排序返回Top K sorted_indices sorted(combined_scores.items(), keylambda x: x[1], reverseTrue)[:top_k] results [] for idx, score in sorted_indices: results.append({ chunk: self.chunks[idx], score: score, dense_score: dense_scores_norm[idx], sparse_score: sparse_scores_norm[idx] }) return results # 使用示例 if __name__ __main__: # 模拟文档数据 docs [ RAG是一种结合检索和生成的技术。它先检索相关文档再基于文档生成答案。, 文本切分是RAG的第一步目的是将长文档切成小块。常见的切分方法有固定长度切分和按分隔符切分。, 嵌入模型将文本转换为向量。语义相似的文本其向量在空间中的距离也近。, HNSW是一种高效的近似最近邻搜索算法用于快速查找相似向量。, 多路召回通过多种方式检索如混合检索可以提高召回率。 ] retriever SimpleRAGRetriever() retriever.build_index(docs) query 如何对文本进行切分 results retriever.hybrid_retrieve(query, top_k3) print(f查询: {query}) for i, res in enumerate(results): print(f{i1}. [分数: {res[score]:.3f}] {res[chunk]})这段代码展示了从文档加载到混合检索的核心流程。请注意这是一个用于教学原型的极度简化版本。生产环境需要考虑高效的ANN索引用FAISSFacebook AI Similarity Search或专业向量数据库Milvus, Weaviate, Qdrant替代暴力计算它们内置了HNSW等算法。更复杂的Chunking使用LangChain的RecursiveCharacterTextSplitter或专门的分割库。异步处理与批处理构建索引时处理大量文档。持久化存储将索引和向量存入磁盘或数据库。重排序模块集成一个重排序模型来精炼最终结果。8. 常见问题、排查技巧与效果评估构建RAG系统时你会遇到各种各样的问题。以下是一些典型问题及其排查思路问题1检索结果完全不相关。检查Embedding模型确认使用的模型是否适合你的语言和领域。尝试用几个简单的句子测试模型本身的相似度计算是否合理。检查Chunking你的文本块是否大小合适是否被不恰当地切断尝试打印出被召回的文本块看其内容是否完整。检查相似度计算确认向量数据库使用的相似度度量如余弦相似度与你的Embedding生成方式是否归一化匹配。调整HNSW参数尝试增大efSearch参数让搜索更彻底。问题2检索结果似乎相关但大模型给出的答案还是不对。检查上下文组装提供给大模型的上下文是否过长或过短是否包含了足够回答问题的信息尝试调整top_k参数。检查Prompt你的Prompt是否清晰指示了模型基于上下文回答尝试优化Prompt例如使用“请严格根据以下上下文回答问题如果上下文不包含答案请说不知道。”这样的指令。启用模型引用功能如果模型支持如GPT-4让其引用上下文中的具体段落这有助于你判断模型是否真的“看到”了关键信息。问题3系统响应速度慢。定位瓶颈使用 profiling 工具确定是Embedding推理慢、向量检索慢还是大模型生成慢。优化检索对于向量检索可以尝试减小efSearch参数以精度换速度或使用更快的ANN算法如IVF。缓存对常见的查询结果进行缓存。异步处理将Embedding计算、检索等步骤设计为异步。问题4如何处理新数据增量更新确保你的向量数据库支持增量插入。对于新文档进行同样的Chunking和Embedding流程后直接插入索引。注意HNSW支持增量插入但频繁插入可能影响索引结构需要定期优化。定期重建对于数据更新非常频繁的场景可以定期如每天全量重建索引以保证索引质量。如何评估RAG系统的效果不能只靠感觉需要量化评估。可以从两个层面进行检索阶段评估命中率对于一组测试问题检索到的Top K个文档中至少包含一个正确答案文档的比例。平均排序倒数正确答案在检索结果列表中的平均排名的倒数。端到端评估人工评估设计一批测试问题让人工评判答案的准确性、相关性和流畅度。基于LLM的自动评估使用一个更强的LLM如GPT-4作为裁判根据标准答案或上下文对生成的答案进行评分。可以评估忠实度答案是否严格基于上下文、相关性、信息完整性等维度。构建一个高效的RAG系统是一个迭代过程需要持续地在Chunking策略、Embedding模型、检索参数和Prompt工程之间进行调试和权衡。每一次调整都最好能有评估数据作为依据而不是盲目尝试。