RAG 实战:用 LangChain + DeepSeek 搭建企业私有知识库问答系统

手把手带你从零搭建一个可用的 RAG 知识库问答系统,支持上传 PDF/Word 文档,基于文档内容精准回答问题。


什么是 RAG?

RAG(Retrieval-Augmented Generation,检索增强生成)是目前企业落地大模型最主流的方案:

复制代码
用户问题 → 向量检索(找到相关文档片段)→ 大模型(基于检索结果生成答案)

解决的核心问题

  • 大模型不了解你的私有数据
  • 避免模型"幻觉"(编造不存在的信息)
  • 答案可溯源,有据可查

系统架构

复制代码
┌─────────────┐    ┌──────────────┐    ┌─────────────┐
│  文档上传    │ → │  文本分割     │ → │  向量化存储  │
│ PDF/Word/MD │    │  Chunk Split  │    │  ChromaDB   │
└─────────────┘    └──────────────┘    └─────────────┘
                                              ↓
┌─────────────┐    ┌──────────────┐    ┌─────────────┐
│  最终回答   │ ← │  DeepSeek    │ ← │  向量检索   │
│  含来源引用  │    │  生成回答     │    │  Top-K 结果 │
└─────────────┘    └──────────────┘    └─────────────┘

环境准备

bash 复制代码
pip install langchain langchain-community langchain-openai
pip install chromadb
pip install pypdf python-docx
pip install sentence-transformers

一、文档加载与分割

python 复制代码
from langchain.document_loaders import PyPDFLoader, Docx2txtLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import os

def load_documents(file_path: str):
    """根据文件类型加载文档"""
    ext = os.path.splitext(file_path)[1].lower()
    
    if ext == '.pdf':
        loader = PyPDFLoader(file_path)
    elif ext in ['.docx', '.doc']:
        loader = Docx2txtLoader(file_path)
    elif ext == '.txt' or ext == '.md':
        loader = TextLoader(file_path, encoding='utf-8')
    else:
        raise ValueError(f"不支持的文件类型: {ext}")
    
    return loader.load()

def split_documents(documents, chunk_size=500, chunk_overlap=50):
    """将文档切割成小块"""
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,      # 每块最大字符数
        chunk_overlap=chunk_overlap, # 块之间的重叠字符数
        separators=["\n\n", "\n", "。", "!", "?", " ", ""],
        length_function=len
    )
    return splitter.split_documents(documents)

# 使用示例
docs = load_documents("company_manual.pdf")
chunks = split_documents(docs)
print(f"共切割为 {len(chunks)} 个文本块")
# 输出:共切割为 156 个文本块

二、向量化与存储

python 复制代码
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings

def create_vector_store(chunks, persist_dir="./chroma_db"):
    """创建向量数据库"""
    
    # 使用本地嵌入模型(免费,不调用API)
    embeddings = HuggingFaceEmbeddings(
        model_name="BAAI/bge-small-zh-v1.5",  # 中文效果好的小模型
        model_kwargs={'device': 'cpu'},
        encode_kwargs={'normalize_embeddings': True}
    )
    
    # 创建并持久化向量库
    vector_store = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        persist_directory=persist_dir
    )
    
    print(f"✅ 向量库创建完成,共 {vector_store._collection.count()} 条记录")
    return vector_store

def load_vector_store(persist_dir="./chroma_db"):
    """加载已有向量库"""
    embeddings = HuggingFaceEmbeddings(
        model_name="BAAI/bge-small-zh-v1.5",
        model_kwargs={'device': 'cpu'},
        encode_kwargs={'normalize_embeddings': True}
    )
    return Chroma(
        persist_directory=persist_dir,
        embedding_function=embeddings
    )

三、接入 DeepSeek 生成回答

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

def create_qa_chain(vector_store):
    """创建问答链"""
    
    # 使用 DeepSeek(兼容 OpenAI API)
    llm = ChatOpenAI(
        model="deepseek-chat",
        api_key="YOUR_DEEPSEEK_API_KEY",
        base_url="https://api.deepseek.com",
        temperature=0.1,  # 低温度,回答更准确
    )
    
    # 自定义提示词,要求基于文档回答
    prompt_template = """你是一个专业的知识库助手。请严格基于以下检索到的文档内容回答用户问题。

如果文档中没有相关信息,请明确说明"根据现有文档,无法找到相关信息",不要编造答案。

检索到的文档内容:
{context}

用户问题:{question}

请给出准确、简洁的回答,并在回答末尾注明信息来源(文档名称和页码):"""

    PROMPT = PromptTemplate(
        template=prompt_template,
        input_variables=["context", "question"]
    )
    
    # 创建检索问答链
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff",
        retriever=vector_store.as_retriever(
            search_type="similarity",
            search_kwargs={"k": 4}  # 检索最相关的4个文档块
        ),
        chain_type_kwargs={"prompt": PROMPT},
        return_source_documents=True  # 返回来源文档
    )
    
    return qa_chain

def ask(qa_chain, question: str):
    """提问并获取带来源的回答"""
    result = qa_chain({"query": question})
    
    answer = result["result"]
    sources = result["source_documents"]
    
    print(f"\n❓ 问题:{question}")
    print(f"\n💡 回答:{answer}")
    print(f"\n📚 参考来源:")
    
    seen = set()
    for doc in sources:
        source = doc.metadata.get("source", "未知来源")
        page = doc.metadata.get("page", "")
        key = f"{source}-{page}"
        if key not in seen:
            seen.add(key)
            print(f"  - {source}" + (f" 第{page+1}页" if page != "" else ""))
    
    return answer

