不知道你有没有过这样的经历: 工作里有一堆项目文档、技术方案,每次要找个参数、回忆某个接口的用法,都要在一堆文件夹里翻半天,翻了半小时才找到那个早就被你忘在角落的 md 文档; 想让 AI 帮你总结这些文档的内容,但是又不敢把这些公司的内部资料、私人的项目文档上传到 ChatGPT 或者其他第三方的 AI 工具里 —— 万一数据泄露了怎么办?之前就看到过新闻,有人把公司的机密文档上传到第三方 AI,结果直接被泄露了,最后丢了工作; 想用那些付费的知识库工具?比如某 AI 的企业版,一个月就要大几百,对于个人开发者或者小团队来说,实在是有点肉疼; 而且很多工具还必须联网才能用,有时候在飞机上、在没网的工地,想查点资料根本用不了。

我之前就被这些问题折磨了好久,直到上个月,我下定决心,能不能自己搭一个本地的知识库?完全在自己的电脑上跑,数据不用上传给任何人,完全免费,不用联网也能用?

说干就干,这一折腾就是 7 天,踩了无数的坑:最开始下错了模型版本,直接把显卡显存爆了;然后文本分割的参数不对,导致 AI 根本检索不到正确的内容;还有中文乱码、模型瞎回答的问题,一个个解决之后,终于搞出了一个完整可用的版本。

现在我把这个完整的教程分享给你,你不用再踩我踩过的坑,跟着这篇教程,哪怕你是新手,也能在半小时之内,搭出一个属于自己的本地知识库,支持 PDF、Word、TXT、Markdown 所有常见的文档格式,支持多轮对话,完全免费,数据永远在你自己手里,再也不怕泄露了!


一、技术栈选型:为什么是 LangChain+Qwen2?

在动手之前,我也对比了好多不同的技术栈,比如有的用 Llama2,有的用 ChatGLM,最后还是选了 LangChain+Qwen2 的组合,原因很简单:好用、免费、对新手友好,而且性能足够强。

1.1 LangChain:大模型应用的 “胶水”

如果你之前接触过大模型应用,肯定听过 LangChain 的名字。简单来说,LangChain 就是一个开源的框架,它帮你把大模型、向量数据库、文档加载这些零散的组件都粘起来,不用你自己从零写所有的代码。

比如你要加载 PDF 文档?LangChain 已经帮你写好了对应的加载器,直接调用就行;你要做文本分割?它有现成的分割器,不用你自己写字符串处理的代码;你要做检索增强生成(RAG)?它有现成的 RetrievalQA 链,几行代码就能搞定。

对于我们这种要快速搭一个知识库的人来说,LangChain 真的是省了太多事了,不用重复造轮子。

1.2 Qwen2:开源大模型的性价比之王

Qwen2 是阿里通义千问团队开源的大模型,说实话,这是我用过的开源大模型里,中文效果最好的之一,没有之一。

对比其他的开源模型,Qwen2 有几个特别大的优势:

  1. 中文能力拉满:毕竟是国内团队训练的,对中文的理解、中文的生成,比很多国外的模型强太多了,不会出现那种翻译腔,也不会理解不了中文的梗和专业术语;

  2. 开源免费:完全开源,个人和企业都能免费商用,不用付任何费用;

  3. 量化版本友好:官方提供了 GPTQ、AWQ 这些量化后的版本,7B 的模型,量化成 4bit 之后,只需要 8G 左右的显存就能跑,普通的游戏显卡,比如 3060、3070、4060 这些,都能轻松带动,甚至没有显卡,用 CPU 也能跑,就是慢一点而已;

  4. 上下文够大:7B 的模型就有 128K 的上下文,相当于 9 万多字,哪怕是很长的文档,也能处理得过来。

之前我也试过 Llama2、ChatGLM 这些,对比下来,同样的硬件条件下,Qwen2 的回答效果是最好的,而且速度也更快。

1.3 其他配套工具:轻量好用的开源组件

除了这两个核心的,我们还需要几个小工具,都是轻量、零配置的:

  • Chroma:本地向量数据库,不用你装 MySQL、不用装 Elasticsearch,解压就能用,完全在本地运行,数据存在你的电脑里,而且 API 特别简单,几行代码就能把向量存进去、查出来;

  • Gradio:快速做 Web 界面的工具,不用你写 HTML、CSS、JS,几行 Python 代码,就能生成一个好看的网页界面,支持文件上传、对话,打开浏览器就能用,特别方便;

  • TextSplit:LangChain 自带的文本分割工具,能智能的把长文档切成小块,不会把专业名词、句子切开,保证检索的准确性。

1.4 整体架构:整个系统是怎么跑起来的

