前言:大模型的"知识困境"
大模型很强大,但有几个致命问题:
问题 1:知识有截止日期
用户: 2024 年诺贝尔物理学奖颁给了谁?
GPT-4 (2023年训练): 抱歉,我的知识截止到 2023 年 4 月,
无法回答 2024 年的问题。问题 2:不知道你的私有数据
用户: 我们公司的报销流程是什么?
大模型: 一般公司的报销流程是... [给了一个通用答案]
(并不知道你们公司的具体规定)问题 3:会"幻觉"——一本正经地胡说八道
用户: 《三体》作者刘慈欣的代表作有哪些?
大模型: 刘慈欣的代表作包括《三体》《流浪地球》《球状闪电》
《北京折叠》... ← 这是郝景芳的作品!RAG(Retrieval-Augmented Generation,检索增强生成) 就是解决这些问题的方案。
graph LR
subgraph 传统大模型
Q1[用户问题] --> LLM1[大模型]
LLM1 --> A1[可能过时/错误的回答]
end
subgraph RAG
Q2[用户问题] --> R[检索器]
R --> KB[(知识库)]
KB --> R
R --> Context[相关文档]
Context --> LLM2[大模型]
Q2 --> LLM2
LLM2 --> A2[基于证据的回答]
end
style A1 fill:#ff6b6b
style A2 fill:#4ecdc4
一、RAG 的核心思想
1.1 一句话解释
RAG = 开卷考试
- 闭卷考试:只能靠记忆(纯大模型)
- 开卷考试:可以翻书查资料(RAG)
graph TB
subgraph RAG流程
Q[用户问题] --> Embed1[问题向量化]
Embed1 --> Search[向量检索]
Search --> VDB[(向量数据库)]
VDB --> Docs[Top-K 相关文档]
Docs --> Prompt[构造 Prompt]
Q --> Prompt
Prompt --> LLM[大模型生成]
LLM --> Answer[最终回答]
end
1.2 RAG 的两个核心步骤
Step 1: Retrieval(检索)
- 把用户问题转换为向量
- 在知识库中找到最相关的文档
- 返回 Top-K 个结果
Step 2: Generation(生成)
- 把检索到的文档作为上下文
- 让大模型基于上下文回答问题
1.3 为什么 RAG 有效?
graph TB
subgraph 信息来源
A[大模型参数中的知识
可能过时/不准确] B[检索到的文档
最新/准确/有出处] end A --> C[结合] B --> C C --> D[更准确的回答] style B fill:#4ecdc4 style D fill:#4ecdc4
可能过时/不准确] B[检索到的文档
最新/准确/有出处] end A --> C[结合] B --> C C --> D[更准确的回答] style B fill:#4ecdc4 style D fill:#4ecdc4
RAG 的优势:
- 知识可更新:只需更新知识库,无需重新训练模型
- 减少幻觉:回答有据可查
- 可解释:可以展示引用来源
- 成本低:比微调模型便宜得多
二、向量检索:RAG 的基础
2.1 文本向量化(Embedding)
把文本转换为向量,语义相近的文本向量距离更近。
from sentence_transformers import SentenceTransformer
# 加载 Embedding 模型
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
# 文本向量化
texts = [
"机器学习是人工智能的一个分支",
"深度学习使用神经网络",
"今天天气很好",
]
embeddings = model.encode(texts)
print(f"向量维度: {embeddings.shape}") # (3, 1024)
# 计算相似度
from sklearn.metrics.pairwise import cosine_similarity
sim_matrix = cosine_similarity(embeddings)
print("相似度矩阵:")
print(sim_matrix)
# [[1.0, 0.82, 0.15],
# [0.82, 1.0, 0.12],
# [0.15, 0.12, 1.0]]
# 前两个文本语义相近,第三个不相关2.2 常用 Embedding 模型
| 模型 | 维度 | 语言 | 特点 |
|---|---|---|---|
| BGE-large-zh | 1024 | 中文 | 中文最佳 |
| BGE-large-en | 1024 | 英文 | 英文优秀 |
| M3E | 768 | 中文 | 开源中文 |
| text-embedding-3-large | 3072 | 多语言 | OpenAI 最新 |
| Cohere embed-v3 | 1024 | 多语言 | 商业方案 |
| GTE-large | 1024 | 多语言 | 阿里出品 |
2.3 向量数据库
存储和检索向量需要专门的数据库:
mindmap
root((向量数据库))
开源
Chroma
轻量级
适合入门
Milvus
高性能
生产级
Qdrant
Rust实现
性能好
Weaviate
GraphQL接口
多模态
FAISS
Meta出品
纯检索库
云服务
Pinecone
Zilliz Cloud
阿里云向量检索
2.4 Chroma 快速入门
import chromadb
from chromadb.utils import embedding_functions
# 创建客户端
client = chromadb.Client()
# 使用 BGE 作为 embedding 函数
embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name="BAAI/bge-large-zh-v1.5"
)
# 创建集合
collection = client.create_collection(
name="my_knowledge_base",
embedding_function=embedding_fn,
)
# 添加文档
documents = [
"机器学习是人工智能的一个重要分支,它使计算机能够从数据中学习。",
"深度学习是机器学习的子领域,使用多层神经网络。",
"自然语言处理让计算机能够理解和生成人类语言。",
"计算机视觉使机器能够从图像和视频中获取信息。",
]
collection.add(
documents=documents,
ids=[f"doc_{i}" for i in range(len(documents))],
metadatas=[{"source": "ai_textbook"} for _ in documents],
)
# 检索
results = collection.query(
query_texts=["什么是深度学习?"],
n_results=2,
)
print("检索结果:")
for doc, distance in zip(results['documents'][0], results['distances'][0]):
print(f" [{distance:.4f}] {doc[:50]}...")三、构建完整的 RAG 系统
3.1 RAG 系统架构
flowchart TB
subgraph 离线索引
D1[原始文档] --> D2[文档分块]
D2 --> D3[向量化]
D3 --> D4[(向量数据库)]
end
subgraph 在线查询
Q[用户问题] --> Q1[问题向量化]
Q1 --> Q2[向量检索]
Q2 --> D4
D4 --> Q3[Top-K 文档]
Q3 --> Q4[构造 Prompt]
Q --> Q4
Q4 --> Q5[LLM 生成]
Q5 --> A[回答]
end
3.2 文档分块(Chunking)
文档太长无法直接处理,需要分块:
from langchain.text_splitter import RecursiveCharacterTextSplitter
def chunk_documents(documents: list[str], chunk_size=500, chunk_overlap=50):
"""文档分块"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""],
)
chunks = []
for doc in documents:
chunks.extend(splitter.split_text(doc))
return chunks
# 分块策略对比
strategies = {
"固定大小": {"chunk_size": 500, "chunk_overlap": 50},
"小块多重叠": {"chunk_size": 200, "chunk_overlap": 100},
"大块少重叠": {"chunk_size": 1000, "chunk_overlap": 50},
}
# 选择建议:
# - 精确检索:小块 (200-500)
# - 保持上下文:大块 (500-1000)
# - 重叠:通常 10-20% 的 chunk_size3.3 分块策略对比
graph TB
subgraph 分块策略
A["固定大小分块
简单但可能切断语义"] B["语义分块
保持完整性但复杂"] C["递归分块
平衡方案,推荐"] D["文档结构分块
按标题/段落切分"] end
简单但可能切断语义"] B["语义分块
保持完整性但复杂"] C["递归分块
平衡方案,推荐"] D["文档结构分块
按标题/段落切分"] end
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 固定大小 | 简单均匀 | 可能切断句子 | 通用 |
| 句子分块 | 语义完整 | 长度不均 | 精确检索 |
| 递归分块 | 灵活平衡 | 需要调参 | 推荐默认 |
| 结构分块 | 保持逻辑 | 依赖格式 | 结构化文档 |
3.4 完整 RAG 实现
import os
from typing import List, Dict
import chromadb
from chromadb.utils import embedding_functions
from openai import OpenAI
class SimpleRAG:
"""简单的 RAG 系统"""
def __init__(
self,
embedding_model: str = "BAAI/bge-large-zh-v1.5",
llm_model: str = "gpt-3.5-turbo",
collection_name: str = "rag_collection",
):
# Embedding 模型
self.embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name=embedding_model
)
# 向量数据库
self.client = chromadb.PersistentClient(path="./chroma_db")
self.collection = self.client.get_or_create_collection(
name=collection_name,
embedding_function=self.embedding_fn,
)
# LLM 客户端
self.llm_client = OpenAI()
self.llm_model = llm_model
def add_documents(
self,
documents: List[str],
metadatas: List[Dict] = None,
chunk_size: int = 500,
):
"""添加文档到知识库"""
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 分块
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=50,
)
all_chunks = []
all_metadatas = []
for i, doc in enumerate(documents):
chunks = splitter.split_text(doc)
all_chunks.extend(chunks)
# 元数据
meta = metadatas[i] if metadatas else {}
all_metadatas.extend([{**meta, "chunk_index": j} for j in range(len(chunks))])
# 添加到向量数据库
self.collection.add(
documents=all_chunks,
ids=[f"chunk_{i}" for i in range(len(all_chunks))],
metadatas=all_metadatas,
)
print(f"添加了 {len(all_chunks)} 个文档块")
def retrieve(self, query: str, top_k: int = 3) -> List[str]:
"""检索相关文档"""
results = self.collection.query(
query_texts=[query],
n_results=top_k,
)
return results['documents'][0]
def generate(self, query: str, context: List[str]) -> str:
"""基于上下文生成回答"""
# 构造 Prompt
context_str = "\n\n".join([f"[文档 {i+1}]: {doc}" for i, doc in enumerate(context)])
prompt = f"""请基于以下参考文档回答用户的问题。如果文档中没有相关信息,请说明。
参考文档:
{context_str}
用户问题:{query}
回答:"""
# 调用 LLM
response = self.llm_client.chat.completions.create(
model=self.llm_model,
messages=[
{"role": "system", "content": "你是一个helpful assistant,基于提供的文档回答问题。"},
{"role": "user", "content": prompt},
],
temperature=0.7,
)
return response.choices[0].message.content
def query(self, question: str, top_k: int = 3) -> Dict:
"""完整的 RAG 查询"""
# 1. 检索
context = self.retrieve(question, top_k)
# 2. 生成
answer = self.generate(question, context)
return {
"question": question,
"answer": answer,
"sources": context,
}
# 使用示例
if __name__ == "__main__":
# 创建 RAG 系统
rag = SimpleRAG()
# 添加文档
documents = [
"""
机器学习(Machine Learning)是人工智能的一个分支,它使计算机系统能够从数据中学习和改进,
而无需进行明确的编程。机器学习算法通过分析大量数据来识别模式,并使用这些模式来做出决策或预测。
主要的机器学习类型包括:监督学习、无监督学习和强化学习。
""",
"""
深度学习(Deep Learning)是机器学习的一个子领域,它使用多层神经网络来学习数据的层次表示。
深度学习在图像识别、自然语言处理和语音识别等领域取得了突破性的成果。
常见的深度学习架构包括:卷积神经网络(CNN)、循环神经网络(RNN)和Transformer。
""",
"""
大型语言模型(LLM)是一种基于深度学习的自然语言处理模型,通过在海量文本数据上进行预训练,
学习语言的统计规律和语义知识。代表性的大型语言模型包括 GPT 系列、BERT、LLaMA 等。
LLM 可以执行多种任务,如文本生成、问答、翻译、摘要等。
""",
]
rag.add_documents(documents, metadatas=[{"source": "ai_intro"} for _ in documents])
# 查询
result = rag.query("什么是深度学习?它和机器学习有什么关系?")
print("问题:", result["question"])
print("\n回答:", result["answer"])
print("\n来源文档:")
for i, source in enumerate(result["sources"]):
print(f" [{i+1}] {source[:100]}...")四、高级 RAG 技术
4.1 RAG 优化全景
mindmap
root((RAG 优化))
检索优化
Query 改写
混合检索
重排序 Rerank
多路召回
分块优化
语义分块
层级分块
小块检索大块返回
生成优化
Prompt 工程
Lost in Middle
压缩上下文
架构优化
Self-RAG
CRAG
GraphRAG
4.2 Query 改写(Query Rewriting)
用户的问题可能不适合直接检索:
def rewrite_query(query: str, llm_client) -> List[str]:
"""改写用户问题,生成多个检索 query"""
prompt = f"""请将以下用户问题改写成 3 个更适合检索的查询语句。
要求:
1. 保持原意
2. 使用不同的表达方式
3. 可以拆分成子问题
用户问题:{query}
改写后的查询(每行一个):"""
response = llm_client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
)
queries = response.choices[0].message.content.strip().split("\n")
return [q.strip() for q in queries if q.strip()]
# 示例
original = "深度学习和传统机器学习有什么区别,各自适用于什么场景?"
rewritten = rewrite_query(original, llm_client)
# 可能的改写结果:
# 1. "深度学习与机器学习的区别"
# 2. "深度学习适用场景"
# 3. "传统机器学习应用领域"4.3 混合检索(Hybrid Search)
结合向量检索和关键词检索:
from rank_bm25 import BM25Okapi
import numpy as np
class HybridRetriever:
"""混合检索:向量 + BM25"""
def __init__(self, documents: List[str], embedding_model):
self.documents = documents
self.embedding_model = embedding_model
# 向量索引
self.embeddings = embedding_model.encode(documents)
# BM25 索引
tokenized = [doc.split() for doc in documents]
self.bm25 = BM25Okapi(tokenized)
def search(
self,
query: str,
top_k: int = 5,
vector_weight: float = 0.5,
) -> List[tuple]:
"""混合检索"""
# 向量检索
query_embedding = self.embedding_model.encode([query])[0]
vector_scores = np.dot(self.embeddings, query_embedding)
# BM25 检索
bm25_scores = self.bm25.get_scores(query.split())
# 归一化
vector_scores = (vector_scores - vector_scores.min()) / (vector_scores.max() - vector_scores.min() + 1e-6)
bm25_scores = (bm25_scores - bm25_scores.min()) / (bm25_scores.max() - bm25_scores.min() + 1e-6)
# 加权融合
final_scores = vector_weight * vector_scores + (1 - vector_weight) * bm25_scores
# 排序
top_indices = np.argsort(final_scores)[::-1][:top_k]
return [(self.documents[i], final_scores[i]) for i in top_indices]4.4 重排序(Reranking)
用更精确的模型对初步检索结果重新排序:
from sentence_transformers import CrossEncoder
class Reranker:
"""重排序器"""
def __init__(self, model_name: str = "BAAI/bge-reranker-large"):
self.model = CrossEncoder(model_name)
def rerank(
self,
query: str,
documents: List[str],
top_k: int = 3,
) -> List[tuple]:
"""重新排序"""
# 构造 query-document 对
pairs = [[query, doc] for doc in documents]
# 计算相关性分数
scores = self.model.predict(pairs)
# 排序
sorted_indices = np.argsort(scores)[::-1][:top_k]
return [(documents[i], scores[i]) for i in sorted_indices]
# 使用
reranker = Reranker()
# 初步检索:召回 20 个
initial_results = retriever.search(query, top_k=20)
# 重排序:精选 5 个
final_results = reranker.rerank(
query,
[doc for doc, _ in initial_results],
top_k=5
)4.5 Self-RAG:自我反思的 RAG
flowchart TB
Q[用户问题] --> R[检索文档]
R --> Judge1{需要检索吗?}
Judge1 -->|不需要| Direct[直接回答]
Judge1 -->|需要| Gen[生成回答]
Gen --> Judge2{回答相关吗?}
Judge2 -->|不相关| R
Judge2 -->|相关| Judge3{有支撑吗?}
Judge3 -->|无支撑| R
Judge3 -->|有支撑| Output[输出回答]
class SelfRAG:
"""Self-RAG:带自我反思的 RAG"""
def __init__(self, retriever, llm_client):
self.retriever = retriever
self.llm = llm_client
def needs_retrieval(self, query: str) -> bool:
"""判断是否需要检索"""
prompt = f"""判断以下问题是否需要外部知识来回答。
问题:{query}
只回答 "是" 或 "否":"""
response = self.llm.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return "是" in response.choices[0].message.content
def is_relevant(self, query: str, document: str) -> bool:
"""判断文档是否相关"""
prompt = f"""判断以下文档是否与问题相关。
问题:{query}
文档:{document}
只回答 "相关" 或 "不相关":"""
response = self.llm.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return "相关" in response.choices[0].message.content
def is_supported(self, answer: str, documents: List[str]) -> bool:
"""判断回答是否有文档支撑"""
docs_str = "\n".join(documents)
prompt = f"""判断以下回答是否有文档支撑。
文档:{docs_str}
回答:{answer}
只回答 "有支撑" 或 "无支撑":"""
response = self.llm.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return "有支撑" in response.choices[0].message.content
def query(self, question: str, max_iterations: int = 3) -> str:
"""Self-RAG 查询"""
# 判断是否需要检索
if not self.needs_retrieval(question):
return self.generate_direct(question)
for _ in range(max_iterations):
# 检索
documents = self.retriever.retrieve(question)
# 过滤不相关文档
relevant_docs = [doc for doc in documents if self.is_relevant(question, doc)]
if not relevant_docs:
continue
# 生成回答
answer = self.generate_with_context(question, relevant_docs)
# 验证支撑
if self.is_supported(answer, relevant_docs):
return answer
return "抱歉,我无法找到足够的信息来回答这个问题。"五、使用 LangChain 构建 RAG
5.1 LangChain RAG 快速入门
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
def build_rag_with_langchain():
"""使用 LangChain 构建 RAG"""
# 1. 加载文档
loader = PyPDFLoader("document.pdf")
documents = loader.load()
# 2. 分块
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
)
chunks = splitter.split_documents(documents)
# 3. 向量化和存储
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5"
)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db",
)
# 4. 创建检索器
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3},
)
# 5. 创建 LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
# 6. 自定义 Prompt
prompt_template = """请基于以下上下文回答问题。如果上下文中没有相关信息,请说明。
上下文:
{context}
问题:{question}
回答:"""
prompt = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"],
)
# 7. 创建 QA Chain
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": prompt},
)
return qa_chain
# 使用
qa_chain = build_rag_with_langchain()
result = qa_chain.invoke({"query": "文档的主要内容是什么?"})
print("回答:", result["result"])
print("\n来源文档:")
for doc in result["source_documents"]:
print(f" - {doc.page_content[:100]}...")5.2 LangChain + 高级检索
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
def build_advanced_retriever(documents, embeddings, llm):
"""构建高级检索器"""
# 向量检索器
vectorstore = Chroma.from_documents(documents, embeddings)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# BM25 检索器
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 5
# 混合检索器
ensemble_retriever = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.5, 0.5],
)
# 上下文压缩(提取关键信息)
compressor = LLMChainExtractor.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=ensemble_retriever,
)
return compression_retriever5.3 对话式 RAG
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
def build_conversational_rag(vectorstore, llm):
"""构建对话式 RAG"""
# 记忆
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="answer",
)
# 对话式检索链
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(),
memory=memory,
return_source_documents=True,
)
return qa_chain
# 使用
qa_chain = build_conversational_rag(vectorstore, llm)
# 多轮对话
result1 = qa_chain.invoke({"question": "什么是机器学习?"})
print("回答1:", result1["answer"])
result2 = qa_chain.invoke({"question": "它和深度学习有什么关系?"}) # 会理解"它"指机器学习
print("回答2:", result2["answer"])六、RAG 评估
6.1 评估指标
mindmap
root((RAG 评估))
检索评估
召回率 Recall
准确率 Precision
MRR
NDCG
生成评估
忠实度 Faithfulness
相关性 Relevance
有害性 Harmfulness
端到端评估
Answer Correctness
Context Relevancy
6.2 使用 RAGAS 评估
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
from datasets import Dataset
def evaluate_rag(questions, ground_truths, rag_system):
"""评估 RAG 系统"""
# 收集预测结果
answers = []
contexts = []
for q in questions:
result = rag_system.query(q)
answers.append(result["answer"])
contexts.append(result["sources"])
# 构建数据集
data = {
"question": questions,
"answer": answers,
"contexts": contexts,
"ground_truth": ground_truths,
}
dataset = Dataset.from_dict(data)
# 评估
results = evaluate(
dataset,
metrics=[
faithfulness, # 回答是否忠于上下文
answer_relevancy, # 回答是否相关
context_precision, # 检索精度
context_recall, # 检索召回
],
)
return results
# 使用
questions = [
"什么是机器学习?",
"深度学习有哪些应用?",
]
ground_truths = [
"机器学习是人工智能的一个分支...",
"深度学习广泛应用于图像识别、NLP...",
]
results = evaluate_rag(questions, ground_truths, rag_system)
print(results)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88, ...}6.3 常见问题诊断
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 检索不到相关文档 | Embedding 模型不匹配 | 换用更好的 Embedding |
| 检索到但不相关 | 分块太大/太小 | 调整分块策略 |
| 回答不忠实 | Prompt 设计问题 | 强调"基于文档回答" |
| 回答太泛泛 | 上下文不够具体 | 增加 top_k 或改进分块 |
| 速度太慢 | 检索/生成效率低 | 使用缓存、优化索引 |
七、生产环境最佳实践
7.1 完整 RAG 服务架构
flowchart TB
subgraph 客户端
Web[Web 应用]
API[API 调用]
end
subgraph 服务层
Gateway[API Gateway]
RAGService[RAG 服务]
Cache[Redis 缓存]
end
subgraph 存储层
VDB[(向量数据库
Milvus)] DocStore[(文档存储
S3/MinIO)] MetaDB[(元数据
PostgreSQL)] end subgraph 模型层 Embedding[Embedding 服务] LLM[LLM 服务
vLLM] Reranker[Reranker 服务] end Web --> Gateway API --> Gateway Gateway --> RAGService RAGService --> Cache RAGService --> VDB RAGService --> DocStore RAGService --> MetaDB RAGService --> Embedding RAGService --> LLM RAGService --> Reranker
Milvus)] DocStore[(文档存储
S3/MinIO)] MetaDB[(元数据
PostgreSQL)] end subgraph 模型层 Embedding[Embedding 服务] LLM[LLM 服务
vLLM] Reranker[Reranker 服务] end Web --> Gateway API --> Gateway Gateway --> RAGService RAGService --> Cache RAGService --> VDB RAGService --> DocStore RAGService --> MetaDB RAGService --> Embedding RAGService --> LLM RAGService --> Reranker
7.2 FastAPI RAG 服务
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import redis
import hashlib
import json
app = FastAPI()
# Redis 缓存
redis_client = redis.Redis(host='localhost', port=6379, db=0)
class QueryRequest(BaseModel):
question: str
top_k: int = 3
use_cache: bool = True
class QueryResponse(BaseModel):
answer: str
sources: List[str]
cached: bool = False
def get_cache_key(question: str) -> str:
"""生成缓存键"""
return f"rag:{hashlib.md5(question.encode()).hexdigest()}"
@app.post("/query", response_model=QueryResponse)
async def query(request: QueryRequest):
"""RAG 查询接口"""
# 检查缓存
if request.use_cache:
cache_key = get_cache_key(request.question)
cached = redis_client.get(cache_key)
if cached:
data = json.loads(cached)
return QueryResponse(**data, cached=True)
try:
# RAG 查询
result = rag_system.query(
request.question,
top_k=request.top_k,
)
response = QueryResponse(
answer=result["answer"],
sources=result["sources"],
)
# 写入缓存
if request.use_cache:
redis_client.setex(
cache_key,
3600, # 1 小时过期
json.dumps(response.dict()),
)
return response
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/index")
async def index_documents(documents: List[str]):
"""索引文档接口"""
try:
rag_system.add_documents(documents)
return {"status": "success", "count": len(documents)}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health")
async def health_check():
"""健康检查"""
return {"status": "healthy"}7.3 性能优化清单
# 1. Embedding 缓存
from functools import lru_cache
@lru_cache(maxsize=10000)
def get_embedding_cached(text: str) -> list:
return embedding_model.encode(text).tolist()
# 2. 批量处理
def batch_embed(texts: List[str], batch_size: int = 32):
embeddings = []
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
embeddings.extend(embedding_model.encode(batch))
return embeddings
# 3. 异步检索
import asyncio
async def async_retrieve(queries: List[str]):
tasks = [retriever.aretrieve(q) for q in queries]
return await asyncio.gather(*tasks)
# 4. 流式输出
async def stream_answer(question: str):
context = retriever.retrieve(question)
async for chunk in llm.astream(
prompt.format(context=context, question=question)
):
yield chunk八、总结
RAG 核心要点
mindmap
root((RAG))
核心组件
Embedding模型
向量数据库
LLM
关键流程
文档分块
向量检索
Prompt构造
生成回答
优化技术
Query改写
混合检索
Reranking
Self-RAG
评估指标
检索质量
生成质量
端到端效果
关键 Takeaway
- RAG = 检索 + 生成:让大模型拥有外部知识
- 分块策略很重要:影响检索质量和上下文完整性
- 混合检索效果更好:向量 + BM25 互补
- Reranking 提升精度:粗排 + 精排两阶段
- 评估驱动优化:用 RAGAS 等工具量化效果
- 生产环境要考虑:缓存、异步、监控
技术选型建议
| 组件 | 推荐 | 备选 |
|---|---|---|
| Embedding | BGE-large | GTE, M3E |
| 向量库 | Milvus (生产) | Chroma (开发) |
| 框架 | LangChain | LlamaIndex |
| Reranker | BGE-reranker | Cohere |
| 评估 | RAGAS | - |
下一步学习
- [ ] Agent:让大模型自己行动
- [ ] Function Calling:工具调用
- [ ] 多模态 RAG:图片、视频
参考资料
- RAG 原论文 - Retrieval-Augmented Generation
- Self-RAG 论文 - Self-Reflective RAG
- LangChain 文档 - RAG 框架
- RAGAS - RAG 评估框架
- BGE Embedding - 中文 Embedding