RAG 实战指南:让大模型拥有最新知识

什么是 RAG

RAG(Retrieval-Augmented Generation,检索增强生成)是一种将外部知识检索与大语言模型(LLM)生成能力结合的技术架构。它的核心思想很简单:让 AI 在回答问题前,先去知识库中查找相关信息,再基于检索到的内容生成答案

为什么需要 RAG

大语言模型虽然强大,但存在几个固有限制:

问题 说明
知识过时 模型的训练数据有截止日期,无法获取最新信息
幻觉问题 模型可能生成看似合理但实际错误的内容
私有知识 模型无法访问你的内部文档、业务数据
成本高昂 微调模型成本高,且需要重新训练

RAG 通过检索 + 生成的方式,有效缓解了这些问题。

RAG 工作原理

RAG 的流程可以分为以下几个步骤:

1
用户问题 → 检索 → 增强 → 生成 → 回答

1. 文档处理(Ingection)

将原始文档切分成小块(Chunk),然后通过 Embedding 模型转换为向量,存入向量数据库。

1
2
3
4
5
6
7
8
# 文档切分示例
text = "长文档内容..."
chunks = text_splitter.split_text(text)

# 向量化并存储
for chunk in chunks:
vector = embedding_model.encode(chunk)
vector_db.add(vector, chunk)

2. 检索阶段(Retrieval)

当用户提问时,将问题本身也转换为向量,在向量数据库中进行相似度搜索,找到最相关的文档块。

1
2
3
4
5
6
7
8
# 用户提问
query = "公司的年假政策是什么?"

# 转成向量
query_vector = embedding_model.encode(query)

# 相似度检索
results = vector_db.search(query_vector, top_k=5)

3. 增强阶段(Augmentation)

将检索到的相关文档块与用户问题组合,形成一个「上下文 + 问题」的提示词。

1
2
3
4
5
6
7
8
prompt = f"""
根据以下上下文回答问题。如果上下文中没有相关信息,请如实说明。

上下文:
{retrieved_docs}

问题:{user_query}
"""

4. 生成阶段(Generation)

将增强后的提示词发送给 LLM,由模型生成最终回答。

1
response = llm.chat(prompt)

RAG 关键技术点

1. 文档切分(Chunking)

切分策略直接影响检索效果:

  • 固定大小切分:简单但可能切断语义
  • 句子级别切分:保持完整性但块太小
  • 递归切分:按段落/句子逐层切,更灵活
  • 语义切分:用模型判断语义边界,效果最好
1
2
3
4
5
6
7
8
9
# 固定大小切分
from langchain.text_splitter import CharacterTextSplitter

splitter = CharacterTextSplitter(
chunk_size=500,
chunk_overlap=50, # 重叠部分,保持上下文连贯
separator="\n"
)
chunks = splitter.split_text(document)

Chunk 大小选择建议:

  • 通用问答:300-500 tokens
  • 摘要任务:1000-2000 tokens
  • 代码检索:200-1000 tokens

2. 向量嵌入(Embedding)

选择合适的 Embedding 模型:

模型 特点 适用场景
OpenAI text-embedding-ada-002 效果好,收费 生产环境
BGE 开源效果好 私有部署
M3E 中文优化,免费 中文场景
Cohere 多语言支持好 多语言场景
1
2
3
4
from sentence_transformers import SentenceTransformer

model = SentenceTransformer('moka-ai/m3e-base')
vector = model.encode("要嵌入的文本")

3. 向量数据库

主流向量数据库对比:

数据库 特点 适用规模
Milvus 功能完善,支持混合检索 中大规模
Chroma 轻量级,易上手 小规模/实验
Qdrant 性能好,支持过滤 生产环境
Pinecone 云原生,无需运维 大规模
Weaviate 支持多模态 多模态场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import chromadb

client = chromadb.Client()
collection = client.create_collection("docs")

# 添加向量
collection.add(
embeddings=[vector],
documents=["文档内容"],
ids=["doc1"]
)

# 检索
results = collection.query(
query_embeddings=[query_vector],
n_results=5
)

4. 检索策略

基本相似度检索:

1
results = vector_db.similarity_search(query, k=5)

带分数阈值的检索:

1
2
3
results = vector_db.similarity_search_with_score(query, k=5)
# 只返回相似度 > 0.8 的结果
filtered = [r for r, score in results if score > 0.8]

混合检索(关键词 + 向量):

1
2
3
4
5
6
# 同时使用 BM25 关键词检索和向量检索
hybrid_results = vector_db.hybrid_search(
query,
vector_weight=0.7, # 向量权重
keyword_weight=0.3 # 关键词权重
)