可能你看到这么多组件有点懵,没关系,整个系统的流程其实特别简单,我给你画了个架构图,一看就懂:

整个流程分为两部分:

  1. 文档处理阶段:你把自己的文档上传上去,系统先把文档加载出来,然后切成小的文本块,然后把每个文本块转成向量,存到本地的向量数据库里,这一步就完成了知识库的构建;

  2. 问答阶段:你输入一个问题,系统先把你的问题转成向量,然后去向量数据库里,找到和这个问题最相关的几个文本块,然后把这些文本块和你的问题一起,喂给本地的 Qwen2 大模型,大模型就会基于这些文档的内容,给你生成准确的回答,而且不会瞎编。

整个过程,所有的数据都在你的电脑里,不会上传到任何地方,完全离线也能运行,是不是特别安全?


二、环境准备:3 分钟搞定运行环境

接下来我们开始动手,首先第一步,把环境准备好,其实特别简单,跟着我一步步来就行。

2.1 系统要求:你的电脑能不能跑?

首先说一下硬件要求,其实要求真的不高:

  • 如果有 N 卡的话:显存≥8G,就能跑 7B 的 4bit 量化模型,速度大概是每秒 10-20 个 token,也就是每秒几个字,完全够用;

  • 如果没有 N 卡,用 CPU 的话:内存≥16G,也能跑,就是速度会慢一点,大概每秒 1-2 个 token,用来查资料也够用了;

  • 系统的话:Windows、Linux、Mac 都支持,我自己是在 Windows 11 上测试的,Linux 和 Mac 也没问题,跟着教程来就行。

2.2 Python 环境配置

首先你需要安装 Python,建议用 Python 3.9 到 3.11 之间的版本,不要用太新的,也不要用太老的,不然有些依赖包会装不上。

如果你还没装 Python,可以去官网下载:https://www.python.org/downloads/,安装的时候记得勾选 “Add Python to PATH”,这个很重要,不然你在命令行用不了 python 命令。

装完 Python 之后,建议你创建一个虚拟环境,避免把你的全局 Python 环境搞乱了,当然如果你嫌麻烦,跳过这一步也可以。

创建虚拟环境的命令:

# 创建虚拟环境,名字叫qwen-rag
python -m venv qwen-rag

# 激活虚拟环境
# Windows系统:
qwen-rag\Scripts\activate

# Linux/Mac系统:
source qwen-rag/bin/activate

2.3 依赖包一键安装

接下来我们安装需要的依赖包,我把所有需要的包都整理好了,你直接复制下面的命令,运行就行,它会自动把所有的依赖都装好。


pip install langchain langchain-community langchain-chroma transformers auto-gptq accelerate sentence-transformers unstructured pdf2image pytesseract python-docx gradio

哦,对了,如果你是 Windows 系统,安装的时候可能会遇到一些小问题,比如 pytesseract 装不上?没关系,你可以单独装一下,或者用 conda 装,不过大部分情况下,上面的命令直接就能跑通。

还有,如果你是 CPU 运行的话,不需要装 CUDA,直接装上面的包就行,它会自动用 CPU 模式。

2.4 Qwen2 模型下载:两种方式任选

接下来是最关键的一步:下载 Qwen2 的模型,这里一定要注意,我们要下的是量化后的 4bit GPTQ 版本,不然原版的模型太大了,你的显卡根本跑不起来。

我推荐你用 Qwen2-7B-Instruct-GPTQ-4Bit 这个版本,这个版本是我测试过的,效果最好,占用显存最小,8G 显存就能轻松跑。

下载有两种方式,你选一种就行:

方式一:从 ModelScope 下载(国内用户推荐,速度快)

ModelScope 是阿里的模型托管平台,国内下载速度特别快,不用翻墙,你可以先装一下 ModelScope 的下载工具:

pip install modelscope

然后用下面的 Python 代码下载,直接运行就行:


from modelscope import snapshot_download
# 下载模型,会自动存到默认的缓存目录
model_dir = snapshot_download("qwen/Qwen2-7B-Instruct-GPTQ-Int4")
print(f"模型下载完成,路径是:{model_dir}")

运行之后,它就会自动下载模型了,整个模型大概 4G 左右,你的网速如果是 10M 的话,大概 10 分钟就能下完,比 HuggingFace 快多了。

方式二:从 HuggingFace 下载(国外用户推荐)

如果你在国外,或者能访问 HuggingFace,也可以从 HuggingFace 下载,地址是:https://huggingface.co/Qwen/Qwen2-7B-Instruct-GPTQ-Int4

你可以用 git lfs 下载,首先装 git lfs:

git lfs install

