LangChain + RAG 知识库系统搭建指南:从零构建企业级文档问答系统

🌸你好呀!我是 lbb小魔仙
🌟 感谢陪伴~ 小白博主在线求友
🌿 跟着小白学Linux/Java/Python
📖 专栏汇总:
《Linux》专栏 | 《Java》专栏 | 《Python》专栏

- [LangChain + RAG 知识库系统搭建指南:从零构建企业级文档问答系统](#LangChain + RAG 知识库系统搭建指南:从零构建企业级文档问答系统)
-
- [一、RAG 技术概述](#一、RAG 技术概述)
-
- [1.1 什么是 RAG?](#1.1 什么是 RAG?)
- [1.2 RAG vs 微调](#1.2 RAG vs 微调)
- [1.3 RAG 应用场景](#1.3 RAG 应用场景)
- 二、系统架构设计
- 三、环境准备与依赖安装
-
- [3.1 Python 环境准备](#3.1 Python 环境准备)
- [3.2 核心依赖安装](#3.2 核心依赖安装)
- [3.3 国内镜像加速](#3.3 国内镜像加速)
- [3.4 Ollama 安装(LLM 后端)](#3.4 Ollama 安装(LLM 后端))
- [四、LangChain 基础概念](#四、LangChain 基础概念)
-
- [4.1 Document Loader(文档加载器)](#4.1 Document Loader(文档加载器))
- [4.2 Text Splitter(文本分割器)](#4.2 Text Splitter(文本分割器))
- [4.3 Embeddings(向量化模型)](#4.3 Embeddings(向量化模型))
- [4.4 Vector Store(向量数据库)](#4.4 Vector Store(向量数据库))
- [4.5 Retrieval Chain(检索链)](#4.5 Retrieval Chain(检索链))
- [五、完整 RAG 系统实现](#五、完整 RAG 系统实现)
-
- [5.1 项目结构](#5.1 项目结构)
- [5.2 文档加载与处理模块](#5.2 文档加载与处理模块)
- [5.3 向量数据库模块](#5.3 向量数据库模块)
- [5.4 RAG 问答模块](#5.4 RAG 问答模块)
- [5.5 完整代码整合 - 主程序](#5.5 完整代码整合 - 主程序)
- 六、高级功能扩展
-
- [6.1 多格式文档支持](#6.1 多格式文档支持)
- [6.2 混合检索策略](#6.2 混合检索策略)
- [6.3 对话历史管理](#6.3 对话历史管理)
- 七、性能优化技巧
-
- [7.1 Embedding 模型选择](#7.1 Embedding 模型选择)
- [7.2 分块策略优化](#7.2 分块策略优化)
- [7.3 缓存策略](#7.3 缓存策略)
- 八、常见问题与解决方案
-
- [Q1: 中文检索效果差](#Q1: 中文检索效果差)
- [Q2: 向量数据库占用空间大](#Q2: 向量数据库占用空间大)
- [Q3: 检索结果不相关](#Q3: 检索结果不相关)
- [Q4: Ollama 推理速度慢](#Q4: Ollama 推理速度慢)
- 九、总结与资源
摘要:本文详细介绍如何使用 LangChain 和 RAG(检索增强生成)技术构建本地知识库问答系统。涵盖环境配置、文档处理、向量化存储、检索链构建等完整流程,提供可直接运行的完整代码,解决企业文档智能化难题。
一、RAG 技术概述
1.1 什么是 RAG?
RAG(Retrieval-Augmented Generation,检索增强生成) 是一种将信息检索与大语言模型生成相结合的技术架构。
核心思想:
- 检索(Retrieval):根据用户问题,从知识库中检索相关文档片段
- 增强(Augmented):将检索到的文档作为上下文,注入到 Prompt 中
- 生成(Generation):LLM 基于上下文生成准确、有据可依的回答
工作流程图示:
用户提问: "什么是RAG?"
↓
向量化问题
↓
向量数据库检索 → 相关文档片段
↓
Prompt 构建: [上下文] + [问题]
↓
大语言模型生成
↓
准确回答(带来源引用)
1.2 RAG vs 微调
| 对比维度 | RAG | 微调(Fine-tuning) |
|---|---|---|
| 知识更新 | 实时更新(添加文档即可) | 需要重新训练 |
| 成本 | 低(无需训练) | 高(需要GPU资源) |
| 知识覆盖 | 可处理海量文档 | 受模型参数限制 |
| 可解释性 | 高(可追溯来源) | 低(黑盒) |
| 部署难度 | 简单 | 复杂 |
| 适用场景 | 知识密集型任务 | 特定风格/格式生成 |
结论:对于企业文档问答、知识库检索等场景,RAG 是更优选择。
1.3 RAG 应用场景
- 企业知识库问答:员工手册、技术文档、FAQ 自动问答
- 客服系统:基于产品文档的智能客服
- 法律/医疗咨询:基于法规/病例库的精准问答
- 代码助手:基于项目文档/代码的智能补全
- 研究助手:基于论文库的知识检索与总结
二、系统架构设计
一个完整的 RAG 系统包含以下核心模块:
┌─────────────────────────────────────────────────────────┐
│ 用户接口层 │
│ (Web UI / API / 命令行) │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ 应用逻辑层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 问题理解 │ │ 答案生成 │ │ 结果评估 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ RAG 核心层 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 检索模块 │ │ 生成模块 │ │
│ │ - 向量检索 │ │ - Prompt构建│ │
│ │ - 关键词检索 │ │ - LLM调用 │ │
│ └──────────────┘ └──────────────┘ │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────┐
│ 数据层 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 文档库 │ │ 向量数据库 │ │
│ │ (原始文档) │ │ (ChromaDB) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
三、环境准备与依赖安装
3.1 Python 环境准备
bash
# 创建虚拟环境(强烈推荐)
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
# 检查 Python 版本(需要 3.9+)
python --version
3.2 核心依赖安装
创建 requirements.txt:
txt
# LangChain 核心
langchain==0.3.15
langchain-community==0.3.14
langchain-core==0.3.34
# 向量数据库
chromadb==0.6.3
# 文档处理
pypdf==5.1.0 # PDF 处理
python-docx==1.1.2 # DOCX 处理
unstructured==0.16.14 # 多种格式文档处理
# Embedding 模型
sentence-transformers==3.3.1 # 本地 Embedding 模型
# LLM 集成(Ollama)
ollama==0.4.7
# 可选:Web UI
streamlit==1.41.1 # Web 界面
安装依赖:
bash
pip install -r requirements.txt
3.3 国内镜像加速
bash
# 使用清华大学镜像
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
# 或设置永久镜像
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
3.4 Ollama 安装(LLM 后端)
RAG 系统需要大语言模型生成答案,我们使用本地部署的 Ollama:
bash
# 安装 Ollama(参考上一篇文章)
# Windows: 下载 https://ollama.com/download
# 拉取中文友好模型
ollama pull qwen2.5:7b
四、LangChain 基础概念
4.1 Document Loader(文档加载器)
LangChain 提供了多种文档加载器,支持 PDF、Word、Markdown、网页等格式。
python
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
# 加载 PDF
pdf_loader = PyPDFLoader("文档.pdf")
pdf_docs = pdf_loader.load()
# 加载 DOCX
docx_loader = Docx2txtLoader("文档.docx")
docx_docs = docx_loader.load()
# 加载 TXT/MD
txt_loader = TextLoader("文档.txt", encoding="utf-8")
txt_docs = txt_loader.load()
print(f"文档页数/段落数: {len(pdf_docs)}")
print(f"第一篇内容预览: {pdf_docs[0].page_content[:200]}")
print(f"文档元数据: {pdf_docs[0].metadata}")
输出示例:
文档页数/段落数: 15
第一篇内容预览: RAG(Retrieval-Augmented Generation)是一种...
文档元数据: {'source': '文档.pdf', 'page': 0}
4.2 Text Splitter(文本分割器)
将长文档分割成适合向量化的小片段。
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 创建文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个片段约 500 字符
chunk_overlap=50, # 相邻片段重叠 50 字符
length_function=len,
separators=["\n\n", "\n", "。", ";", ",", " ", ""]
)
# 分割文档
splits = text_splitter.split_documents(pdf_docs)
print(f"分割后片段数: {len(splits)}")
print(f"片段大小: {[len(doc.page_content) for doc in splits[:5]]}")
参数说明:
chunk_size:每个片段的最大字符数chunk_overlap:相邻片段的重叠字符数(保持语义连贯性)separators:分割符优先级(先按段落,再按句子,最后按字符)
4.3 Embeddings(向量化模型)
将文本转换为向量表示,用于语义检索。
方案1:使用本地 Sentence Transformers(推荐,无需联网)
python
from langchain_community.embeddings import HuggingFaceEmbeddings
# 使用本地 embedding 模型(需提前下载或设置镜像)
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
model_kwargs={'device': 'cpu'}, # 'cuda' 如果有 GPU
encode_kwargs={'normalize_embeddings': True}
)
# 测试向量化
test_text = "这是一个测试句子"
vector = embeddings.embed_query(test_text)
print(f"向量维度: {len(vector)}")
print(f"前10个值: {vector[:10]}")
方案2:使用 Ollama Embeddings(需要 Ollama 0.1.26+)
python
from langchain_community.embeddings import OllamaEmbeddings
embeddings = OllamaEmbeddings(model="nomic-embed-text")
# 测试
vector = embeddings.embed_query("测试文本")
print(f"向量维度: {len(vector)}")
4.4 Vector Store(向量数据库)
将文档向量存储到向量数据库,支持快速相似度检索。
python
from langchain_community.vectorstores import Chroma
# 构建向量数据库
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化目录
)
# 相似度检索测试
query = "什么是RAG?"
docs = vectorstore.similarity_search(query, k=3)
print(f"检索到 {len(docs)} 个相关片段:")
for i, doc in enumerate(docs):
print(f"\n片段 {i+1}:")
print(doc.page_content[:200])
print(f"来源: {doc.metadata}")
4.5 Retrieval Chain(检索链)
构建完整的 RAG 检索问答链。
python
from langchain_community.chat_models import ChatOllama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.prompts import ChatPromptTemplate
# 初始化 LLM
llm = ChatOllama(model="qwen2.5:7b", temperature=0)
# 创建检索器
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
)
# 创建 Prompt 模板
prompt_template = """
根据以下上下文信息,回答用户的问题。如果你不知道答案,就说不知道,不要编造答案。
上下文:
{context}
问题:{input}
答案:"""
prompt = ChatPromptTemplate.from_template(prompt_template)
# 创建文档问答链
combine_docs_chain = create_stuff_documents_chain(llm, prompt)
# 创建检索问答链
rag_chain = create_retrieval_chain(retriever, combine_docs_chain)
# 使用链进行问答
result = rag_chain.invoke({"input": "什么是RAG?"})
print(result["answer"])
五、完整 RAG 系统实现
5.1 项目结构
rag_system/
├── data/ # 原始文档存放目录
│ ├── doc1.pdf
│ ├── doc2.docx
│ └── doc3.txt
├── chroma_db/ # 向量数据库持久化目录
├── src/
│ ├── document_processor.py # 文档处理模块
│ ├── vector_store.py # 向量数据库模块
│ ├── rag_chain.py # RAG 链模块
│ └── main.py # 主程序
├── requirements.txt
└── README.md
5.2 文档加载与处理模块
创建 src/document_processor.py:
python
import os
from typing import List
from langchain_core.documents import Document
from langchain_community.document_loaders import (
PyPDFLoader,
Docx2txtLoader,
TextLoader,
UnstructuredMarkdownLoader
)
from langchain_text_splitters import RecursiveCharacterTextSplitter
class DocumentProcessor:
"""文档处理类:加载、分割文档"""
def __init__(self, chunk_size: int = 500, chunk_overlap: int = 50):
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
separators=["\n\n", "\n", "。", ";", ",", " ", ""]
)
def load_document(self, file_path: str) -> List[Document]:
"""根据文件类型加载文档"""
ext = os.path.splitext(file_path)[1].lower()
if ext == '.pdf':
loader = PyPDFLoader(file_path)
elif ext == '.docx':
loader = Docx2txtLoader(file_path)
elif ext == '.txt':
loader = TextLoader(file_path, encoding='utf-8')
elif ext == '.md':
loader = UnstructuredMarkdownLoader(file_path)
else:
raise ValueError(f"不支持的文件类型: {ext}")
return loader.load()
def load_directory(self, dir_path: str) -> List[Document]:
"""加载目录下所有支持的文档"""
all_docs = []
supported_exts = ['.pdf', '.docx', '.txt', '.md']
for root, _, files in os.walk(dir_path):
for file in files:
if os.path.splitext(file)[1].lower() in supported_exts:
file_path = os.path.join(root, file)
try:
docs = self.load_document(file_path)
all_docs.extend(docs)
print(f"✓ 已加载: {file}")
except Exception as e:
print(f"✗ 加载失败 {file}: {e}")
return all_docs
def split_documents(self, documents: List[Document]) -> List[Document]:
"""分割文档为小片段"""
splits = self.text_splitter.split_documents(documents)
print(f"文档分割完成: {len(documents)} 篇文档 → {len(splits)} 个片段")
return splits
# 测试代码
if __name__ == "__main__":
processor = DocumentProcessor()
# 测试目录
docs = processor.load_directory("../data")
splits = processor.split_documents(docs)
print(f"\n示例片段内容:\n{splits[0].page_content[:300]}")
5.3 向量数据库模块
创建 src/vector_store.py:
python
from typing import List
from langchain_core.documents import Document
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
class VectorStoreManager:
"""向量数据库管理类"""
def __init__(self, persist_directory: str = "./chroma_db"):
self.persist_directory = persist_directory
self.embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True}
)
self.vectorstore = None
def create_vectorstore(self, documents: List[Document]) -> Chroma:
"""创建向量数据库"""
self.vectorstore = Chroma.from_documents(
documents=documents,
embedding=self.embeddings,
persist_directory=self.persist_directory
)
print(f"✓ 向量数据库已创建,存储位置: {self.persist_directory}")
return self.vectorstore
def load_vectorstore(self) -> Chroma:
"""加载已有的向量数据库"""
self.vectorstore = Chroma(
persist_directory=self.persist_directory,
embedding_function=self.embeddings
)
print(f"✓ 已加载向量数据库: {self.persist_directory}")
return self.vectorstore
def similarity_search(self, query: str, k: int = 3) -> List[Document]:
"""相似度检索"""
if self.vectorstore is None:
raise ValueError("向量数据库未初始化,请先创建或加载")
return self.vectorstore.similarity_search(query, k=k)
def get_retriever(self, search_kwargs: dict = None):
"""获取检索器"""
if self.vectorstore is None:
raise ValueError("向量数据库未初始化")
if search_kwargs is None:
search_kwargs = {"k": 3}
return self.vectorstore.as_retriever(
search_type="similarity",
search_kwargs=search_kwargs
)
# 测试代码
if __name__ == "__main__":
from document_processor import DocumentProcessor
# 处理文档
processor = DocumentProcessor()
docs = processor.load_directory("../data")
splits = processor.split_documents(docs)
# 创建向量数据库
vs_manager = VectorStoreManager()
vectorstore = vs_manager.create_vectorstore(splits)
# 测试检索
results = vs_manager.similarity_search("测试查询", k=2)
for i, doc in enumerate(results):
print(f"\n片段 {i+1}: {doc.page_content[:200]}")
5.4 RAG 问答模块
创建 src/rag_chain.py:
python
from langchain_community.chat_models import ChatOllama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.prompts import ChatPromptTemplate
class RAGSystem:
"""RAG 问答系统"""
def __init__(self, retriever, model_name: str = "qwen2.5:7b"):
self.llm = ChatOllama(model=model_name, temperature=0)
self.retriever = retriever
self.rag_chain = self._build_rag_chain()
def _build_rag_chain(self):
"""构建 RAG 链"""
# 自定义 Prompt 模板(中文优化)
prompt_template = """
根据以下上下文信息,回答用户的问题。如果你不知道答案,就说不知道,不要编造答案。
上下文:
{context}
问题:{input}
答案:"""
prompt = ChatPromptTemplate.from_template(prompt_template)
# 创建文档组合链
combine_docs_chain = create_stuff_documents_chain(self.llm, prompt)
# 创建检索问答链
rag_chain = create_retrieval_chain(self.retriever, combine_docs_chain)
return rag_chain
def ask(self, question: str) -> dict:
"""提问并获取答案"""
result = self.rag_chain.invoke({"input": question})
return {
"question": question,
"answer": result["answer"],
"source_documents": result.get("context", [])
}
def ask_with_sources(self, question: str) -> str:
"""提问并返回带来源的格式化答案"""
result = self.ask(question)
answer = f"**问题**:{result['question']}\n\n"
answer += f"**答案**:{result['answer']}\n\n"
answer += f"**参考来源**(共 {len(result['source_documents'])} 个片段):\n"
for i, doc in enumerate(result['source_documents'], 1):
source = doc.metadata.get('source', '未知来源')
page = doc.metadata.get('page', '')
page_info = f"第 {page} 页" if page else ""
answer += f"\n片段 {i}({source} {page_info}):\n"
answer += doc.page_content[:200] + "...\n"
return answer
# 测试代码
if __name__ == "__main__":
from vector_store import VectorStoreManager
# 加载向量数据库
vs_manager = VectorStoreManager()
vs_manager.load_vectorstore()
retriever = vs_manager.get_retriever()
# 创建 RAG 系统
rag = RAGSystem(retriever)
# 测试问答
result = rag.ask("什么是RAG?")
print(f"问题: {result['question']}")
print(f"答案: {result['answer']}")
print(f"参考片段数: {len(result['source_documents'])}")
5.5 完整代码整合 - 主程序
创建 src/main.py:
python
import sys
import os
# 添加 src 目录到路径
sys.path.append(os.path.dirname(__file__))
from document_processor import DocumentProcessor
from vector_store import VectorStoreManager
from rag_chain import RAGSystem
def build_knowledge_base(data_dir: str):
"""构建知识库"""
print("=" * 60)
print("开始构建知识库...")
print("=" * 60)
# 1. 处理文档
processor = DocumentProcessor(chunk_size=500, chunk_overlap=50)
documents = processor.load_directory(data_dir)
if not documents:
print("✗ 未找到任何文档,请检查 data 目录")
return None
splits = processor.split_documents(documents)
# 2. 创建向量数据库
vs_manager = VectorStoreManager(persist_directory="./chroma_db")
vectorstore = vs_manager.create_vectorstore(splits)
print("✓ 知识库构建完成!")
return vs_manager
def load_knowledge_base():
"""加载已有知识库"""
print("=" * 60)
print("加载已有知识库...")
print("=" * 60)
vs_manager = VectorStoreManager(persist_directory="./chroma_db")
try:
vs_manager.load_vectorstore()
print("✓ 知识库加载成功!")
return vs_manager
except Exception as e:
print(f"✗ 加载失败: {e}")
return None
def interactive_qa(rag_system: RAGSystem):
"""交互式问答"""
print("\n" + "=" * 60)
print("进入交互式问答模式(输入 'exit' 退出,'rebuild' 重建知识库)")
print("=" * 60)
while True:
question = input("\n💬 请输入问题: ").strip()
if not question:
continue
if question.lower() == 'exit':
print("再见!")
break
if question.lower() == 'rebuild':
return 'rebuild'
try:
result = rag_system.ask(question)
print(f"\n🤖 答案: {result['answer']}")
print(f"\n📚 参考了 {len(result['source_documents'])} 个文档片段")
# 显示来源
for i, doc in enumerate(result['source_documents'][:2], 1):
source = doc.metadata.get('source', '未知')
print(f" [{i}] {source}")
except Exception as e:
print(f"✗ 发生错误: {e}")
def main():
"""主函数"""
data_dir = "./data"
# 检查是否已有向量数据库
if os.path.exists("./chroma_db"):
print("检测到已有知识库,是否加载?(y/n)")
choice = input("请选择: ").strip().lower()
if choice == 'y':
vs_manager = load_knowledge_base()
else:
vs_manager = build_knowledge_base(data_dir)
else:
vs_manager = build_knowledge_base(data_dir)
if vs_manager is None:
print("知识库初始化失败,程序退出")
return
# 创建检索器和 RAG 系统
retriever = vs_manager.get_retriever(search_kwargs={"k": 3})
rag_system = RAGSystem(retriever, model_name="qwen2.5:7b")
# 进入交互式问答
while True:
result = interactive_qa(rag_system)
if result == 'rebuild':
vs_manager = build_knowledge_base(data_dir)
if vs_manager:
retriever = vs_manager.get_retriever(search_kwargs={"k": 3})
rag_system = RAGSystem(retriever, model_name="qwen2.5:7b")
else:
break
if __name__ == "__main__":
main()
六、高级功能扩展
6.1 多格式文档支持
扩展 DocumentProcessor 支持更多格式:
python
from langchain_community.document_loaders import CSVLoader
from langchain_community.document_loaders import JSONLoader
from langchain_community.document_loaders import UnstructuredHTMLLoader
class AdvancedDocumentProcessor(DocumentProcessor):
"""高级文档处理器,支持更多格式"""
def load_document(self, file_path: str) -> List[Document]:
ext = os.path.splitext(file_path)[1].lower()
# 调用父类方法处理基础格式
if ext in ['.pdf', '.docx', '.txt', '.md']:
return super().load_document(file_path)
# 处理其他格式
elif ext == '.csv':
loader = CSVLoader(file_path)
elif ext == '.json':
loader = JSONLoader(file_path, jq_schema=".", text_content=False)
elif ext == '.html':
loader = UnstructuredHTMLLoader(file_path)
else:
raise ValueError(f"不支持的文件类型: {ext}")
return loader.load()
6.2 混合检索策略
结合向量检索和关键词检索,提升召回率:
python
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain.schema import Document
class HybridRetriever:
"""混合检索器:向量检索 + BM25 关键词检索"""
def __init__(self, vectorstore, documents: List[Document]):
# 向量检索器
self.vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# BM25 关键词检索器
self.bm25_retriever = BM25Retriever.from_documents(documents)
self.bm25_retriever.k = 3
# 混合检索器
self.ensemble_retriever = EnsembleRetriever(
retrievers=[self.vector_retriever, self.bm25_retriever],
weights=[0.7, 0.3] # 向量检索权重 0.7,BM25 权重 0.3
)
def get_retriever(self):
return self.ensemble_retriever
# 使用混合检索器
# hybrid = HybridRetriever(vectorstore, splits)
# retriever = hybrid.get_retriever()
6.3 对话历史管理
实现多轮对话,保持上下文:
python
from langchain.chains import create_history_aware_retriever
from langchain_core.prompts import MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
class ConversationalRAG:
"""支持多轮对话的 RAG 系统"""
def __init__(self, vectorstore, model_name="qwen2.5:7b"):
self.llm = ChatOllama(model=model_name, temperature=0)
self.retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
self.chat_history = []
self.chain = self._build_conversational_chain()
def _build_conversational_chain(self):
# 构建历史感知的检索器
contextualize_q_prompt = ChatPromptTemplate.from_messages([
("system", "根据对话历史,重新表述用户的问题,使其成为一个独立的问题。"),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
])
history_aware_retriever = create_history_aware_retriever(
self.llm, self.retriever, contextualize_q_prompt
)
# 构建问答 Prompt
qa_prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的问答助手。根据以下上下文回答问题。\n\n上下文:{context}"),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
])
# 创建链
stuff_documents_chain = create_stuff_documents_chain(self.llm, qa_prompt)
return create_retrieval_chain(history_aware_retriever, stuff_documents_chain)
def ask(self, question: str) -> str:
result = self.chain.invoke({
"input": question,
"chat_history": self.chat_history
})
# 更新对话历史
self.chat_history.append(HumanMessage(content=question))
self.chat_history.append(AIMessage(content=result["answer"]))
return result["answer"]
七、性能优化技巧
7.1 Embedding 模型选择
| 模型 | 维度 | 速度 | 中文能力 | 推荐场景 |
|---|---|---|---|---|
| paraphrase-multilingual-MiniLM-L12-v2 | 384 | 快 | 强 | 通用推荐 |
| text2vec-base-chinese | 768 | 中 | 很强 | 纯中文场景 |
| bge-large-zh-v1.5 | 1024 | 慢 | 极强 | 高精度要求 |
7.2 分块策略优化
python
# 针对不同文档类型,使用不同的分块策略
def get_optimized_splitter(doc_type: str):
if doc_type == 'code':
# 代码文档:按函数/类分割
return RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=100,
separators=["\n\nclass ", "\ndef ", "\n\n", "\n"]
)
elif doc_type == 'legal':
# 法律文档:按条款分割
return RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=200,
separators=["\n第", "\n一、", "\n1.", "\n\n"]
)
else:
# 通用文档
return RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
7.3 缓存策略
python
from langchain.cache import InMemoryCache
from langchain.globals import set_llm_cache
# 启用 LLM 缓存(避免重复调用)
set_llm_cache(InMemoryCache())
# 或者使用 SQLite 缓存(持久化)
from langchain.cache import SQLiteCache
set_llm_cache(SQLiteCache(database_path="./llm_cache.db"))
八、常见问题与解决方案
Q1: 中文检索效果差
解决方案:
- 使用中文优化的 Embedding 模型(如
text2vec-base-chinese或bge-large-zh) - 在文本分割时,使用中文分隔符(
。,,,;) - 对中文文档进行分词预处理
python
from langchain_community.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5", # 中文最优模型
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': True}
)
Q2: 向量数据库占用空间大
解决方案:
- 删除重复片段
- 使用量化压缩
- 定期清理无用数据
python
# 查看向量数据库大小
import shutil
db_size = shutil.disk_usage("./chroma_db").used / (1024 ** 3) # GB
print(f"数据库大小: {db_size:.2f} GB")
Q3: 检索结果不相关
解决方案:
- 调整
chunk_size和chunk_overlap - 使用混合检索(向量 + BM25)
- 对检索结果进行重排序(Reranking)
python
# 使用 Rerank 模型重排序
from langchain_community.document_compressors import FlashrankRerank
from langchain.retrievers.contextual_compression import ContextualCompressionRetriever
compressor = FlashrankRerank()
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=retriever
)
Q4: Ollama 推理速度慢
解决方案:
- 使用量化模型(
qwen2.5:7b-q4_0) - 启用 GPU 加速
- 调整
num_ctx参数
python
llm = ChatOllama(
model="qwen2.5:7b",
temperature=0,
num_ctx=2048 # 减小上下文窗口
)
九、总结与资源
本文总结
本文详细介绍了使用 LangChain 和 RAG 技术构建知识库问答系统的完整流程:
- RAG 原理:检索 + 增强 + 生成的协同工作机制
- 环境搭建:依赖安装、国内镜像加速、Ollama 部署
- 核心模块:文档加载、文本分割、向量化、向量数据库、检索链
- 完整实现:提供了可直接运行的完整代码(文档处理器、向量数据库管理、RAG 问答系统)
- 高级功能:多格式支持、混合检索、多轮对话、性能评估
- 优化技巧:模型选择、分块策略、缓存机制
下一步学习方向
- Reranking(重排序):使用 Cohere Rerank 或 FlashRank 提升检索精度
- Multi-Agent RAG:结合多个专业 Agent 处理复杂查询
- Graph RAG:引入知识图谱,提升多跳推理能力
- 生产部署:使用 FastAPI + Streamlit 构建 Web 应用
参考资料
- LangChain 官方文档:https://python.langchain.com/docs/
- ChromaDB 文档:https://docs.trychroma.com/
- Ollama 官方文档:https://ollama.com/docs
- Sentence Transformers 文档:https://www.sbert.net/
如果本文对您有帮助,欢迎点赞、收藏、关注!您的支持是我持续创作的动力。
本文完整代码可直接运行,如有问题,欢迎在评论区留言讨论。
📕个人领域 :Linux/C++/java/AI
🚀 个人主页 :有点流鼻涕 · CSDN
💬 座右铭 : "向光而行,沐光而生。"