5. 重排序(Reranking)

初步检索后,用重排序模型进一步优化结果顺序:

1
2
3
4
5
6
7
8
from sentence_transformers import CrossEncoder

reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')

# 对初步结果重排序
reranked = reranker.predict([
(query, doc) for doc in initial_results
])

RAG 实战框架

LangChain

最流行的 RAG 开发框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from langchain.document_loaders import PDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA

# 1. 加载文档
loader = PDFLoader("document.pdf")
documents = loader.load()

# 2. 切分文档
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_documents(documents)

# 3. 创建向量数据库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=OpenAIEmbeddings()
)

# 4. 创建检索链
qa_chain = RetrievalQA.from_chain_type(
llm=ChatOpenAI(model="gpt-4"),
retriever=vectorstore.as_retriever()
)

# 5. 问答
result = qa_chain({"query": "文档的主要内容是什么?"})

LlamaIndex

更专注于索引和检索的框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from llama_index import VectorStoreIndex, SimpleDirectoryReader
from llama_index.query_engine import RetrieverQueryEngine
from llama_index.retrievers import VectorIndexRetriever

# 加载文档
documents = SimpleDirectoryReader("./data").load_data()

# 构建索引
index = VectorStoreIndex.from_documents(documents)

# 配置检索器
retriever = VectorIndexRetriever(
index=index,
similarity_top_k=5
)

# 创建查询引擎
query_engine = RetrieverQueryEngine(retriever=retriever)

# 查询
response = query_engine.query("你的问题")

RAG 优化技巧

1. 提升检索质量

使用更好的 Embedding 模型:

1
2
3
4
# 中文场景推荐 BGE 或 M3E
embedding = HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-small-zh-v1.5"
)

添加元数据过滤:

1
2
3
4
5
# 支持按时间、类别等过滤
vector_db.search(
query_vector,
filter={"category": "技术文档", "date": "2024"}
)

2. 优化生成质量

Few-shot 提示:

1
2
3
4
5
6
7
8
9
10
11
12
13
prompt = """
你是一个技术文档助手。请根据上下文回答问题。

示例:
问:如何重置密码?
答:进入设置页面,点击「账户安全」,选择「重置密码」。

上下文:
{context}

问:{question}
答:
"""

链式思考(Chain of Thought):

1
2
3
4
5
6
7
8
9
10
prompt = """
请分步骤分析以下问题:

1. 首先理解问题的关键点
2. 从上下文中找到相关信息
3. 基于信息给出回答

上下文:{context}
问题:{question}
"""

3. 处理长上下文

上下文压缩:

1
2
3
4
5
6
7
8
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor

compressor = LLMChainExtractor.from_llm(llm)
compressor_retriever = ContextualCompressionRetriever(
base_retriever=base_retriever,
document_compressor=compressor
)

4. 多跳推理

处理需要组合多个知识点的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 分解问题
sub_questions = [
"A公司的CEO是谁?",
"A公司CEO的年薪是多少?"
]

# 分别检索
answers = [retriever.search(q) for q in sub_questions]

# 合并答案
final_prompt = f"""
基于以下信息回答:{answers}
原始问题:{original_question}
"""

RAG 的局限性

RAG 不是银弹,仍然存在一些局限:

  1. 检索质量依赖文档质量:脏乱差的数据无法带来好结果
  2. 复杂推理仍有局限:多跳推理、逻辑推演仍是难题
  3. 实时性 vs 成本的权衡:频繁更新索引有成本
  4. 上下文长度限制:无法一次性处理超长文档

RAG vs 微调

维度 RAG 微调
知识更新 实时,更新索引即可 需要重新训练
成本 低,无需训练 高,GPU + 训练时间
可解释性 高,可查看检索来源 低,隐含在模型中
定制程度 中等
幻觉问题 较少 可能继承模型问题
适用场景 知识库问答 风格/领域适配

建议:

  • 知识库问答 → RAG
  • 领域特定风格 → 微调
  • 两者结合 → 最佳效果

总结

RAG 是当前构建知识库问答系统的主流方案,它通过「检索 + 生成」的范式,让大模型能够访问最新、最准确的外部知识。

掌握 RAG 的核心组件(文档处理、嵌入、向量数据库、提示工程),理解常见的优化技巧(混合检索、重排序、上下文压缩),就能构建出效果不错的知识库问答系统。

在实际项目中,建议从简单方案开始迭代,根据效果逐步引入更复杂的技术。


有问题或想法,欢迎留言交流!