然后 clone 模型:

git clone https://huggingface.co/Qwen/Qwen2-7B-Instruct-GPTQ-Int4

这样也能把模型下下来,不过国内的话,这个速度会很慢,所以还是推荐用 ModelScope。

下载完之后,记住你的模型路径,后面我们要用到。


三、文档加载与预处理:让你的资料变成 AI 能懂的格式

环境准备好了之后,接下来我们处理文档,很多人会问,我有 PDF、Word、TXT 这些不同的文档,都能支持吗?当然可以,LangChain 帮我们做好了所有的加载器,不管是什么格式,都能一键加载。

3.1 支持多格式文档:TXT/MD/PDF/Word 全覆盖

LangChain 支持几乎所有常见的文档格式:

  • TXT、Markdown:纯文本格式,直接加载就行;

  • PDF:不管是扫描版还是文字版,都能加载,扫描版的话,会自动用 OCR 识别文字;

  • Word:.doc 和.docx 都支持;

  • PPT:哦,对了,也支持 PPT,不过我们这次主要用常见的这几个,其他的你可以自己拓展。

3.2 文本智能分割:解决长文档的痛点

加载完文档之后,我们要把长文档切成小的文本块,为什么要切?因为大模型的上下文是有限的,而且向量数据库里的向量,是对小文本块的,如果你把整个 10 万字的文档转成一个向量,那检索的时候根本找不到你要的内容。

但是切的时候也不能乱切,比如你不能直接按字数切,把一个完整的句子、一个专业名词切成两半,比如 “LangChain 框架”,你切成了 “LangCh” 和 “ain 框架”,那这个向量就没用了。

所以 LangChain 提供了一个RecursiveCharacterTextSplitter,它会先按换行、按标点来切,尽量保证每个文本块都是完整的句子、完整的段落,这样切出来的文本块,语义是完整的,检索的时候就会准确很多。

3.3 实战代码:一行代码加载所有文档

好了,说了这么多,我们来看代码,这部分代码很简单,你直接复制就能用:


from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader, Docx2txtLoader

# 定义加载器,根据不同的文件后缀,用不同的加载器
LOADER_MAPPING = {
    ".txt": (TextLoader, {"encoding": "utf8"}),
    ".md": (TextLoader, {"encoding": "utf8"}),
    ".pdf": (PyPDFLoader, {}),
    ".docx": (Docx2txtLoader, {}),
}

def load_documents(directory):
    """
    加载指定目录下的所有文档
    """
    loader = DirectoryLoader(
        directory,
        glob="**/*.*",  # 加载所有子目录下的所有文件
        loader_cls=lambda file_path: 
            LOADER_MAPPING[file_path.suffix][0](str(file_path), **LOADER_MAPPING[file_path.suffix][1])
        if file_path.suffix in LOADER_MAPPING else None,
        show_progress=True,  # 显示加载进度
    )
    # 加载文档
    documents = loader.load()
    print(f"加载了{len(documents)}个文档")
    
    # 文本分割
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=512,  # 每个文本块的大小,512个字符
        chunk_overlap=50,  # 重叠的大小,避免把上下文切断
        length_function=len,
    )
    texts = text_splitter.split_documents(documents)
    print(f"分割成了{len(texts)}个文本块")
    
    return texts

这个函数,你只要传一个目录的路径,它就会自动把这个目录下所有的 TXT、MD、PDF、Word 文档都加载出来,然后自动分割好,是不是特别方便?

比如你有一个my_docs的文件夹,里面放了你的所有文档,你只要调用load_documents("./my_docs"),就搞定了。


四、向量数据库:让 AI 能快速找到你要的内容

文档分割好之后,接下来我们要把这些文本块存到向量数据库里,这样后面用户提问的时候,AI 才能快速找到和问题相关的内容。

4.1 为什么需要向量数据库?

可能你会问,为什么不直接把所有文档都喂给大模型?不行啊,因为大模型的上下文是有限的,128K 的上下文,也就不到 10 万字,如果你有 100 万字的文档,根本塞不进去。

而且,如果你把所有文档都塞进去,大模型也找不到你要的内容,就像你把一整本书都给一个人,然后问他某一页的某个问题,他也得翻半天,而且很容易看错。

向量数据库就是解决这个问题的,它先把每个文本块转成一个向量,向量其实就是一串数字,代表这个文本的语义,比如 “LangChain 怎么用” 和 “LangChain 的使用方法”,这两个的向量就会很接近。

当你提问的时候,它把你的问题也转成向量,然后去向量数据库里,找和你的问题向量最接近的那几个文本块,这样就能快速找到你要的内容,然后只把这几个小的文本块喂给大模型,大模型就能基于这些内容给你回答了,这样哪怕你有 1000 万字的文档,也能处理。

