RAG 检索增强生成:让 LLM 拥有私有知识
·
RAG 检索增强生成:让 LLM 拥有私有知识
**AI/LLM 应用开发实战系列** 第 4 篇
上一篇:[LangChain 工作流](./2026-03-01-llm-series-03.md) | 下一篇:向量数据库选型与实战
什么是 RAG
RAG(Retrieval-Augmented Generation,检索增强生成)是一种让 LLM 访问外部知识库的技术。
**核心思想**:在回答用户问题前,先从知识库中检索相关信息,然后将检索结果作为上下文提供给 LLM。
用户问题 → 检索相关知识 → 拼接成 Prompt → LLM 生成答案
为什么需要 RAG
直接使用 LLM 有以下局限:
| 问题 | 说明 | RAG 解决方案 |
|------|------|-------------|
| 知识截止 | GPT-4 只训练到 2024 年 4 月 | 检索最新信息 |
| 私有知识 | 无法访问企业内部文档 | 连接私有知识库 |
| 幻觉问题 | 可能编造不存在的信息 | 基于检索到的事实回答 |
| 上下文限制 | 无法放入所有知识 | 只检索相关内容 |
| 成本高昂 | 长上下文 token 费用高 | 只检索必要信息 |
RAG 工作流程
1. 文档加载 → 2. 文本分块 → 3. 向量化 → 4. 存储到向量数据库
↓
5. 用户提问 → 6. 问题向量化 → 7. 相似度检索 → 8. 拼接 Prompt → 9. LLM 生成
实战:构建文档问答系统
1. 安装依赖
pip install langchain langchain-openai chromadb pypdf
2. 加载 PDF 文档
from langchain_community.document_loaders import PyPDFLoader
# 加载 PDF
loader = PyPDFLoader("company_handbook.pdf")
documents = loader.load()
print(f"加载了 {len(documents)} 页文档")
print(f"第一页内容预览:{documents[0].page_content[:200]}")
3. 文本分块
直接将整篇文档放入 Prompt 会超出 token 限制。需要分块:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每块 1000 字符
chunk_overlap=200, # 重叠 200 字符,保持上下文连贯
length_function=len,
separators=["\n\n", "\n", "。", "!", "?", "!", "?", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"分成了 {len(chunks)} 个文本块")
4. 向量化并存储
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# 初始化嵌入模型
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 创建向量数据库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化存储
)
print(f"向量数据库已创建,包含 {vectorstore._collection.count()} 个向量")
5. 构建检索问答链
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4", temperature=0.7)
# 创建检索问答链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 将所有检索结果拼接到一个 Prompt
retriever=vectorstore.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 3} # 返回最相关的 3 个文本块
),
return_source_documents=True # 返回源文档(用于溯源)
)
# 提问
query = "公司的年假政策是什么?"
result = qa_chain.invoke({"query": query})
print(f"问题:{query}")
print(f"答案:{result['result']}")
print(f"\n参考来源:")
for doc in result['source_documents']:
print(f"- {doc.metadata.get('source', 'unknown')}")
完整的 RAG 系统示例
让我们构建一个可以问答公司内部文档的完整系统:
import os
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# ============ 1. 配置 ============
os.environ["OPENAI_API_KEY"] = "your-api-key"
DATA_DIR = "./company_docs" # 公司文档目录
DB_DIR = "./vector_db" # 向量数据库目录
# ============ 2. 加载文档 ============
def load_documents():
"""加载多种格式的文档"""
loaders = [
DirectoryLoader(DATA_DIR, glob="**/*.pdf", loader_cls=PyPDFLoader),
DirectoryLoader(DATA_DIR, glob="**/*.txt", loader_cls=TextLoader),
DirectoryLoader(DATA_DIR, glob="**/*.md", loader_cls=TextLoader),
]
documents = []
for loader in loaders:
documents.extend(loader.load())
print(f"加载了 {len(documents)} 个文档")
return documents
# ============ 3. 文本分块 ============
def split_documents(documents):
"""将文档分成小块"""
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"分成了 {len(chunks)} 个文本块")
return chunks
# ============ 4. 创建向量数据库 ============
def create_vectorstore(chunks):
"""创建或加载向量数据库"""
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
if os.path.exists(DB_DIR):
# 加载已有数据库
vectorstore = Chroma(
persist_directory=DB_DIR,
embedding_function=embeddings
)
else:
# 创建新数据库
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=DB_DIR
)
vectorstore.persist()
return vectorstore
# ============ 5. 创建问答链 ============
def create_qa_chain(vectorstore):
"""创建检索问答链"""
llm = ChatOpenAI(model="gpt-4", temperature=0.7)
# 自定义 Prompt 模板
template = """基于以下参考信息回答问题。如果参考信息中没有答案,请说"根据现有文档,我无法回答这个问题"。
参考信息:
{context}
问题:{question}
答案:"""
prompt = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
return_source_documents=True,
chain_type_kwargs={"prompt": prompt}
)
return qa_chain
# ============ 6. 主程序 ============
def main():
# 初始化(首次运行需要)
print("正在加载文档...")
documents = load_documents()
chunks = split_documents(documents)
vectorstore = create_vectorstore(chunks)
# 创建问答链
qa_chain = create_qa_chain(vectorstore)
# 交互式问答
print("\n=== 文档问答系统已就绪 ===")
print("输入问题开始提问,输入'quit'退出\n")
while True:
query = input("你:").strip()
if query.lower() == 'quit':
break
result = qa_chain.invoke({"query": query})
print(f"AI: {result['result']}")
# 显示参考来源
if result['source_documents']:
print("\n参考来源:")
for i, doc in enumerate(result['source_documents'], 1):
source = doc.metadata.get('source', 'unknown')
page = doc.metadata.get('page', '')
print(f" [{i}] {source}" + (f" 第{page}页" if page else ""))
print()
if __name__ == "__main__":
main()
优化技巧
1. 混合搜索(相似度 + 关键词)
retriever = vectorstore.as_retriever(
search_type="mmr", # Maximal Marginal Relevance
search_kwargs={
"k": 5, # 返回 5 个结果
"fetch_k": 10, # 先取 10 个,再多样性选择
"lambda_mult": 0.5 # 相关性和多样性的平衡 (0-1)
}
)
2. 多向量检索
from langchain.retrievers import EnsembleRetriever
from langchain_community.vectorstores import FAISS
# 结合多个检索器
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 3})
chroma_retriever = chroma_vectorstore.as_retriever(search_kwargs={"k": 3})
ensemble_retriever = EnsembleRetriever(
retrievers=[faiss_retriever, chroma_retriever],
weights=[0.5, 0.5]
)
3. 添加对话历史
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
qa_chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(),
memory=memory
)
# 多轮对话
result = qa_chain({"question": "年假有多少天?"})
result = qa_chain({"question": "那病假呢?"}) # 会记住之前的对话
4. 流式输出
for chunk in qa_chain.stream({"query": "公司有哪些福利?"}):
if 'result' in chunk:
print(chunk['result'], end='', flush=True)
常见问题
Q: 检索结果不相关怎么办?
1. 调整分块大小(chunk_size)
2. 增加重叠(chunk_overlap)
3. 使用更好的嵌入模型(text-embedding-3-large)
4. 调整 k 值(返回结果数量)
Q: 回答质量不高怎么办?
1. 优化 Prompt 模板
2. 增加参考上下文数量
3. 降低 temperature(更确定性)
4. 在 Prompt 中明确要求"基于参考信息回答"
Q: 如何处理大量文档?
1. 使用增量更新(只添加新文档)
2. 使用分布式向量数据库(Milvus、Weaviate)
3. 实现文档版本管理
总结
RAG 让 LLM 可以:
- ✅ 访问最新信息
- ✅ 利用私有知识库
- ✅ 减少幻觉
- ✅ 降低 token 成本
-
下一篇我们将学习**向量数据库选型**,对比 FAISS、Chroma、Milvus 等主流方案。
参考资源
- [LangChain RAG 文档](https://python.langchain.com/docs/use_cases/question_answering/)
- [RAG 论文](https://arxiv.org/abs/2005.11401)
- [Chroma 向量数据库](https://www.trychroma.com/)
更多推荐


所有评论(0)