四、完整系统整合

python 复制代码
import os

class KnowledgeBase:
    def __init__(self, persist_dir="./chroma_db"):
        self.persist_dir = persist_dir
        self.vector_store = None
        self.qa_chain = None
    
    def build(self, file_paths: list):
        """从文件列表构建知识库"""
        all_chunks = []
        
        for path in file_paths:
            print(f"📄 加载文档:{path}")
            docs = load_documents(path)
            chunks = split_documents(docs)
            all_chunks.extend(chunks)
            print(f"   切割为 {len(chunks)} 块")
        
        print(f"\n⏳ 正在向量化 {len(all_chunks)} 个文本块...")
        self.vector_store = create_vector_store(all_chunks, self.persist_dir)
        self.qa_chain = create_qa_chain(self.vector_store)
        print("✅ 知识库构建完成!")
    
    def load(self):
        """加载已有知识库"""
        self.vector_store = load_vector_store(self.persist_dir)
        self.qa_chain = create_qa_chain(self.vector_store)
        print("✅ 知识库加载完成!")
    
    def chat(self, question: str) -> str:
        """提问"""
        if not self.qa_chain:
            raise RuntimeError("知识库未初始化,请先调用 build() 或 load()")
        return ask(self.qa_chain, question)
    
    def add_document(self, file_path: str):
        """向已有知识库添加文档"""
        docs = load_documents(file_path)
        chunks = split_documents(docs)
        self.vector_store.add_documents(chunks)
        print(f"✅ 已添加 {len(chunks)} 个文本块")


# ====== 使用示例 ======

# 第一次:构建知识库
kb = KnowledgeBase()
kb.build([
    "company_manual.pdf",      # 公司手册
    "product_docs.docx",       # 产品文档
    "faq.md"                   # 常见问题
])

# 后续使用:直接加载
kb = KnowledgeBase()
kb.load()

# 提问
kb.chat("公司的年假政策是什么?")
kb.chat("产品支持哪些操作系统?")
kb.chat("如何申请报销?")

五、实际效果展示

复制代码
❓ 问题:年假是几天?

💡 回答:根据公司员工手册,年假天数与工龄挂钩:
- 工龄1-3年:5天
- 工龄3-10年:10天  
- 工龄10年以上:15天
年假需提前3个工作日申请,经直属领导审批后方可使用。

📚 参考来源:
  - company_manual.pdf 第12页

六、性能优化建议

python 复制代码
# 1. 批量处理嵌入,减少 API 调用
embeddings = HuggingFaceEmbeddings(
    encode_kwargs={
        'normalize_embeddings': True,
        'batch_size': 64  # 批量处理
    }
)

# 2. 混合检索(相似度 + 关键词)
from langchain.retrievers import BM25Retriever, EnsembleRetriever

bm25_retriever = BM25Retriever.from_documents(chunks)
bm25_retriever.k = 4

vector_retriever = vector_store.as_retriever(search_kwargs={"k": 4})

# 混合检索效果更好
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, vector_retriever],
    weights=[0.3, 0.7]  # 向量检索权重更高
)

# 3. 问题改写,提高检索准确率
rewrite_prompt = "将以下问题改写为更适合文档检索的形式:{question}"

总结

一个完整的 RAG 系统需要:

组件 本文选择 原因
嵌入模型 BGE-small-zh 本地运行,中文效果好,免费
向量数据库 ChromaDB 轻量,支持本地持久化
大模型 DeepSeek 性价比最高,中文能力强
编排框架 LangChain 生态完善,组件丰富

整个系统部署在本地服务器即可,数据不出门,适合企业私有化部署。


代码已经过测试可直接运行,如有问题欢迎评论区交流! 觉得有用点个赞 👍,后续会出 Dify 可视化版本教程。

相关推荐
懷淰メ2 小时前
python3GUI---基于PyQt5+YOLOv8+DeepSort的智慧行车可视化系统(详细介绍)
开发语言·yolo·计算机视觉·pyqt·yolov8·deepsort·车距
weixin_649555672 小时前
C语言程序设计第四版(何钦铭、颜晖)第十一章指针进阶之查找子串
c语言·开发语言
应用市场2 小时前
王者荣耀式匹配系统深度解析:从 ELO 到 TrueSkill 的完整工程实现
开发语言·python
说实话起个名字真难啊2 小时前
前端JS审计:渗透测试的“破局之钥”
开发语言·前端·javascript·测试工具
xieliyu.2 小时前
Java、抽象类
java·开发语言
卷Java2 小时前
Python面向对象:class类与对象,3个案例讲透封装与继承
开发语言·python
计算机安禾2 小时前
【数据结构与算法】第13篇:栈(三):中缀表达式转后缀表达式及计算
c语言·开发语言·数据结构·c++·算法·链表
happymaker06262 小时前
servlet、jsp、请求转发、重定向的一些个人理解
java·开发语言·servlet
于先生吖2 小时前
国际版答题系统 JAVA 源码实战指南
java·开发语言