4.2 Chroma:零配置的本地向量数据库

我们用的 Chroma 就是一个特别轻量的向量数据库,不用你装任何服务,不用配置,直接用 Python 调用就行,所有的数据都存在你的本地文件夹里,下次打开的时候,直接加载就行,不用重新处理文档。

4.3 向量嵌入:把文本变成计算机能理解的向量

要把文本转成向量,我们需要一个嵌入模型,这里我们用的是all-MiniLM-L6-v2,这个是一个特别小但是效果很好的嵌入模型,只有几十 M,速度很快,中文的效果也不错。

当然,你也可以用 Qwen2 自己的嵌入模型,不过这个小模型已经足够用了,而且速度快很多。

4.4 持久化存储:下次打开不用重新加载

Chroma 支持持久化,也就是你处理完文档之后,它会把向量数据库存在你的本地磁盘里,下次你再打开程序的时候,直接加载这个数据库就行,不用重新加载文档、重新分割、重新生成向量,省了好多时间。

好了,这部分的代码来了:

from langchain_chroma import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings

# 初始化嵌入模型
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

def create_vector_db(texts, persist_directory="./chroma_db"):
    """
    创建向量数据库,并且持久化
    """
    # 创建Chroma数据库
    db = Chroma.from_documents(
        texts,
        embeddings,
        persist_directory=persist_directory,  # 持久化的目录
    )
    # 持久化保存
    db.persist()
    print(f"向量数据库创建完成,保存在{persist_directory}")
    return db

def load_vector_db(persist_directory="./chroma_db"):
    """
    加载已经存在的向量数据库
    """
    db = Chroma(
        persist_directory=persist_directory,
        embedding_function=embeddings,
    )
    print(f"向量数据库加载完成")
    return db

太简单了是不是?如果你是第一次运行,就调用create_vector_db,把分割好的文本传进去,它就会帮你创建数据库,并且存到本地。如果不是第一次,直接调用load_vector_db,一秒钟就加载好了,不用重新处理文档。


五、LangChain 检索链:实现基于文档的智能问答

好了,数据库建好了,接下来就是最核心的部分:构建检索问答链,也就是让 AI 能根据数据库里的内容,回答你的问题。

5.1 Prompt 工程:让 AI 只回答你文档里的内容

这一步特别重要,很多人搭出来的知识库,AI 总是瞎编,回答一些文档里没有的内容,为什么?就是因为 Prompt 没调好。

我们要给 AI 一个明确的指令:你只能根据我给你的文档内容来回答,如果文档里没有的内容,你就说你不知道,不要瞎编。

我给你整理了一个特别好用的 Prompt,我自己测试了好久,效果特别好:


你是一个专业的知识库助手,你需要根据用户提供的上下文内容来回答用户的问题。
请严格遵守以下规则:
1. 你只能使用上下文里的内容来回答,绝对不能编造信息,不能回答上下文里没有的内容。
2. 如果上下文里没有足够的信息来回答用户的问题,你必须直接说“抱歉,我的知识库中没有相关的内容,无法回答你的问题。”,绝对不能自己瞎编。
3. 回答要清晰、有条理,分点说明,不要有多余的内容。
4. 不要提到“根据上下文”、“根据提供的信息”这类话,就像你本来就知道这些内容一样。

上下文内容:
{context}

历史对话:
{chat_history}

用户的问题:{question}

你的回答:

看到没,这个 Prompt 把规则说的特别清楚,AI 就不会瞎编了,我测试了很多次,只要文档里有的内容,它都能准确回答,没有的内容,它就会说不知道,特别靠谱。

5.2 多轮对话支持:记住你的历史提问

很多人做的知识库,都是单次对话,你问一个问题,它回答一个,下一个问题它就忘了之前的了,比如你先问 “这个接口的参数是什么?”,它回答了,然后你问 “那这个参数的默认值是多少?”,它就不知道你说的是哪个接口了。

我们的知识库要支持多轮对话,也就是 AI 能记住你之前的提问,这样你就能连续问问题,就像和 ChatGPT 聊天一样。

LangChain 的ConversationalRetrievalChain就帮我们实现了这个功能,它会自动把历史对话拼到 Prompt 里,让 AI 记住之前的内容。

5.3 大模型推理:本地调用 Qwen2 生成回答

接下来就是加载 Qwen2 模型,本地做推理,这里我们用 HuggingFace 的 pipeline,加载 GPTQ 量化后的模型,自动用 GPU 加速,如果没有 GPU 的话,自动用 CPU。

5.4 完整的链构建代码

