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

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,检索增强生成) 是一种将信息检索与大语言模型生成相结合的技术架构。

核心思想

  1. 检索(Retrieval):根据用户问题,从知识库中检索相关文档片段
  2. 增强(Augmented):将检索到的文档作为上下文,注入到 Prompt 中
  3. 生成(Generation):LLM 基于上下文生成准确、有据可依的回答

工作流程图示

复制代码
用户提问: "什么是RAG?"
    ↓
向量化问题
    ↓
向量数据库检索 → 相关文档片段
    ↓
Prompt 构建: [上下文] + [问题]
    ↓
大语言模型生成
    ↓
准确回答(带来源引用)

1.2 RAG vs 微调

对比维度 RAG 微调(Fine-tuning)
知识更新 实时更新(添加文档即可) 需要重新训练
成本 低(无需训练) 高(需要GPU资源)
知识覆盖 可处理海量文档 受模型参数限制
可解释性 高(可追溯来源) 低(黑盒)
部署难度 简单 复杂
适用场景 知识密集型任务 特定风格/格式生成

结论:对于企业文档问答、知识库检索等场景,RAG 是更优选择。

1.3 RAG 应用场景

  1. 企业知识库问答:员工手册、技术文档、FAQ 自动问答
  2. 客服系统:基于产品文档的智能客服
  3. 法律/医疗咨询:基于法规/病例库的精准问答
  4. 代码助手:基于项目文档/代码的智能补全
  5. 研究助手:基于论文库的知识检索与总结

二、系统架构设计

一个完整的 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: 中文检索效果差

解决方案

  1. 使用中文优化的 Embedding 模型(如 text2vec-base-chinesebge-large-zh
  2. 在文本分割时,使用中文分隔符(
  3. 对中文文档进行分词预处理
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: 向量数据库占用空间大

解决方案

  1. 删除重复片段
  2. 使用量化压缩
  3. 定期清理无用数据
python 复制代码
# 查看向量数据库大小
import shutil
db_size = shutil.disk_usage("./chroma_db").used / (1024 ** 3)  # GB
print(f"数据库大小: {db_size:.2f} GB")

Q3: 检索结果不相关

解决方案

  1. 调整 chunk_sizechunk_overlap
  2. 使用混合检索(向量 + BM25)
  3. 对检索结果进行重排序(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 推理速度慢

解决方案

  1. 使用量化模型(qwen2.5:7b-q4_0
  2. 启用 GPU 加速
  3. 调整 num_ctx 参数
python 复制代码
llm = ChatOllama(
    model="qwen2.5:7b",
    temperature=0,
    num_ctx=2048  # 减小上下文窗口
)

九、总结与资源

本文总结

本文详细介绍了使用 LangChain 和 RAG 技术构建知识库问答系统的完整流程:

  1. RAG 原理:检索 + 增强 + 生成的协同工作机制
  2. 环境搭建:依赖安装、国内镜像加速、Ollama 部署
  3. 核心模块:文档加载、文本分割、向量化、向量数据库、检索链
  4. 完整实现:提供了可直接运行的完整代码(文档处理器、向量数据库管理、RAG 问答系统)
  5. 高级功能:多格式支持、混合检索、多轮对话、性能评估
  6. 优化技巧:模型选择、分块策略、缓存机制

下一步学习方向

  • Reranking(重排序):使用 Cohere Rerank 或 FlashRank 提升检索精度
  • Multi-Agent RAG:结合多个专业 Agent 处理复杂查询
  • Graph RAG:引入知识图谱,提升多跳推理能力
  • 生产部署:使用 FastAPI + Streamlit 构建 Web 应用

参考资料


如果本文对您有帮助,欢迎点赞、收藏、关注!您的支持是我持续创作的动力。

本文完整代码可直接运行,如有问题,欢迎在评论区留言讨论。

📕个人领域 :Linux/C++/java/AI

🚀 个人主页有点流鼻涕 · CSDN

💬 座右铭 : "向光而行,沐光而生。"

相关推荐
梦道长生2 小时前
对于extend和append无法解决的字典解析情况
python
2501_901006472 小时前
c++如何利用C++23 std--print加速大量格式化数据的文件IO性能【进阶】
jvm·数据库·python
享客多网络2 小时前
2026年滨海新区GEO获客公司,本地企业增长新引擎
人工智能·python
dinl_vin2 小时前
LangChain 系列·(六):RAG 评估——你怎么知道它够好?
人工智能·langchain
码流怪侠2 小时前
【GitHub】TextGen:开源本地大模型运行平台的终极解决方案
python·程序员·github
2301_782040452 小时前
JavaScript中Map在频繁增删键值对场景下的稳定性
jvm·数据库·python
Jelena157795857922 小时前
Python 爬虫获取淘宝商品详情(标题、主图、SKU、价格)实战指南
网络·爬虫·python
a7963lin3 小时前
Golang怎么用GitLab CI构建_Golang如何编写.gitlab-ci.yml自动化构建流程【教程】
jvm·数据库·python
我鑫如一3 小时前
口碑好的AI API中转站哪家强
人工智能·python