RAG实战:从原理到代码,构建企业级知识库问答系统
本文深入讲解RAG技术原理,并通过LangChain+FAISS手把手实现一个企业级知识库问答系统。
一、为什么需要RAG?
大模型虽然强大,但存在三大痛点:
- 知识截止:训练数据有时效性,无法获取最新信息
- 幻觉问题:面对陌生领域可能编造错误答案
- 私有数据缺失:无法访问企业内部文档、数据库
RAG通过"检索+生成"的组合拳,让大模型能够:
- 实时获取最新知识
- 基于可信文档生成答案
- 接入私有知识库
二、RAG核心原理
RAG的工作流程可以概括为四个步骤:
用户问题 → 向量检索 → 召回相关文档 → 构建Prompt → 大模型生成答案
2.1 文档处理流水线
python
# 文档处理核心流程
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
# 1. 文档加载
from langchain.document_loaders import TextLoader, PDFLoader
loader = PDFLoader("company_manual.pdf")
documents = loader.load()
# 2. 文档切片(关键参数:chunk_size, overlap)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个切片的字符数
chunk_overlap=50, # 切片之间的重叠字符数
separators=["\n\n", "\n", "。", "!", "?"]
)
chunks = text_splitter.split_documents(documents)
# 3. 向量化并存储
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5" # 中文推荐
)
vectorstore = FAISS.from_documents(chunks, embeddings)
# 4. 持久化存储
vectorstore.save_local("faiss_index")
2.2 检索与生成
python
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
# 加载向量库
vectorstore = FAISS.load_local("faiss_index", embeddings)
# 构建RAG链
qa_chain = RetrievalQA.from_chain_type(
llm=OpenAI(temperature=0),
chain_type="stuff", # 将所有检索结果塞入prompt
retriever=vectorstore.as_retriever(
search_kwargs={"k": 4} # 返回top-4相关文档
),
return_source_documents=True # 返回来源文档
)
# 查询
result = qa_chain({"query": "公司的报销流程是什么?"})
print(result["result"])
print("来源文档:", result["source_documents"])
三、进阶:提升RAG效果的五大技巧
3.1 文档切片优化
问题:固定长度切片会切断语义完整性。
解决方案:使用语义切片器
python
from langchain.text_splitter import SemanticChunker
# 基于语义相似度自动确定切分点
semantic_splitter = SemanticChunker(
embeddings,
breakpoint_threshold_type="percentile" # 根据语义差异切分
)
chunks = semantic_splitter.split_documents(documents)
3.2 混合检索策略
单一向量检索可能遗漏关键词精确匹配的结果,混合检索结合了:
- 稠密检索:向量相似度(语义理解强)
- 稀疏检索:BM25关键词匹配(精确匹配强)
python
from langchain.retrievers import EnsembleRetriever
from langchain.retrievers import BM25Retriever
# 向量检索器
faiss_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# BM25检索器
bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 5
# 混合检索
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, faiss_retriever],
weights=[0.4, 0.6] # BM25权重40%,向量检索60%
)
3.3 重排序(Rerank)
初次检索可能返回大量相关度不高的文档,Rerank模型进行二次精排:
python
from sentence_transformers import CrossEncoder
# 加载重排序模型
reranker = CrossEncoder('BAAI/bge-reranker-large')
def rerank_documents(query, documents, top_k=4):
# 计算query与每个doc的相关性分数
pairs = [[query, doc.page_content] for doc in documents]
scores = reranker.predict(pairs)
# 按分数排序,返回top_k
ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
return [doc for doc, score in ranked[:top_k]]
3.4 Prompt工程优化
构建高质量的Prompt模板,引导模型正确使用检索内容:
python
from langchain.prompts import PromptTemplate
prompt_template = """
你是一个专业的知识库问答助手。请基于以下参考资料回答问题。
【要求】
1. 只使用参考资料中的信息回答
2. 如果资料中没有答案,请明确告知用户
3. 回答时标注信息来源
【参考资料】
{context}
【用户问题】
{question}
【回答】
"""
PROMPT = PromptTemplate(
template=prompt_template,
input_variables=["context", "question"]
)
3.5 增量更新机制
知识库需要持续更新,FAISS支持增量添加:
python
def add_new_documents(vectorstore, new_docs_path):
"""增量添加新文档到向量库"""
loader = PDFLoader(new_docs_path)
new_docs = loader.load()
new_chunks = text_splitter.split_documents(new_docs)
# 增量添加
vectorstore.add_documents(new_chunks)
vectorstore.save_local("faiss_index")
四、完整实战:构建企业知识库问答系统
将上述技术整合为一个完整系统:
python
import os
from langchain.document_loaders import DirectoryLoader
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
class EnterpriseRAG:
def __init__(self, docs_path, index_path="faiss_index"):
self.docs_path = docs_path
self.index_path = index_path
self.embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5"
)
self.llm = ChatOpenAI(model="gpt-4", temperature=0)
def build_index(self):
"""构建向量索引"""
# 加载所有文档
loader = DirectoryLoader(self.docs_path, glob="**/*.pdf")
documents = loader.load()
# 切片
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50
)
chunks = splitter.split_documents(documents)
# 构建向量库
self.vectorstore = FAISS.from_documents(chunks, self.embeddings)
self.vectorstore.save_local(self.index_path)
def load_index(self):
"""加载已有索引"""
self.vectorstore = FAISS.load_local(
self.index_path, self.embeddings
)
def query(self, question, top_k=4):
"""查询知识库"""
retriever = self.vectorstore.as_retriever(
search_kwargs={"k": top_k}
)
qa = RetrievalQA.from_chain_type(
llm=self.llm,
retriever=retriever,
return_source_documents=True
)
return qa({"query": question})
# 使用示例
if __name__ == "__main__":
rag = EnterpriseRAG(docs_path="./knowledge_base")
# 首次构建索引
rag.build_index()
# 查询
result = rag.query("公司的年假制度是怎样的?")
print(f"答案:{result['result']}\n")
print(f"来源:{result['source_documents'][0].metadata['source']}")
五、常见问题与避坑指南
问题1:检索结果不相关
原因:切片过大或过小,检索粒度不匹配。
解决 :根据文档特点调整chunk_size,一般建议300-1000字符。
问题2:模型"答非所问"
原因:Prompt未明确约束模型使用检索内容。
解决:使用强制引用模板,要求"只基于参考资料回答"。
问题3:中英文混合检索效果差
原因:单一模型对多语言支持不足。
解决 :使用多语言Embedding模型,如bge-m3。
六、总结
RAG是大模型落地企业的关键技术栈,核心要点:
- 文档处理:合理切片是检索质量的基础
- 检索优化:混合检索 + Rerank显著提升准确率
- Prompt设计:明确约束模型使用检索内容
- 持续迭代:建立知识库增量更新机制
延伸阅读:
- LangChain官方文档:Retrieval章节
- LlamaIndex:另一个优秀的RAG框架
- 向量数据库对比:FAISS vs Milvus vs Pinecone