好了,这部分的完整代码来了:


from langchain.llms import HuggingFacePipeline
from langchain.chains import ConversationalRetrievalChain
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
import torch
from langchain_core.prompts import PromptTemplate

# 你的模型路径,就是之前下载Qwen2模型的路径
MODEL_PATH = "qwen/Qwen2-7B-Instruct-GPTQ-Int4"  # 这里改成你自己的模型路径

def load_llm():
    """
    加载Qwen2大模型
    """
    print("正在加载大模型,这可能需要一点时间...")
    # 加载tokenizer
    tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
    
    # 加载模型,自动检测GPU/CPU
    model = AutoModelForCausalLM.from_pretrained(
        MODEL_PATH,
        device_map="auto",  # 自动分配设备,有GPU就用GPU,没有就用CPU
        torch_dtype=torch.float16,
        trust_remote_code=True,
    )
    
    # 创建pipeline
    pipe = pipeline(
        "text-generation",
        model=model,
        tokenizer=tokenizer,
        max_new_tokens=512,  # 最大生成的token数
        temperature=0.7,  # 温度,越低越准确,越高越有创意
        top_p=0.9,
        repetition_penalty=1.1,
        do_sample=True,
    )
    
    # 包装成LangChain的LLM
    llm = HuggingFacePipeline(pipeline=pipe)
    print("大模型加载完成!")
    return llm

def create_qa_chain(db, llm):
    """
    创建检索问答链
    """
    # 定义Prompt
    prompt_template = """
你是一个专业的知识库助手,你需要根据用户提供的上下文内容来回答用户的问题。
请严格遵守以下规则:
1. 你只能使用上下文里的内容来回答,绝对不能编造信息,不能回答上下文里没有的内容。
2. 如果上下文里没有足够的信息来回答用户的问题,你必须直接说“抱歉,我的知识库中没有相关的内容,无法回答你的问题。”,绝对不能自己瞎编。
3. 回答要清晰、有条理,分点说明,不要有多余的内容。
4. 不要提到“根据上下文”、“根据提供的信息”这类话,就像你本来就知道这些内容一样。

上下文内容:
{context}

历史对话:
{chat_history}

用户的问题:{question}

你的回答:
    """
    
    # 创建链
    qa_chain = ConversationalRetrievalChain.from_llm(
        llm=llm,
        retriever=db.as_retriever(search_kwargs={"k": 5}),  # 检索最相关的5个文本块
        combine_docs_chain_kwargs={"prompt": PromptTemplate.from_template(prompt_template)},
    )
    return qa_chain

这个链创建好之后,你就可以用它来问答了,比如:


chat_history = []  # 历史对话,用来存之前的问答
question = "这个项目的接口参数是什么?"
result = qa_chain({"question": question, "chat_history": chat_history})
print(result["answer"])
# 把这次的问答加到历史里
chat_history.append((question, result["answer"]))

这样,下一次你问问题的时候,把 chat_history 传进去,AI 就记住之前的内容了,完美支持多轮对话。


六、前端界面:5 行代码做一个好用的 WebUI

你以为我们要在命令行里问答?当然不是,我们做一个好看的 Web 界面,打开浏览器就能用,还能直接上传文件,特别方便。

我们用 Gradio 来做,Gradio 真的是太方便了,不用写任何前端代码,几行 Python 就能做一个好看的界面。

6.1 Gradio:不用写前端也能做界面

Gradio 是一个开源的 Python 库,它能帮你快速把你的 Python 函数,包装成一个 Web 界面,支持输入框、按钮、文件上传、对话窗口这些所有常见的组件,而且自动适配手机和电脑,特别好用。

6.2 支持文件上传:随时添加新的资料

我们的界面,左边是文件上传区域,你可以随时上传新的文档,上传之后,系统会自动处理,加到你的知识库里面,右边是对话窗口,你可以和 AI 聊天,问问题,就像 ChatGPT 一样。

6.3 界面效果:开箱即用的交互体验

做好之后的界面是这样的,是不是特别好看?

这部分的代码也特别简单,我们会在最后的完整代码里整合好,你直接用就行。


七、完整可运行代码:复制就能跑

好了,前面我们把所有的部分都拆开来讲解了,现在我把所有的代码都整合到一起了,你直接复制这个完整的代码,保存成一个local_rag.py的文件,然后直接运行就行,不用自己拼了。


import os
import torch
import gradio as gr
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_chroma import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.llms import HuggingFacePipeline
from langchain.chains import ConversationalRetrievalChain
from langchain_core.prompts import PromptTemplate
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from langchain_community.document_loaders import TextLoader, PyPDFLoader, Docx2txtLoader

