RAG检索增强生成
RAG 检索增强生成(Retrieval-Augmented Generation)
本文档定位:RAG 入门到面试全攻略,适合新手系统学习,也适合面试前快速复习。
把"私有知识"塞进大模型问答流程,让 LLM 基于你给的资料回答,而不是凭训练记忆瞎编。
一、为什么需要 RAG(目的)
LLM 直接回答有三个硬伤:
| 问题 | 表现 | RAG 怎么解 |
|---|---|---|
| 知识截断 | 训练数据有截止日期,新事件不知道 | 把新资料向量化存库,检索后喂给 LLM |
| 幻觉 | 没答案也硬编一个 | 让 LLM 只基于检索到的资料回答,无依据就说"不知道" |
| 私有知识缺位 | 公司文档、个人 PDF 没进训练集 | 私有文档本地入库,LLM 不用重训即可问答 |
一句话目的:用"检索 + 拼接上下文"代替"微调",让 LLM 低成本接入任意私有/最新知识。
二、RAG 步骤流程
整体分两大阶段:离线(建库) + 在线(问答)。
阶段 1:离线——构建知识库
PDF/Word/Markdown ──①加载──▶ 纯文本 ──②切分──▶ 文本块 ──③向量化──▶ 向量 ──④入库──▶ 向量库
| 步骤 | 做什么 | 关键参数 |
|---|---|---|
| ① 文档加载 | 用 Loader 把 PDF 等解析成纯文本(带元数据如页码) | PyPDFLoader / TextLoader |
| ② 文本切分 | 把长文本按语义边界切成小块,便于向量检索 | chunk_size、chunk_overlap |
| ③ 向量化 | 用 Embedding 模型把每块文本转成向量 | 模型维度(768/1024/1536) |
| ④ 入库 | 向量 + 原文存进向量数据库,建立索引 | persist_directory、距离度量 |
阶段 2:在线——检索生成
用户提问 ──⑤向量化──▶ 问题向量 ──⑥相似检索──▶ Top-K 文本块 ──⑦拼接上下文──▶ Prompt ──⑧LLM 生成──▶ 回答
| 步骤 | 做什么 | 关键参数 |
|---|---|---|
| ⑤ 问题向量化 | 用同一个 Embedding 模型把问题也转成向量 | 必须和离线阶段一致 |
| ⑥ 相似检索 | 在向量库找最相似的 K 个文本块 | k=2~5、距离度量(余弦/L2) |
| ⑦ 拼接上下文 | 把检索到的文本块塞进 Prompt 模板 | stuff / map_reduce / refine |
| ⑧ LLM 生成 | LLM 基于上下文 + 问题生成回答 | temperature、模型选择 |
流程图(端到端)
离线四步详解
| 步骤 | 名称 | 做什么 | 常用工具/模型 |
|---|---|---|---|
| 离线-① | 文档解析 | 把 PDF/Word/MD 解析成纯文本,保留页码等元数据 | PyPDFLoader、TextLoader、UnstructuredFileLoader |
| 离线-② | 文本分块 | 按语义边界(句号/换行)切成长度适中的小块,重叠保留上下文 | RecursiveCharacterTextSplitter,chunk_size + chunk_overlap |
| 离线-③ | 向量化模型 | 用 Embedding 模型把每个文本块转成向量 | BGE-m3、m3e-base、OpenAI ada-002、nomic-embed-text |
| 离线-④ | 向量数据库 | 向量 + 原文存入数据库,建索引,持久化 | Chroma、FAISS、Milvus、Qdrant、Pinecone |
在线四步详解
| 步骤 | 名称 | 做什么 | 关键点 |
|---|---|---|---|
| 在线-① | 转向量 | 用同一个 Embedding 模型把用户问题转成向量 | 必须和离线一致,否则距离无意义 |
| 在线-② | 检索 | 语义相似度检索:先 Top-K 召回,再 Rerank 重排精筛 | 余弦相似度 + Rerank 模型(如 BAAI/bge-reranker-v2-m3) |
| 在线-③ | 上下文构建 | 把检索到的参考内容 + 用户问题塞进 Prompt 模板 | PromptTemplate / ChatPromptTemplate,指定 system 人格 |
| 在线-④ | 生成 | 大模型基于拼接好的 Prompt 生成回答 | 本地:Ollama/SGLang/vLLM;在线 API:DeepSeek/OpenAI/Claude |
检索阶段的两段式(召回 + 重排)
问题向量 ─▶ 向量库 Top-K 召回(粗筛,K=20~50) ─▶ Rerank 重排(精筛,取 Top-3~5)─▶ 最终上下文
│ │
▼ ▼
语义相似度(余弦) Rerank 模型打分
速度快、粗 慢但准
为什么需要 Rerank:向量检索(双塔)快但粗,Rerank 模型(交叉编码)慢但准,两者互补。
Rerank 代码示例(用 bge-reranker-v2-m3)
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
# 1. 加载 bge-reranker-v2-m3 重排模型(本地,约 568MB)
reranker = HuggingFaceCrossEncoder(model_name="BAAI/bge-reranker-v2-m3")
# 2. 用它做压缩重排:先召回 Top-20,再重排取 Top-3
compression_retriever = ContextualCompressionRetriever(
base_compressor=CrossEncoderReranker(model=reranker, top_n=3),
base_retriever=vectorstore.as_retriever(search_kwargs={"k": 20})
)
# 3. 检索时自动完成:召回 → 重排 → 返回 Top-3
docs = compression_retriever.invoke("你的问题")
上下文构建示例
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是助手,只根据以下参考资料回答,没有就说不知道:\n{context}"),
("human", "{question}"),
])
生成端的部署形态对比
| 形态 | 代表 | 优势 | 劣势 |
|---|---|---|---|
| 本地部署 | Ollama / SGLang / vLLM | 隐私、不出境、可微调 | 要 GPU、部署麻烦 |
| 在线 API | DeepSeek / OpenAI / Claude | 开箱即用、性能强 | 数据出境、付费、限速 |
三、向量数据库对比
总览表
| 数据库 | 特点 | 部署形态 | 适合场景 | 维度限制 | 持久化 |
|---|---|---|---|---|---|
| Chroma | 轻量级,本地开发首选 | 本地嵌入式 | 原型/小项目/教学 | 任意 | sqlite + parquet |
| FAISS | Facebook 开源,高性能 CPU/GPU | 库(无服务端) | 高性能检索/内存够大 | 任意 | 需手动 save/load |
| Pinecone | 云端托管,企业级 | SaaS | 不想运维/生产上线 | 任意 | 云端托管 |
| Milvus / Zilliz | 分布式,大规模 | 自建/云托管 | 亿级以上向量 | 任意 | 分布式存储 |
| Qdrant | 功能丰富,支持过滤 | 本地/自建/云 | 需元数据过滤 | 任意 | 本地文件 |
区别详解
Chroma
- 定位:本地开发首选,开箱即用
- 优势:纯 Python,pip 装完即用;自动持久化到 sqlite;和 LangChain 集成最好
- 劣势:单机、性能一般、扩展性弱
- 使用:
from langchain_chroma import Chroma db = Chroma.from_documents(documents, embedding=embeddings, persist_directory='lx') retriever = db.as_retriever(search_kwargs={'k': 2})
FAISS
- 定位:高性能检索库,Facebook 出品
- 优势:CPU/GPU 都快,内存检索极速;支持多种索引(IVF/HNSW/Flat)
- 劣势:无服务端、无元数据过滤、需手动 save_local/load_local;不是真正的数据库
- 使用:
from langchain_community.vectorstores import FAISS db = FAISS.from_documents(documents, embedding=embeddings) db.save_local('faiss_db') db = FAISS.load_local('faiss_db', embeddings, allow_dangerous_deserialization=True)
Pinecone
- 定位:云端托管向量数据库
- 优势:零运维、自动扩容、企业级 SLA
- 劣势:付费、数据上云(合规问题)、国内访问慢
- 使用:
from langchain_pinecone import PineconeVectorStore db = PineconeVectorStore.from_documents(documents, embedding=embeddings, index_name='my-index')
Milvus / Zilliz
- 定位:分布式大规模向量库
- 优势:亿级向量、高可用、云原生
- 劣势:部署复杂(需 etcd/MinIO)、资源占用大
- 使用:通常用 Zilliz Cloud 或 docker compose 起 Milvus
from langchain_milvus import Milvus db = Milvus.from_documents(documents, embedding=embeddings, connection_args={'uri':'http://localhost:19530'})
Qdrant
- 定位:功能丰富的向量库
- 优势:原生支持元数据过滤、Payload 过滤强、Rust 写性能稳
- 劣势:相比 Chroma 稍重
- 使用:
from langchain_qdrant import Qdrant db = Qdrant.from_documents(documents, embedding=embeddings, url='http://localhost:6333', collection_name='docs')
选型建议
| 你的情况 | 推荐 |
|---|---|
| 学习/本地原型/小数据 | Chroma |
| 要极致检索速度、内存够 | FAISS |
| 不想运维、上线即用 | Pinecone |
| 亿级向量、大规模生产 | Milvus |
| 要复杂过滤、生产中等规模 | Qdrant |
四、Embedding 模型对比
总览表
| 模型 | 维度 | 中文效果 | 部署方式 | 费用 | 适用 |
|---|---|---|---|---|---|
| OpenAI text-embedding-ada-002 | 1536 | 一般 | 远程 API | 付费 | 通用、不折腾 |
| BGE (BAAI) | 768 / 1024 | 好 | 本地开源 | 免费 | 中文首选 |
| nomic-embed-text | 768 | 一般 | 本地(Ollama) | 免费 | 隐私优先、本地 |
| m3e-base | 768 | 好(专用) | 本地开源 | 免费 | 中文专用 |
各模型特点
OpenAI text-embedding-ada-002
- 特点:OpenAI 老牌嵌入模型,通用性好
- 优势:调 API 就行、多语言支持
- 劣势:付费(按 token 计)、中文效果不如国产、数据出境
- 使用:
from langchain_openai import OpenAIEmbeddings embeddings = OpenAIEmbeddings(model='text-embedding-ada-002')
BGE (BAAI)
- 特点:智源研究院开源,中文榜单常年前列
- 子型号:
bge-large-zh-v1.5(1024 维,效果好但慢)bge-small-zh-v1.5(512 维,轻量)bge-m3(多语言/多功能/多粒度,推荐)
- 优势:中文效果好、开源免费、可本地部署
- 使用(通过 Ollama 调用):
from langchain_community.embeddings import OllamaEmbeddings embeddings = OllamaEmbeddings(model='bge-m3:567m', base_url='http://localhost:11434')
nomic-embed-text
- 特点:Nomic 出品,主打本地部署
- 优势:免费、隐私好、Ollama 一行起
- 劣势:中文效果一般(英文优化)
- 使用:
embeddings = OllamaEmbeddings(model='nomic-embed-text', base_url='http://localhost:11434')
m3e-base
- 特点:Moka AI 出品,中文专用
- 优势:中文任务表现稳定、模型小(768 维)
- 劣势:多语言能力弱、英文不行
- 使用(HuggingFace):
from langchain_huggingface import HuggingFaceEmbeddings embeddings = HuggingFaceEmbeddings(model_name='moka-ai/m3e-base')
选型建议
| 你的情况 | 推荐 |
|---|---|
| 中文为主、本地部署 | BGE-m3 或 m3e-base |
| 要省事、不在意付费 | OpenAI ada-002 |
| 隐私优先、英文为主 | nomic-embed-text |
五、完整使用方法(参考项目 rag.py)
环境准备(新手必看)
运行下面的代码前,请先完成这三步:
# 1. 安装依赖
pip install langchain langchain-community langchain-chroma langchain-openai fastapi uvicorn pypdf
# 2. 拉取本地嵌入模型(Ollama 已安装并运行)
ollama pull bge-m3:567m
# 3. 配置 DeepSeek API Key(二选一)
# 方式 A:在代码里写死(仅本地测试)
# 方式 B:在系统环境变量里设 deepseek_api_key1 和 deepseek_base_url
# 如果选方式 A:在脚本最前面加上这两行,把 your-key 换成你的真实 key
import os
os.environ['deepseek_api_key1'] = 'your-key-here'
os.environ['deepseek_base_url'] = 'https://api.deepseek.com/v1'
端到端代码示例(基于本项目 rag.py)
# -*- coding: utf-8 -*-
import os
from fastapi import FastAPI
from langchain_community.document_loaders import PyPDFLoader # ① 加载
from langchain_text_splitters import RecursiveCharacterTextSplitter # ② 切分
from langchain_community.embeddings import OllamaEmbeddings # ③ 向量化
from langchain_chroma import Chroma # ④ 入库
from langchain_openai import ChatOpenAI # ⑧ LLM
from langchain.chains.retrieval_qa.base import RetrievalQA # ⑦ 拼接
app = FastAPI()
# ===== 离线阶段:构建知识库(启动时执行一次) =====
pdf_path = r"C:\path\to\your.pdf"
loader = PyPDFLoader(pdf_path) # ① 加载 PDF
documents = loader.load() # 得到 Document 列表(每页一个)
splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50) # ② 切分
chunks = splitter.split_documents(documents) # 切成小块
embeddings = OllamaEmbeddings( # ③ 嵌入模型
model='bge-m3:567m',
base_url='http://localhost:11434'
)
vectorstore = Chroma.from_documents( # ④ 入库
documents=chunks,
embedding=embeddings,
persist_directory='lx' # 持久化目录
)
retriever = vectorstore.as_retriever(search_kwargs={'k': 2}) # ⑥ 检索器(取 Top-2)
llm = ChatOpenAI( # ⑧ LLM(DeepSeek API)
model='deepseek-chat',
api_key=os.getenv('deepseek_api_key1'),
base_url=os.getenv('deepseek_base_url'),
temperature=0.7
)
qa_chain = RetrievalQA.from_chain_type( # ⑦+⑧ 问答链
llm=llm,
chain_type='stuff', # 检索结果拼成一整段塞 prompt
retriever=retriever,
return_source_documents=True # 返回来源
)
# ===== 在线阶段:问答接口 =====
@app.post('/ask')
async def ask(question: str):
result = qa_chain.invoke({'query': question}) # ⑤⑦⑧ 自动完成
return {
'answer': result['result'],
'sources': [doc.page_content for doc in result['source_documents']]
}
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=8000)
chain_type 的三种模式
| 模式 | 工作原理 | 适用 |
|---|---|---|
stuff | 直接把所有检索块拼成超长 prompt,一次性喂给 LLM | 块少、上下文短 |
map_reduce | 每块单独问 LLM 得到"分答案",再汇总成最终答案 | 块多、可并行 |
refine | 用第一块初始化答案,每读一块就迭代更新一次答案 | 要答案逐步精炼、长答案优先 |
六、常见面试题
基础题
Q1:什么是 RAG?为什么需要 RAG?
A:RAG = 检索增强生成。LLM 直接回答存在知识截断、幻觉、私有知识缺位三个问题;RAG 把私有知识向量化存库,问答时检索相关片段拼进 prompt,让 LLM 基于资料回答,从而无需微调就能接入新知识。
Q2:RAG 的两个阶段是什么?
A:离线阶段(文档加载→切分→向量化→入库)和在线阶段(问题向量化→检索→拼 prompt→LLM 生成)。离线只跑一次,在线每次问答都跑。
Q3:离线和在线用的 Embedding 模型能不能不一样?
A:不能。两边必须用同一个模型,因为相似度计算要求问题向量和文档向量在同一空间。换了模型,距离就没意义了。
Q4:chunk_size 和 chunk_overlap 怎么设?
A:
chunk_size:单块长度,太小丢上下文,太大检索精度降。中文常 300–800,英文 500–1000。chunk_overlap:块间重叠,保持上下文连贯,一般为 chunk_size 的 10–20%。- 关键:按语义边界(句号、换行)切,不要硬按字数切。
进阶题
Q5:FAISS 和 Chroma 有什么区别?怎么选?
A:FAISS 是高性能检索库,无服务端、无元数据、要手动 save/load;Chroma 是嵌入式数据库,自动持久化、支持元数据、开箱即用。原型/小项目选 Chroma,要极致性能选 FAISS。
Q6:相似度检索用哪种距离度量?
A:常用三种:
- 余弦相似度(Cosine):忽略向量长度,关注方向,最常用
- L2 距离(欧氏):关注绝对距离
- 内积(Inner Product):归一化后等价于余弦
- 选哪个看模型:BGE 等已归一化的用内积即可,OpenAI 用余弦。
Q7:为什么用 BGE 而不是 OpenAI ada-002?
A:BGE 中文效果好(中文榜单常年前列)、开源免费、可本地部署(隐私 + 不出境);OpenAI 中文一般、付费、数据出境。国内中文 RAG 场景 BGE/m3e 更合适。
Q8:怎么避免 LLM 幻觉?
A:
- Prompt 明确要求"只根据参考资料回答,没有就说不知道"
temperature调低(0.3 以下)- 检索 Top-K 取够、相关度阈值过滤
- 让 LLM 输出"来源片段",可追溯
Q9:retriever 的 k 设多少合适?
A:一般 2–5。太小漏信息,太大塞太多无关内容、token 超限且分散 LLM 注意。长文档建议先召回多再 rerank。
Q10:stuff / map_reduce / refine 三种 chain 怎么选?
A:
stuff:检索块少、加起来不超上下文窗口 → 首选map_reduce:块多、可并行 → 量大时refine:要答案逐步精炼 → 长答案质量优先
Q11:RAG 和微调(Fine-tuning)怎么选?
A:
- RAG:知识频繁变、要可追溯、数据量中等、低成本 → 优先 RAG
- 微调:要改模型风格/能力、知识稳定、数据量大 → 微调
- 也可结合:微调改能力,RAG 接知识
实战题
Q12:Chroma 报"sqlite 锁冲突"怎么办?
A:多个进程同时读 Chroma 的 sqlite 会锁。解决:单进程跑;或重启服务;或删 persist_directory 重建。
Q13:FAISS 加载时报 allow_dangerous_deserialization 是什么?
A:FAISS 用 pickle 持久化,加载别人的 .pkl 有安全风险。LangChain 强制要传 allow_dangerous_deserialization=True 才能加载,表示你信任这份文件。
Q14:检索召回的相关度不高怎么办?
A:
- 检查 Embedding 模型是否匹配场景(中文用 BGE/m3e)
- 调整 chunk_size,太大太小都不行
- 加 Rerank(重排)模型,如
BAAI/bge-reranker-v2-m3,先召回 Top-20 再重排取 Top-3 - 改用 Hybrid 检索:向量检索 + 关键词(BM25)检索融合
- Query 改写:把用户口语化问题改写成更利于检索的形式
Q15:长文档(几百页 PDF)怎么处理?
A:
- 切分时按章节/段落优先,保留元数据
- 用 Parent-Child Splitter:小块检索,大块返回上下文
- 加目录/摘要,先定位章节再细查
- 多级检索:先粗检索章节,再细检索段落
七、总结
RAG 的核心三件套
文档 ──Embedding 模型──▶ 向量 ──向量数据库──▶ 检索 ──LLM──▶ 回答
- Embedding 模型:决定向量化质量,中文推荐 BGE-m3 / m3e-base
- 向量数据库:决定存储和检索能力,本地开发用 Chroma,高性能用 FAISS,生产用 Milvus/Qdrant/Pinecone
- LLM:决定生成质量,国内推荐 DeepSeek、Qwen;国外 OpenAI、Claude
RAG 优势
| 维度 | 微调 | RAG |
|---|---|---|
| 成本 | 高(要训练) | 低(只建库) |
| 时效 | 低(重训才更新) | 高(换库即更新) |
| 可追溯 | 不行 | 行(来源可查) |
| 私有知识 | 要训练 | 直接入库 |
容易踩的坑
- ⚠️ 离线和在线必须用同一个 Embedding 模型
- ⚠️ 改了 chunk_size 要删旧库重建,否则新旧块混库
- ⚠️ Chroma 的
persist_directory多进程访问会锁冲突 - ⚠️ FAISS 的
load_local要传allow_dangerous_deserialization=True - ⚠️ 检索 Top-K 不要太大,否则 prompt 臃肿、LLM 注意力分散
- ⚠️ temperature 调低(0.3 以下)减少幻觉
本文档基于项目
rag.py实际代码整理,配套示例代码可直接运行。
更多推荐



所有评论(0)