标准RAG基础全流程拆解(你写的这条链路是最经典朴素RAG)
手写的完整流程:
知识手册 → Embedding 分块编码 → 构建向量索引,向量 + 原始文档存入向量库 → 用户提问 → 向量召回 (捞取 Top‑10 候选片段) → 重排 Rerank (精选 Top‑3) → Prompt 拼接 (问题 + 3 份参考文档) 送入 LLM → 输出答案
一、逐环节详细解释
阶段 1:文档预处理(离线,一次性做完)
-
原始文档:PDF、Word、知识库手册、markdown 等
-
文档分块 Chunking
❗这里刚好用到刚刚聊的 固定 stride 滑动窗口切分 !
把长文档切成一块块短片段
chunk,参数:chunk_size(窗口大小window_size)、chunk_overlap重叠长度,stride = chunk_size‑overlap为什么需要重叠?防止一句话被一刀切成两个 chunk、语义断裂,就是你前面 PPT 里面 LLM 文本滑窗。
-
Embedding
每一个文本块
chunk送入嵌入模型,转换成固定维度浮点向量,向量代表这段文字的语义 -
存入向量数据库
- 存两份东西:
chunk向量+ 原始文本 chunk、页码、来源文件名等元数据 - 向量库会建立专门索引 (FAISS、HNSW 等),用来做高速相似度搜索
⚠️向量库不会丢掉原文!向量只是用来做检索的钥匙;最终给大模型的仍然是原始文字,不是向量。
阶段 2:用户在线问答(实时流程)
- 用户输入问题
- 问题 Embedding :把用户提问,用同一个 Embedding 模型转为查询向量
- 初筛召回 (Retrieval)
在向量库里面,计算问题向量 和 全部文档块向量之间余弦相似度,返回相似度最高的 Top‑10 片段
- 优点:速度极快,支持百万级文档
- 缺点:粗筛,精度一般,容易出现语义相似但是文字事实无关的噪声
- 重排序 Rerank
把刚刚召回出来的 10 条候选片段,丢进专门的重排模型 (Cross‑Encoder)
重排模型同时看【用户问题 + 整条文档】做精细打分,重新排序,选出质量最高 Top‑3
召回:粗、快、广,宁多勿漏;重排:精、慢、准,做二次筛选
-
Prompt 组装
把用户问题、筛选好的 3 段参考上下文,拼接到提示词模板参考资料:
【文档1】xxx
【文档2】xxx
【文档3】xxx
根据上面材料回答问题:{用户提问} -
送入大模型生成答案
LLM 阅读参考文档,生成回答并且尽量引用知识库内容,减少幻觉。
二、流程图文字版
【离线构建知识库】
原始文档 → 滑动窗口分块(stride+overlap) → embedding → (向量+原文)存入向量数据库
【在线问答】
用户提问 → 问题embedding →向量数据库相似度召回Top‑10 →Rerank重排筛选Top‑3 →构造提示词(问题+上下文) →大模型生成回答
三、每个环节常见坑点
| 步骤 | 最常见问题 |
|---|---|
| 分块 Chunking | chunk 大小不合理,或者 overlap=0,完整句子被切开,关键信息被拆分到两个块(就是为什么要用带重叠 stride 滑窗) |
| Embedding | 问题、文档使用不一样的 embedding 模型,向量不在同一个语义空间,召回完全错乱 |
| 向量召回 | 只靠余弦相似度,字面相近但是事实无关,召回很多无效内容,这一步就是为什么需要重排 |
| Rerank | 召回数量太少 (比如直接召回 top3),重排没有选择空间;召回太多则速度变慢 |
| Prompt 拼接 | 上下文过长超过大模型上下文窗口上限,产生截断 |
四、和前面三个滑窗算法做联动回顾
- RAG 文档分块 = LLM 一维固定 stride 滑动窗口
- CNN stride:二维图像固定步长滑窗
- KMP:一维、内容驱动动态步长滑窗(精确字符串匹配 ,RAG 是语义相似度匹配,完全不一样)
补充区分:
KMP 是字面关键词精确查找 ;向量召回是模糊语义查找,哪怕用词不一样,只要意思接近就能找出来。
五、最简 Python 伪代码(逻辑版,不可直接运行,方便理解)
python
#离线阶段
def build_knowledge_base(文档,chunk_size,overlap):
chunks = sliding_window_split(文档,window_size=chunk_size,stride=chunk_size-overlap)
for chunk in chunks:
vec = embedding_model.encode(chunk)
vector_db.insert(vector=vec,text=chunk)
#在线问答
def rag_answer(question):
q_vec = embedding_model.encode(question)
#向量召回 top10
candidates = vector_db.search(q_vec,top_k=10)
#重排取top3
top3 = rerank_model.rerank(query=question,docs=candidates,top_k=3)
#拼接prompt
prompt = build_prompt(question,top3)
answer = llm.chat(prompt)
return answer
六、RAG核心三步可视化:分块、召回、重排
为直观理解RAG核心流程差异,以下通过可视化流程示意图+通俗解析,拆解分块、向量召回、重排三个关键环节的执行逻辑、数据变化和核心作用。
6.1 第一步:文档分块可视化(离线预处理)
核心规则:固定窗口大小+固定重叠长度,LLM滑动窗口切分,避免语义断裂
原始完整长文档:【A B C D E F G H I J】
窗口大小chunk_size=4 | 重叠overlap=2 | 步长stride=2
分块过程:
Chunk1:【A B C D】
Chunk2:【C D E F】 (重叠2个字符:C D)
Chunk3:【E F G H】 (重叠2个字符:E F)
Chunk4:【G H I J】 (重叠2个字符:G H)
最终入库:4个文本块 + 对应4组语义向量
关键作用:将超长文档切割为大模型可处理的短片段,窗口重叠设计完美规避句子、关键信息被生硬切割的问题。
6.2 第二步:向量召回可视化(在线粗筛)
核心规则:语义相似度匹配,广撒网、宁多勿漏,粗筛Top10候选片段
用户提问:【针对 C F H 相关知识点解答】
问题Embedding:生成提问语义向量Q
向量库匹配逻辑:
Q VS 所有Chunk向量 → 计算余弦相似度,从高到低排序
初筛结果(Top10):
✅Chunk2【C D E F】(高度相关)
✅Chunk3【E F G H】(高度相关)
✅Chunk1【A B C D】(部分相关)
❌Chunk5【xxx】(语义相近、事实无关噪声)
❌Chunk6【xxx】(低相关冗余内容)
......(共10条候选)
关键作用:依托向量数据库高速检索,快速过滤海量知识库内容,保留大概率相关片段,速度快、覆盖全,但存在少量噪声。
6.3 第三步:重排Rerank可视化(在线精筛)
核心规则:全文语义精细校验,去噪提纯、择优留存,精选Top3优质片段
输入:召回阶段10条候选片段
重排模型逐一对齐【用户提问+完整文本块】精细打分
重排打分排序后:
1. Chunk2【C D E F】(匹配度98%,核心相关)
2. Chunk3【E F G H】(匹配度95%,核心相关)
3. Chunk1【A B C D】(匹配度82%,辅助相关)
--------- 以下全部淘汰 ---------
4. 噪声片段(匹配度45%,无关)
5. 冗余片段(匹配度30%,无效)
......
最终留存:Top3高质量文本块,送入LLM生成答案
关键作用:弥补向量召回的精度缺陷,剔除语义相似但事实无关的噪声,筛选出最贴合用户问题的核心上下文,提升回答准确率。
6.4 三步核心差异可视化总结
| 流程环节 | 核心特点 | 工作逻辑 | 核心目标 |
|---|---|---|---|
| 文档分块 | 离线、固定规则、无筛选 | 滑动窗口均匀切分,保留重叠语义 | 适配模型输入长度,保全文本语义 |
| 向量召回 | 在线、高速、粗筛选 | 向量语义相似度匹配,批量召回Top10 | 全覆盖、不遗漏有效信息 |
| 重排Rerank | 在线、精细、精筛选 | 全文语义精准打分,过滤噪声留存Top3 | 提精度、降噪、优化上下文质量 |
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RAG 完整流程动态可视化动画</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft Yahei", sans-serif;
}
body {
background: #0f172a;
color: #f1f5f9;
padding: 40px 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 10px;
color: #38bdf8;
font-size: 28px;
}
.desc {
text-align: center;
color: #94a3b8;
margin-bottom: 40px;
}
.stage-tip {
text-align: center;
font-size: 18px;
color: #facc15;
margin-bottom: 30px;
height: 24px;
}
.flow-wrap {
display: flex;
flex-direction: column;
gap: 24px;
}
.box {
background: #1e293b;
border-radius: 12px;
padding: 20px;
border: 1px solid #334155;
transition: all 0.5s ease;
opacity: 0.4;
transform: translateY(10px);
}
.box.active {
opacity: 1;
transform: translateY(0);
border-color: #38bdf8;
box-shadow: 0 0 15px #38bdf840;
}
.box-title {
font-size: 16px;
color: #e2e8f0;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.box-title span {
display: inline-block;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 50%;
background: #38bdf8;
color: #000;
font-weight: bold;
font-size: 14px;
}
.chunk-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.chunk {
padding: 8px 12px;
background: #334155;
border-radius: 6px;
font-size: 14px;
transition: all 0.4s ease;
}
.chunk.overlap {
background: #22d3ee33;
border: 1px solid #22d3ee;
}
.chunk.in-db {
background: #10b98133;
border: 1px solid #10b981;
}
.query-box {
padding: 10px 14px;
background: #facc1522;
border: 1px solid #facc15;
border-radius: 6px;
color: #facc15;
display: inline-block;
}
.ret-list .item {
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
margin-bottom: 8px;
background: #334155;
transition: all 0.4s ease;
}
.item.high {
background: #10b98133;
border: 1px solid #10b981;
}
.item.low {
background: #ef444422;
border: 1px solid #ef4444;
opacity: 0.5;
}
.arrow {
text-align: center;
color: #38bdf8;
font-size: 20px;
margin: 4px 0;
opacity: 0;
transition: all 0.3s ease;
}
.arrow.show {
opacity: 1;
}
.btn-wrap {
text-align: center;
margin-top: 40px;
}
button {
padding: 12px 30px;
font-size: 16px;
border: none;
border-radius: 8px;
background: #38bdf8;
color: #000;
font-weight: bold;
cursor: pointer;
transition: 0.3s;
margin: 0 8px;
}
button:hover {
background: #0ea5e9;
}
button.reset {
background: #64748b;
color: #fff;
}
.answer-box {
margin-top: 12px;
padding: 14px;
background: #a855f722;
border: 1px solid #a855f7;
border-radius: 8px;
color: #e9d5ff;
opacity: 0;
transition: all 0.5s ease;
}
.answer-box.show {
opacity: 1;
}
</style>
</head>
<body>
<div class="container">
<h1>RAG 完整流程动态可视化动画</h1>
<p class="desc">离线分块入库 → 在线提问 → 向量召回Top10 → 重排Top3 → LLM 生成答案</p>
<div class="stage-tip" id="stageTip">点击「开始播放」启动动画</div>
<!-- 1.原始文档分块 -->
<div class="box" id="box1">
<div class="box-title"><span>1</span>文档滑动窗口分块(离线)</div>
<p style="color:#94a3b8;font-size:14px;margin-bottom:10px;">窗口大小=4,重叠=2,步长=2,带重叠防语义断裂</p>
<div class="chunk-list">
<div class="chunk" id="c1">Chunk1: A B C D</div>
<div class="chunk" id="c2">Chunk2: <span class="overlap">C D</span> E F</div>
<div class="chunk" id="c3">Chunk3: <span class="overlap">E F</span> G H</div>
<div class="chunk" id="c4">Chunk4: <span class="overlap">G H</span> I J</div>
</div>
</div>
<div class="arrow" id="arr1">↓</div>
<!-- 2.向量化入库 -->
<div class="box" id="box2">
<div class="box-title"><span>2</span>Embedding 向量化 + 向量数据库入库</div>
<p style="color:#94a3b8;font-size:14px;margin-bottom:10px;">文本块转为语义向量,向量+原文存入向量库</p>
<div class="chunk-list" id="dbList">
</div>
</div>
<div class="arrow" id="arr2">↓</div>
<!-- 3.用户提问 -->
<div class="box" id="box3">
<div class="box-title"><span>3</span>用户提问 + 问题 Embedding</div>
<div class="query-box" id="queryText">问题:针对 C、F、H 相关知识点解答</div>
</div>
<div class="arrow" id="arr3">↓</div>
<!-- 4.向量召回Top10 -->
<div class="box" id="box4">
<div class="box-title"><span>4</span>向量粗召回(Top10 广筛、宁多勿漏)</div>
<p style="color:#94a3b8;font-size:14px;margin-bottom:10px;">余弦相似度匹配,批量召回,包含少量噪声</p>
<div class="ret-list" id="retList">
<div class="item" id="r1">Chunk2【C D E F】- 高度相关</div>
<div class="item" id="r2">Chunk3【E F G H】- 高度相关</div>
<div class="item" id="r3">Chunk1【A B C D】- 部分相关</div>
<div class="item" id="r4">Chunk5【xxx】- 语义相近无关(噪声)</div>
<div class="item" id="r5">Chunk6【xxx】- 低相关冗余</div>
</div>
</div>
<div class="arrow" id="arr4">↓</div>
<!-- 5.重排Top3 -->
<div class="box" id="box5">
<div class="box-title"><span>5</span>Rerank 重排精筛(留存 Top3 优质上下文)</div>
<p style="color:#94a3b8;font-size:14px;margin-bottom:10px;">精细语义打分,过滤噪声,提纯上下文</p>
<div class="ret-list" id="rerankList">
</div>
</div>
<div class="arrow" id="arr5">↓</div>
<!-- 6.LLM生成答案 -->
<div class="box" id="box6">
<div class="box-title"><span>6</span>Prompt 拼接 + LLM 生成最终答案</div>
<div class="answer-box" id="answerBox">
根据参考文档中 C、F、H 相关知识点:<br/><br/>
1. C、D、E、F 模块核心知识点覆盖基础定义与使用规则;<br/>
2. E、F、G、H 模块为拓展应用知识点,承接前文基础内容;<br/>
3. 结合上下文可完整解答对应考点,无幻觉、答案贴合内部知识库。
</div>
</div>
<div class="btn-wrap">
<button id="playBtn">开始播放动画</button>
<button class="reset" id="resetBtn">重置动画</button>
</div>
</div>
<script>
// 获取所有DOM
const stageTip = document.getElementById('stageTip');
const playBtn = document.getElementById('playBtn');
const resetBtn = document.getElementById('resetBtn');
const boxes = [
document.getElementById('box1'),
document.getElementById('box2'),
document.getElementById('box3'),
document.getElementById('box4'),
document.getElementById('box5'),
document.getElementById('box6')
];
const arrows = [
document.getElementById('arr1'),
document.getElementById('arr2'),
document.getElementById('arr3'),
document.getElementById('arr4'),
document.getElementById('arr5')
];
const dbList = document.getElementById('dbList');
const rerankList = document.getElementById('rerankList');
const answerBox = document.getElementById('answerBox');
// 重置所有状态
function resetAll() {
boxes.forEach(box => box.classList.remove('active'));
arrows.forEach(arr => arr.classList.remove('show'));
dbList.innerHTML = '';
rerankList.innerHTML = '';
answerBox.classList.remove('show');
stageTip.innerText = '点击「开始播放」启动动画';
// 重置召回列表样式
document.querySelectorAll('.ret-list .item').forEach(item => {
item.classList.remove('high','low');
});
}
// 延时函数
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 动画主流程
async function runAnimation() {
resetAll();
playBtn.disabled = true;
// 1.分块
stageTip.innerText = '步骤1:滑动窗口分块,生成带重叠文本块';
boxes[0].classList.add('active');
await sleep(1200);
// 箭头1
arrows[0].classList.add('show');
await sleep(600);
// 2.入库
stageTip.innerText = '步骤2:文本Embedding,向量+原文存入向量数据库';
boxes[1].classList.add('active');
const chunksText = ['Chunk1: A B C D','Chunk2: C D E F','Chunk3: E F G H','Chunk4: G H I J'];
for(let text of chunksText){
let div = document.createElement('div');
div.className = 'chunk in-db';
div.innerText = text;
dbList.appendChild(div);
await sleep(300);
}
await sleep(1000);
// 箭头2
arrows[1].classList.add('show');
await sleep(600);
// 3.用户提问
stageTip.innerText = '步骤3:用户提问,问题生成Embedding查询向量';
boxes[2].classList.add('active');
await sleep(1200);
// 箭头3
arrows[2].classList.add('show');
await sleep(600);
// 4.向量召回
stageTip.innerText = '步骤4:向量数据库相似度召回,粗筛Top10(含噪声)';
boxes[3].classList.add('active');
// 标记高低质量
document.getElementById('r1').classList.add('high');
document.getElementById('r2').classList.add('high');
document.getElementById('r3').classList.add('high');
document.getElementById('r4').classList.add('low');
document.getElementById('r5').classList.add('low');
await sleep(1500);
// 箭头4
arrows[3].classList.add('show');
await sleep(600);
// 5.重排
stageTip.innerText = '步骤5:Rerank重排降噪,精准筛选Top3优质上下文';
boxes[4].classList.add('active');
const top3 = [
'1. Chunk2【C D E F】 匹配度98%',
'2. Chunk3【E F G H】 匹配度95%',
'3. Chunk1【A B C D】 匹配度82%'
];
for(let text of top3){
let div = document.createElement('div');
div.className = 'item high';
div.innerText = text;
rerankList.appendChild(div);
await sleep(400);
}
await sleep(1200);
// 箭头5
arrows[4].classList.add('show');
await sleep(600);
// 6.生成答案
stageTip.innerText = '步骤6:拼接Prompt,LLM基于知识库生成答案,杜绝幻觉';
boxes[5].classList.add('active');
answerBox.classList.add('show');
stageTip.innerText = '✅ RAG 全流程动画演示完毕!';
playBtn.disabled = false;
}
// 绑定事件
playBtn.addEventListener('click', runAnimation);
resetBtn.addEventListener('click', resetAll);
</script>
</body>
</html>