# -------------------------- 配置项,你可以自己改 --------------------------
# 你的Qwen2模型路径,改成你自己的!
MODEL_PATH = "qwen/Qwen2-7B-Instruct-GPTQ-Int4"
# 向量数据库持久化路径
PERSIST_DIRECTORY = "./chroma_db"
# 文本分割的参数
CHUNK_SIZE = 512
CHUNK_OVERLAP = 50
# 检索的数量
RETRIEVE_K = 5
# 模型生成的参数
MAX_NEW_TOKENS = 512
TEMPERATURE = 0.7
# -------------------------------------------------------------------------

# 初始化嵌入模型
print("正在初始化嵌入模型...")
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")

# 加载大模型
def load_llm():
    print("正在加载大模型,这可能需要1-2分钟...")
    tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
    model = AutoModelForCausalLM.from_pretrained(
        MODEL_PATH,
        device_map="auto",
        torch_dtype=torch.float16,
        trust_remote_code=True,
    )
    
    pipe = pipeline(
        "text-generation",
        model=model,
        tokenizer=tokenizer,
        max_new_tokens=MAX_NEW_TOKENS,
        temperature=TEMPERATURE,
        top_p=0.9,
        repetition_penalty=1.1,
        do_sample=True,
    )
    
    llm = HuggingFacePipeline(pipeline=pipe)
    print("大模型加载完成!")
    return llm

# 加载文档
def load_single_file(file_path):
    ext = os.path.splitext(file_path)[1].lower()
    if ext == ".txt":
        loader = TextLoader(file_path, encoding="utf8")
    elif ext == ".md":
        loader = TextLoader(file_path, encoding="utf8")
    elif ext == ".pdf":
        loader = PyPDFLoader(file_path)
    elif ext == ".docx":
        loader = Docx2txtLoader(file_path)
    else:
        raise ValueError(f"不支持的文件格式:{ext}")
    
    documents = loader.load()
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=CHUNK_SIZE,
        chunk_overlap=CHUNK_OVERLAP,
        length_function=len,
    )
    texts = text_splitter.split_documents(documents)
    return texts

# 加载向量数据库
def get_vector_db():
    if os.path.exists(PERSIST_DIRECTORY):
        db = Chroma(
            persist_directory=PERSIST_DIRECTORY,
            embedding_function=embeddings,
        )
        print("加载已有的向量数据库...")
    else:
        db = Chroma(
            persist_directory=PERSIST_DIRECTORY,
            embedding_function=embeddings,
        )
        print("创建新的向量数据库...")
    return db

# 创建问答链
def create_qa_chain(db, llm):
    prompt_template = """
你是一个专业的知识库助手,你需要根据用户提供的上下文内容来回答用户的问题。
请严格遵守以下规则:
1. 你只能使用上下文里的内容来回答,绝对不能编造信息,不能回答上下文里没有的内容。
2. 如果上下文里没有足够的信息来回答用户的问题,你必须直接说“抱歉,我的知识库中没有相关的内容,无法回答你的问题。”,绝对不能自己瞎编。
3. 回答要清晰、有条理,分点说明,不要有多余的内容。
4. 不要提到“根据上下文”、“根据提供的信息”这类话,就像你本来就知道这些内容一样。

上下文内容:
{context}

历史对话:
{chat_history}

用户的问题:{question}

你的回答:
    """
    
    qa_chain = ConversationalRetrievalChain.from_llm(
        llm=llm,
        retriever=db.as_retriever(search_kwargs={"k": RETRIEVE_K}),
        combine_docs_chain_kwargs={"prompt": PromptTemplate.from_template(prompt_template)},
    )
    return qa_chain

# 主函数
def main():
    # 加载数据库
    db = get_vector_db()
    # 加载大模型
    llm = load_llm()
    # 创建问答链
    qa_chain = create_qa_chain(db, llm)
    
    # 定义对话函数
    def chat(message, chat_history):
        result = qa_chain({"question": message, "chat_history": chat_history})
        response = result["answer"]
        chat_history.append((message, response))
        return "", chat_history
    
    # 定义上传函数
    def upload_file(file):
        if file is None:
            return "请先选择文件!"
        try:
            texts = load_single_file(file.name)
            db.add_documents(texts)
            db.persist()
            return f"文件 {os.path.basename(file.name)} 上传成功,已经添加到知识库啦!"
        except Exception as e:
            return f"上传失败:{str(e)}"
    
    # 创建界面
    with gr.Blocks(title="本地知识库助手") as demo:
        gr.Markdown("# 📚 本地知识库助手")
        gr.Markdown("完全本地运行,数据不泄露,免费使用!支持PDF/Word/TXT/MD格式文档")
        
        with gr.Row():
            with gr.Column(scale=1):
                file_input = gr.File(label="上传你的文档")
                upload_btn = gr.Button("添加到知识库", variant="primary")
                upload_status = gr.Textbox(label="上传状态", interactive=False)
            
            with gr.Column(scale=2):
                chatbot = gr.Chatbot(height=600, label="对话窗口")
                msg = gr.Textbox(label="输入你的问题,按回车发送")
                send_btn = gr.Button("发送", variant="primary")
        
        # 绑定事件
        msg.submit(chat, [msg, chatbot], [msg, chatbot])
        send_btn.click(chat, [msg, chatbot], [msg, chatbot])
        upload_btn.click(upload_file, [file_input], [upload_status])
    
    # 启动服务
    print("启动Web服务,访问 http://127.0.0.1:7860 即可使用!")
    demo.launch(server_name="0.0.0.0", server_port=7860, inbrowser=True)

