rag
rag
1、基本工作流程
一个完整的RAG应用流程主要分为两大核心环节。在数据准备阶段,系统通过数据提取、文本分割和向量化,将外部知识构建成一个可检索的数据库。随后在应用阶段,系统会响应用户的提问,从数据库中检索(通过相似度对比)相关信息,将其注入Prompt,并最终驱动大语言模型生成答案。
2、入库流程
2.1、chunk切分
问:为什么 RAG 一定要做 Chunk(文档分块),我直接把整个文档 Embedding 不行吗?
答:实际上大部分场景都不行。
原因:
-
Embedding 模型有长度限制:会出现超长截断,后面的内容直接丢失。所以必须将整篇文档切成多个chunk,分别embedding
-
太长会导致语义模糊:如果太长召回的就是整个文章,而非某一段落
-
方便更新:方便修改对应的一块级别
文档chunk切分方案有:通用方式、父子分块、Q&A
-
通用:通用文件分块模式,检索和召回的块是相同的
-
父子:子块用于检索,父块作用上下文
-
Q&A:块被拆分为问题和答案对,检索时将使用问题部分进行检索,答案部分将作为上下文返回
2.1.1、通用切片
比如每 500 Token 一个 Chunk,重叠 50 Token。
特点: 实现简单,适合大多数文档、Wiki、技术文档;缺点是切得不好可能把一个完整语义拆开。
2.1.2、父子切片(Parent-Child Chunk)
核心思想是:小块负责检索,大块负责回答**。**
小 Chunk 更容易精准匹配用户问题,但真正给 LLM 时返回父 Chunk,保证上下文完整。
特点: 兼顾检索精度 + 上下文完整性,适合长文档、技术手册、合同、复杂知识库;代价是数据结构和检索逻辑更复杂。
2.1.3、QA 切片
把知识整理成 问题 + 答案 的形式。
如果文档本身就是qa的方式,就只需要按照格式切分,如果不是,就需要介入大模型进行qa切片
特点: 问答场景命中率高,适合 FAQ、客服、知识库;缺点是需要提前生成/维护 QA,可能丢失原文上下文。
2.1.4、总结
| 方式 | 核心思路 | 适合 |
|---|
2.2、元数据构建
元数据构建就是给每个 Chunk 加一些额外信息,例如:
后面检索到 Chunk 后,就能知道它来自哪个文档、哪一页、哪个章节。
2.3、建立索引 Index
可以把 RAG 建立索引理解成:把原始资料加工成“以后能快速搜到”的知识库。这一步是在向量化后(通常建立索引和入库是同步的)
这和一些普通数据库需要建立索引index来方便查询是一个道理(是一种优化查询速度的手段,不是必须的)
常见的索引主要有两类:
-
向量索引:每个 chunk 转成 embedding,存到向量数据库里。用户提问也转成向量,然后找最相似的 chunk。
-
关键词/倒排索引:记录“某个词出现在哪些 chunk 中”,比如“年假”出现在 chunk 12、chunk 35,常见于 Elasticsearch / BM25。
如果你的查询阶段是:
那么入库阶段最好也对应建立两套索引:
这样查询时才能做 Hybrid Search(混合检索)/多路召回。
3、检索流程
3.1、前置知识
-
词频(TF):文字在句子中出现的次数
-
逆文档频率(IDF):在文档中出现的次数越少,得分越高
-
长句子惩罚:文档长度归一化处理(防止长句子占的评分高)
- 常用的打分算法:
TF-IDF和BM25,都是用来相似度打分的。BM25算法可以“精准”匹配
3.2、查询重写
实际生产环境里,查询重写方式一般会拆成下面几类:
3.2.1、指代消解/上下文补全
需要修正歧义,比如一些代词
实现方式:
- 会话历史:
- LLM重写:根据上下文,推出那篇文章指的是具体的哪篇
3.2.2、同义词扩展
就需要对内容进行拆解,分别进行检索,最后合并问题
重写后:
生成多个查询,然后分别检索
3.2.3、缩写补全
3.2.4、问题拆解
这是企业级 RAG 必做的
拆解为以下内容,分别检索:
再复杂点:
拆成:
3.2.5、HyDE(Hypothetical Document Embedding)
HyDE翻译过来是假设性文档嵌入,就是拿用户的问题,先让大模型生成一个假答案,然后拿这个假答案再去Embedding。
因为模型会多猜几个结果,再去匹配知识库重合度就会高。
流程:
3.2.6、Multi Query Retrieval
一个问题生成多个查询。例如LangChain里面就有:MultiQueryRetriever
3.2.7、意图补全
3.2.8、总结
实际上 Query Rewrite 干的事情可以归为三类:
-
补充缺失上下文
-
转换表达方式
-
拆解复杂问题
如果需要做企业级知识库,需要:
其中 Query Rewrite 至少做:
✅ 指代消解
✅ 同义词扩展
✅ 缩写补全
✅ 问题拆解
3.3、混合检索/多路召回
RAG 里的多路召回,可以简单理解成:同时用多种方法去“找资料”,把找到的结果合在一起,再挑最相关的给大模型
比如用户问:“公司的年假政策是什么?”
系统可能同时走几条“路”:
-
向量召回:按语义找相似内容,比如找到“员工休假制度”
-
关键词召回:直接搜索“年假”“休假政策”
-
结构化召回:从数据库里查员工制度、HR 规则
-
知识图谱/标签召回:根据“HR → 假期 → 年假”关系找到文档
然后把这几路结果合并、去重、排序(rerank),选出最相关的几段交给大模型生成答案。
混合检索方案基本就是:稀疏检索和稠密检索
3.3.1、稠密向量检索
稠密检索 Dense就是向量/语义检索,通过Embedding + Vector Search
也就是将query转为向量,然后去将向量数据库与query的向量进行相似度对比(和pgsql查询具体数据一样),高的排在前面
3.3.2、稀疏检索
稀疏检索 Sparse就是关键词检索,常见的方案是:BM25、倒排索引
-
BM25负责:“这些文档哪个和 Query 更相关”
-
倒排索引负责:“快速找到哪些文档包含这些词”
例如使用Elasticsearch、OpenSearch进行全文检索,OpenSearch也提供BM25 全文关键词检索
BM25是检索算法
3.3.3、RRF:按名次融合
RRF(Reciprocal Rank Fusion)不关心原始分数,只看文档在每一路检索中的排名。
基本公式:
RRF 的优点是简单稳定,不需要比较 BM25 分数和向量相似度分数的量纲。一般适合作为默认方案。
3.3.4、加权归一化:按分数融合
先把不同检索方式的分数统一到相近尺度,再按照设定的权重比例计算一个最终分数。
BM25 可能给出 12.5,向量检索可能给出 0.86,两者不能直接相加。因此先把分数归一化到相同范围,再按权重组合:
例如:
适合需要明确控制关键词和语义检索权重的场景,但权重和归一化方法需要通过评测调优。
3.4、合并/去重
同一个文档或相邻切片可能同时被 BM25 和向量检索命中,比如两路召回:
-
向量召回:
A、B、C -
关键词召回:
B、C、D
合并:直接把结果放到一个候选集合:A、B、C、B、C、D
去重:最常见的是根据唯一 ID 去重。如果发现两个结果的 chunk_id 一样,就知道它们其实是同一段内容。也可以通过向量相似度去判断。
3.5、rerank重排
RRF 解决的是“两路结果如何合并”,rerank 解决的是“哪些内容真正能回答问题”。
例如先召回 30 条候选:
Reranker 通常比向量相似度判断得更精确,但计算更贵,所以只对已经召回的小批候选执行。
两种主流实现方式:
-
Cross-Encoder 交叉编码器(工业最常用)
-
LLM-based Rerank(大模型重排)
3.5.1、Cross-Encoder Reranker
普通向量检索是分别计算 query 和文档的向量:
Cross-Encoder 则把两者一起输入模型:
也就是需要把 Query 和混合检索召回的每一个候选 Chunk 一一配对送进 Cross-Encoder,然后直接对 Query + Chunk 这一对文本进行联合理解,然后输出一个相关性分数。
例如用户问题:退款到账需要多久?
候选结果:
| 候选 | RRF 排名 | Rerank 分数 |
|---|
RRF 认为第二条综合排名第一,但 reranker 判断第一、第三条更能回答“多久到账”,因此重新排序。
3.5.2、LLM-based Rerank
调用轻量大模型,让模型判断 “这条文档是否能回答用户问题”,输出相关性分值,适合复杂业务、多轮问答。 成本更高,但理解能力更强,能区分字面相似但逻辑无关的文本。
3.6、context
Context 就是最终提交给大模型的证据材料,通常包含正文、标题、来源和引用编号:
然后要求模型:
一句话总结:RRF/加权归一化负责合并多路召回,去重负责清理重复内容,rerank 负责挑出最相关证据,context 负责把最终证据整理给大模型。