RAG 是怎么解决 LLM 上下文窗口问题的?
LLM 有一个天然限制:上下文窗口是有限的。
假设你有一个包含 10 万篇文档的知识库,当用户问:
“公司的退款规则是什么?”
显然不能把 10 万篇文档全部塞给 LLM。
一方面可能超过模型的上下文窗口,另一方面,即使勉强塞进去,大量无关内容也会干扰模型判断。
RAG 的思路其实很简单:
不要让 LLM 阅读整个图书馆,而是先帮它找到最相关的几页。
整个过程大概分成两步。
第一步:知识入库时,把文档切成小块
例如原始文档:
员工手册
├── 公司介绍
├── 请假制度
├── 报销制度
├── 退款制度
└── 离职流程
RAG 通常不会直接保存整篇文档,而是切成很多小块:
Chunk 1:公司介绍……
Chunk 2:请假制度……
Chunk 3:报销制度……
Chunk 4:退款制度……
Chunk 5:离职流程……
然后使用 Embedding 模型,把每个文本块转换成一串数字,也就是向量:
退款制度
↓
Embedding
↓
[0.21, -0.37, 0.81, 0.12, ...]
这些向量会存进向量数据库。
所以可以把向量数据库理解成:
一个按照“语义”保存知识的搜索引擎。
第二步:用户提问时,只找相关的知识块
用户问:
退款需要多久?
这个问题也会被转换成向量:
退款需要多久?
↓
Embedding
↓
[0.19, -0.35, 0.79, 0.15, ...]
接下来系统比较这个问题和知识库里各个 Chunk 的向量距离。
例如:
退款制度 相似度 0.93
付款说明 相似度 0.81
报销制度 相似度 0.52
公司介绍 相似度 0.12
于是系统只取最相关的几个块:
Top 1:退款制度
Top 2:付款说明
再把它们和用户的问题一起交给 LLM:
参考资料:
退款通常会在 3~5 个工作日原路退回。
用户问题:
退款需要多久?
请根据参考资料回答。
于是 LLM 回答:
退款通常需要 3~5 个工作日。
这就是最基础的 RAG。
所以 RAG 到底解决了什么?
没有 RAG 时:
用户问题
↓
大量文档全部塞给 LLM
↓
上下文太长
↓
成本高 + 噪音多 + 容易找不到重点
有 RAG 后:
用户问题
↓
检索
↓
只找最相关的几个 Chunk
↓
放进上下文
↓
LLM 回答
所以它解决问题的核心并不是:
“让 LLM 的上下文窗口变大。”
而是:
在有限的上下文窗口里,只放最值得放进去的信息。
可以把它理解成一个“上下文筛选器”。
你的理解哪里对,哪里需要补充?
你说:
知识处理的时候分块,然后检索的时候寻找语义相近的块。
这个理解基本正确。
完整一点其实是:
文档
↓
Chunk 分块
↓
Embedding 向量化
↓
向量数据库
↓
用户问题
↓
Embedding
↓
语义检索
↓
Top-K Chunk
↓
重新排序 Rerank
↓
放进 LLM Context
↓
生成答案
这里还有两个很重要的细节。
第一个叫 Top-K。
不是只找一个 Chunk,而是通常会找:
Top 3
Top 5
Top 10
因为真正的答案可能分散在多个 Chunk 中。
第二个叫 Rerank。
向量搜索只是粗筛,例如先找到 20 个可能相关的 Chunk。
然后再让一个更精确的模型重新排序:
向量搜索:
20 个候选 Chunk
↓
Reranker
↓
最相关的 5 个 Chunk
↓
LLM
因此真实生产环境里的 RAG,通常不是简单的:
Embedding → Vector Search → LLM
而更像:
Embedding
↓
Retrieve
↓
Rerank
↓
Context
↓
LLM
最后一个容易混淆的问题
RAG 可以解决上下文太长的问题,但不能完全解决 LLM 的“上下文位置偏差”。
比如给模型 20 段资料:
资料1
资料2
资料3
...
资料10 ← 真正答案
...
资料20
模型有时候对开头和结尾的信息利用得更好,而容易忽略中间的信息,这通常被称为:
Lost in the Middle。
RAG 的作用是尽量把:
20 段资料
减少成:
3~5 段真正相关的资料
这样 LLM 就更容易找到答案。
所以一句话总结 RAG:
RAG 不是让模型记住更多知识,而是在模型回答问题之前,先帮它把最相关的知识找出来,再放进有限的上下文窗口里。
如果把 LLM 比作一个正在考试的学生,那么 RAG 就像一个助手:
学生不用翻完整本教材,助手先根据题目找到最相关的几页,再把这几页递给学生。
这就是 RAG 最核心的思想。