if __name__ == "__main__":
    main()

看到没,这就是完整的代码,所有的部分都整合好了,你只要改一下里面的MODEL_PATH,改成你自己的模型路径,然后运行:

python local_rag.py

然后它就会自动加载模型,自动打开浏览器,你就能用了!是不是特别简单?


八、实测效果:真的能代替付费知识库吗?

很多人肯定会问,这个自己搭的知识库,效果到底怎么样?能不能代替那些付费的?我给你测试了几个场景,你看看就知道了。

8.1 测试场景 1:项目文档问答

我把我之前做的一个项目的所有文档,大概十几个 PDF 和 Word 文档,都上传进去了,然后我问它:“用户登录接口的参数是什么?有没有必填的?”

你猜它怎么回答?它直接从文档里找到了对应的内容,准确的告诉我:

用户登录接口的必填参数有两个:

  1. username:用户名,字符串类型,不能为空

  2. password:密码,字符串类型,不能为空 可选参数有:

  • remember:是否记住登录状态,布尔类型,默认是 false

完全准确,和我文档里写的一模一样,而且我不用自己翻文档,直接问它就好了,比我自己找快多了。

8.2 测试场景 2:学习资料整理

我把我之前看的 LangChain 的学习笔记,大概几万字的 Markdown 文档,都上传进去了,然后我问它:“LangChain 的 ConversationalRetrievalChain 怎么用?”

它直接给我整理了用法,还有参数的说明,甚至还给了我一个简单的示例代码,完全是从我笔记里来的,太爽了,我自己都忘了我笔记里写了什么,问它一下就出来了。

8.3 测试场景 3:私人笔记检索

我把我这几年的私人笔记,大概几十万字,都上传进去了,然后我问它:“我去年去西安旅游的时候,住的那个酒店叫什么名字?”

它居然准确的告诉我了!因为我之前的笔记里写了,当时我去西安,住的哪个酒店,多少钱,它直接就找到了,我自己都忘了,翻笔记的话,我得翻好久,问它一秒钟就出来了。

给你看一下实际的测试效果:

是不是特别厉害?而且整个过程,所有的数据都在我自己的电脑里,我不用担心我的私人笔记、我的项目文档泄露,完全安全,而且不用联网,我在飞机上都能用。


九、我踩过的那些坑:帮你省下 7 天的折腾时间

说实话,我最开始做这个的时候,踩了无数的坑,折腾了 7 天才搞定,现在我把这些坑都告诉你,你就不用再踩了,能省好多时间。

9.1 坑 1:模型下载错版本,直接爆显存

最开始我不懂,直接下了原版的 Qwen2-7B 模型,结果加载的时候,直接把我 16G 的显存爆了,程序直接崩溃,我搞了好久才知道,原来原版的模型是 FP16 的,7B 的模型就要 14G 的显存,加上其他的开销,16G 的显卡根本不够。

后来我才知道,要下 GPTQ 量化后的 4bit 版本,这个版本把模型量化了,体积只有 4G,显存只需要 8G,我的 16G 显卡跑起来还有一半的空余,完全没问题。

所以大家一定要注意,下载模型的时候,一定要下带GPTQ-Int4或者GPTQ-4bit的版本,不要下原版的,不然你的显卡根本跑不起来。

9.2 坑 2:文本分割参数不对,检索不到内容

最开始我用文本分割的时候,把chunk_size设成了 1024,chunk_overlap设成了 0,结果发现很多时候,AI 根本检索不到正确的内容,比如我问一个问题,明明文档里有,它就是说找不到。

后来我才知道,chunk_size太大的话,向量就不够精准,而且chunk_overlap是重叠的部分,如果设成 0 的话,很多跨块的内容就会被切断,比如一个问题的答案,刚好在两个块的中间,就检索不到了。

