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
提取纯文本+元数据

离线-② 文本分块
RecursiveCharacterTextSplitter
chunk_size / chunk_overlap

离线-③ 向量化模型
BGE-m3 / m3e / OpenAI
文本→向量

离线-④ 向量数据库
Chroma / FAISS / Milvus
向量+原文 入库持久化

用户问题

在线-① 转向量
同一个 Embedding 模型

在线-② 检索
Top-K 召回 + Rerank 重排

在线-③ 上下文构建
Prompt Template

在线-④ 生成(大模型)
Ollama / vLLM / 在线API

✅ 回答

离线四步详解

步骤名称做什么常用工具/模型
离线-①文档解析把 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、部署麻烦
在线 APIDeepSeek / OpenAI / Claude开箱即用、性能强数据出境、付费、限速

三、向量数据库对比

总览表

数据库特点部署形态适合场景维度限制持久化
Chroma轻量级,本地开发首选本地嵌入式原型/小项目/教学任意sqlite + parquet
FAISSFacebook 开源,高性能 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-0021536一般远程 API付费通用、不折腾
BGE (BAAI)768 / 1024好本地开源免费中文首选
nomic-embed-text768一般本地(Ollama)免费隐私优先、本地
m3e-base768好(专用)本地开源免费中文专用

各模型特点

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:

  1. Prompt 明确要求"只根据参考资料回答,没有就说不知道"
  2. temperature 调低(0.3 以下)
  3. 检索 Top-K 取够、相关度阈值过滤
  4. 让 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:

  1. 检查 Embedding 模型是否匹配场景(中文用 BGE/m3e)
  2. 调整 chunk_size,太大太小都不行
  3. 加 Rerank(重排)模型,如 BAAI/bge-reranker-v2-m3,先召回 Top-20 再重排取 Top-3
  4. 改用 Hybrid 检索:向量检索 + 关键词(BM25)检索融合
  5. Query 改写:把用户口语化问题改写成更利于检索的形式

Q15:长文档(几百页 PDF)怎么处理?
A:

  1. 切分时按章节/段落优先,保留元数据
  2. 用 Parent-Child Splitter:小块检索,大块返回上下文
  3. 加目录/摘要,先定位章节再细查
  4. 多级检索:先粗检索章节,再细检索段落

七、总结

RAG 的核心三件套

文档 ──Embedding 模型──▶ 向量 ──向量数据库──▶ 检索 ──LLM──▶ 回答
  1. Embedding 模型:决定向量化质量,中文推荐 BGE-m3 / m3e-base
  2. 向量数据库:决定存储和检索能力,本地开发用 Chroma,高性能用 FAISS,生产用 Milvus/Qdrant/Pinecone
  3. LLM:决定生成质量,国内推荐 DeepSeek、Qwen;国外 OpenAI、Claude

RAG 优势

维度微调RAG
成本高(要训练)低(只建库)
时效低(重训才更新)高(换库即更新)
可追溯不行行(来源可查)
私有知识要训练直接入库

容易踩的坑

  1. ⚠️ 离线和在线必须用同一个 Embedding 模型
  2. ⚠️ 改了 chunk_size 要删旧库重建,否则新旧块混库
  3. ⚠️ Chroma 的 persist_directory 多进程访问会锁冲突
  4. ⚠️ FAISS 的 load_local 要传 allow_dangerous_deserialization=True
  5. ⚠️ 检索 Top-K 不要太大,否则 prompt 臃肿、LLM 注意力分散
  6. ⚠️ temperature 调低(0.3 以下)减少幻觉

本文档基于项目 rag.py 实际代码整理,配套示例代码可直接运行。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