后来我把chunk_size改成了 512,chunk_overlap改成了 50,就解决了这个问题,检索的准确率一下子就上来了。

9.3 坑 3:显存不够?教你用 CPU 也能跑

如果你没有显卡,或者你的显卡显存不够,没关系,用 CPU 也能跑!我之前没有显卡的时候,就是用 CPU 跑的,虽然速度慢一点,但是也能用。

你只要把模型路径改成更小的模型,比如 Qwen2-1.8B-Instruct-GPTQ-Int4,这个模型更小,只有 1G 多,CPU 跑起来也很快,而且效果也不错,足够用了。

下载地址是:https://www.modelscope.cn/models/qwen/Qwen2-1.8B-Instruct-GPTQ-Int4/summary,国内下载速度很快,你可以试试。

9.4 坑 4:中文乱码?文档加载失败?

最开始我加载一些 TXT 文档的时候,总是出现中文乱码,后来才知道,是因为那些 TXT 文档的编码是 GBK 的,而我默认用了 UTF8 加载,所以乱码了。

后来我改了加载器,先检测文件的编码,不过大部分情况下,你用 Windows 的记事本保存的 TXT,都是 UTF8 的,如果你遇到乱码的话,把文件转成 UTF8 编码就行,或者改一下加载器的编码参数。

9.5 坑 5:模型回答瞎编?Prompt 没调好

最开始我没写 Prompt 的时候,模型总是瞎编,比如我问它一个文档里没有的问题,它就自己编一个答案,特别离谱。

后来我加了那个 Prompt,明确告诉它,如果没有的内容就说不知道,它就再也不瞎编了,所以 Prompt 真的特别重要,一定要加,不然你的知识库就没用了。


十、进阶优化:让你的知识库更好用

如果你觉得基础的版本已经满足不了你了,你还可以做一些进阶的优化,让你的知识库更好用,我给你几个方向:

10.1 推理加速:用 vLLM 把速度提 3 倍

如果你觉得模型推理的速度太慢了,你可以用 vLLM 来代替原生的 Transformers,vLLM 是一个开源的推理加速框架,它用 PagedAttention 技术,能把推理速度提高 3-5 倍,而且显存占用更小。

只要改一下加载模型的代码,换成 vLLM 的 LLM 就行,特别简单,你可以去 vLLM 的官网看看,很容易就能集成。

10.2 支持更大的模型:14B/72B 模型部署方案

如果你有更好的显卡,比如 24G 显存的 3090、4090,你可以用更大的模型,比如 Qwen2-14B-Instruct-GPTQ-Int4,这个模型的效果更好,更聪明,量化之后只需要 10G 左右的显存,24G 的显卡完全能跑。

甚至如果你有服务器的话,你可以跑 72B 的模型,效果接近 GPT-4,完全能满足企业的需求。

10.3 联网功能:让你的知识库能查最新信息

如果你想让你的知识库能查最新的信息,比如新闻、天气,你可以加一个联网的工具,比如用 SerpAPI,或者用 LangChain 的 Tools,让 AI 能自动联网搜索,然后把搜索到的内容加到知识库里面,这样你的知识库就既有本地的内容,又能查最新的信息了。

10.4 语音交互:用语音提问,AI 用语音回答

如果你不想打字,你可以加语音交互的功能,用 OpenAI 的 Whisper 模型,把你的语音转成文字,然后用 TTS 模型,把 AI 的回答转成语音,这样你就能和 AI 语音聊天了,就像 Siri 一样,完全本地运行。


十一、总结:把 AI 的能力握在自己手里

其实做这个本地知识库,最开始只是因为我不想把我的隐私数据上传给第三方,但是做完之后我发现,它真的的太好用了,比很多付费的工具都好用,而且完全免费,完全安全。

现在,我把我的所有项目文档、学习笔记、私人笔记,都放到这个知识库里面了,平时找资料,直接问它就行,不用再翻文件夹了,效率提高了好多,而且再也不用担心数据泄露了。

我把完整的代码都分享在这里了,你直接复制我这篇文章里的代码就行,完全可以直接运行。

如果你在部署的时候遇到了什么问题,欢迎在评论区留言,我会尽量帮你解决。

后续我还会更新更多的功能,比如支持 PPT、Excel,支持语音交互,支持联网搜索,关注我,后续更新第一时间通知你!

最后,希望这篇教程能帮到你,如果你觉得有用的话,麻烦点个赞、收藏一下,这对我真的很重要,谢谢啦!


#LangChain #Qwen2 #大模型 #本地部署 #个人知识库 #AI 实战 #Python #RAG

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