基于大语言模型的本地知识库问答离线部署
本文详细介绍了基于大语言模型(LLM)的本地知识库问答系统的离线部署方案。文章涵盖了大语言模型基础、Prompt 工程、RLHF 技术及 RAG 架构原理。通过 LangChain 和 ChromaDB 实现了文档加载、切片、向量化存储及检索功能,并结合 Llama2 模型完成了问答生成。内容包含完整的 Python 代码示例、环境配置指南及常见问题排查方法,旨在帮助开发者快速搭建安全、私有的智能问答系统,无需联网即可处理敏感数据。

本文详细介绍了基于大语言模型(LLM)的本地知识库问答系统的离线部署方案。文章涵盖了大语言模型基础、Prompt 工程、RLHF 技术及 RAG 架构原理。通过 LangChain 和 ChromaDB 实现了文档加载、切片、向量化存储及检索功能,并结合 Llama2 模型完成了问答生成。内容包含完整的 Python 代码示例、环境配置指南及常见问题排查方法,旨在帮助开发者快速搭建安全、私有的智能问答系统,无需联网即可处理敏感数据。

知识库问答系统是一种应用广泛的解决方案,可以在企业文档管理、智能客服、技术支持等多个领域发挥重要作用。以往的系统通常基于固定规则匹配、传统相似度检索或者 Seq2Seq 模型构建。这类系统的开发成本较高,修改逻辑较为麻烦,尤其在数据准备阶段需要耗费大量精力进行标注和清洗。
随着大语言模型(LLM)技术的突破,这一局面得到了根本性改变。在 LLM 的加持下,无论是系统架构的编写还是数据的预处理工作,工作量都大大减少。开发者可以使用较少的代码实现非常智能的知识库问答系统,显著降低了技术门槛。
本文将详细介绍如何使用开源大语言模型,完全离线部署一套本地知识库问答系统,确保数据隐私安全的同时实现智能化交互。
语言模型是自然语言处理领域中一个非常重要的概念,其核心任务是评估一串有序字符构成句子的概率。比较经典的模型包括 BERT 系列、GPT 系列等。而现在业界常说的大语言模型更多是指以 GPT 为代表的生成式预训练 Transformer 模型。
以往的 GPT 模型训练方式相对简单,主要是根据一个句子的前 n 个词,预测第 n+1 个词。这种看似简单的'文字接龙'方式,却赋予了 GPT 模型极大的泛化能力和自由度,使其能够理解上下文语义并生成连贯文本。
Prompt 工程是通过设计输入指令来引导模型输出特定结果的技术。例如我们可以直接把 GPT 应用在情感分析任务上,只需要设置一下输入的前缀:
这部电影真烂的情绪是
然后 GPT 就可以预测下一个词以及后续内容,最后得到结果'消极'。或者我们可以采用少样本学习的方式:
这部电影真烂,消极
太好看了,
然后 GPT 就可以预测出'积极'。输入的内容还可以更长,通过提供多个示例让模型学习模式:
这部电影真烂,消极
xxxx,积极
xxxx,消极
太好看了,
上面这种通过改变输入内容让 GPT 输出特定结果的方式就叫做 Prompt 工程。通过精心修改 Prompt,我们可以让 GPT 完成更复杂的工作,比如多轮对话、翻译、代码生成等。下面以翻译为例,我们只需要输入 Prompt:
中文:不要温顺的走进那个良夜
英文:
这样就可以让 GPT 输出对应的英文句子。
在实际使用中,输入的 Prompt 往往包含部分内容是动态变化的,这部分可以用一些占位符来预留位置。例如:
这部电影真烂,消极
{sentence},
我们只需要输入具体的句子 s,然后用字符串替换的方式将 prompt 中的 {sentence} 替换掉,就得到了最终输入给 GPT 的完整 Prompt。上面这种带有占位符的 Prompt 结构被称为模板,它极大地提高了 Prompt 的可复用性。
早期的 GPT 模型主要采用无监督学习,即前面描述的根据前 n 个词预测下一个词。而现在主流的类 Chat 模型都是使用无监督预训练 + RLHF(基于人类反馈的强化学习)的方式。
首先无监督学习部分和早期 GPT 是一样的,做文字接龙或者类似句子接龙等任务,建立基础的语料理解能力。而 RLHF 则是在此基础上,引入人类反馈机制,在这个过程中会有人类老师帮助 GPT 纠正输出,让 GPT 的输出更符合人类的对话习惯和价值取向。也正是 RLHF 造就了 ChatGPT 这样优秀的对话模型。
在 BERT 时代,还有一种基于向量相似度的问答系统。这种系统架构非常简单,但是前期数据的收集和处理需要花费较多的时间。这种系统与本文要讨论的系统有许多相似的地方。
首先我们需要收集大量的标准问答对,例如:
江西是省会是哪?南昌
北京是南方还是北方?北方
...?xx
收集完成后,使用 BERT 等模型提取问题的特征向量,然后存储到向量数据库中。在用户提问时,提取问题的特征向量,并在数据库中检索出最相似的问题,返回对应的答案。

基于检索的传统问答系统有几个明显的问题:因为数据必须严格符合问答对的形式,因此数据收集需要消耗大量时间整理;另外回答的内容是固定的,因此输入同一个问题,会得到相同的结果。在某些系统中,这是个优点,但是如果是客服系统或咨询系统,过于机械的回答体验较差。
基于大语言模型的知识库问答(RAG 架构)也需要借助向量数据库,但流程更加灵活。下面是具体实现步骤:
首先是数据收集,我们不再需要严格的问答对形式,只需要处理干净的文档形式,这样就减少了大量的数据标注工作。
因为文档通常比较大,而 Embedding 模型有上下文长度的限制,因此需要将文档拆分成 Embedding 模型支持的大小。同时,某一个问题可能出现在文档的多个位置,因此检索时返回 k 个相关文档段,以便 LLM 综合判断。
最后利用 Prompt 工程 + LLM 完成最后的问答生成。下图展示了典型的 RAG 流程:

首先需要选择一个合适的 LLM。现在开源 LLM 百花齐放,可以选择的方案有很多,包括 ChatGLM、Llama2、Qwen 等。这里选择 Llama2 作为演示模型。而 Llama2 的部署方式也是多种多样,为了简化环境依赖,这里使用 llama-cpp-python 进行推理部署,我们需要安装该模块:
pip install llama-cpp-python
另外还需要编译转换 Llama2 的模型文件为 GGUF 格式。具体方式可以参考官方文档或社区教程。
然后只需要编写下面的代码就可以运行 Llama2 进行对话:
from llama_cpp import Llama
model_path = "llama-2-13b-chat.Q4_0.gguf"
llm = Llama(
model_path=model_path,
n_ctx=2048,
chat_format="llama-2"
)
response = llm('Human:你好啊\nAssistant:', stop=['Human:'])
print(response['choices'][0]['text'])
文档处理对应 3.2 中的前三步,我们可以使用 LangChain 框架来完成这步操作,代码如下:
import os
from langchain.document_loaders import DirectoryLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
# 加载 embedding 模型配置
embedding_model_dict = {
"ernie-tiny": "nghuyong/ernie-3.0-nano-zh",
"ernie-base": "nghuyong/ernie-3.0-base-zh",
"text2vec": "GanymedeNil/text2vec-large-chinese",
"text2vec2": "uer/sbert-base-chinese-nli",
"text2vec3": "shibing624/text2vec-base-chinese",
}
def load_documents(directory="documents"):
"""
加载指定目录下的文件,并进行拆分
:param directory: 文档存放路径
:return: 拆分后的文档列表
"""
loader = DirectoryLoader(directory)
documents = loader.load()
# 设置切片大小和重叠部分
text_spliter = CharacterTextSplitter(chunk_size=256, chunk_overlap=0)
split_docs = text_spliter.split_documents(documents)
return split_docs
def load_embedding_model(model_name="text2vec3"):
"""
加载 embedding 模型
:param model_name: 模型名称
:return: Embedding 对象
"""
encode_kwargs = {"normalize_embeddings": False}
model_kwargs = {"device": "cuda:0"}
return HuggingFaceEmbeddings(
model_name=embedding_model_dict[model_name],
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
def store_chroma(docs, embeddings, persist_directory="VectorStore"):
"""
将文档向量化,存入向量数据库
:param docs: 文档列表
:param embeddings: 嵌入模型
:param persist_directory: 持久化路径
:return: 向量数据库实例
"""
db = Chroma.from_documents(docs, embeddings, persist_directory=persist_directory)
db.persist()
return db
# 初始化流程
embeddings = load_embedding_model('text2vec3')
if not os.path.exists('VectorStore'):
documents = load_documents()
db = store_chroma(documents, embeddings)
else:
db = Chroma(persist_directory='VectorStore', embedding_function=embeddings)
我们在当前目录下准备一个 documents 文件夹,在里面放入我们的 txt 文档即可。在 LangChain 里面内置了包括 json、csv、PDF 等文档处理的类,这里可以根据自己的需求修改 load_documents 函数。
这里需要注意两个关键参数:
text_spliter = CharacterTextSplitter(chunk_size=256, chunk_overlap=0)
上面这句是拆分文档的代码。其中 chunk_size 是每段的长度,而 chunk_overlap 则是两个段之间重叠的大小。chunk_size 可以根据电脑性能、Embedding 模型上下文限制、LLM 上下文限制来确定。如果设置过小,可能会丢失上下文信息;如果设置过大,可能会超出模型限制。chunk_overlap 可以选 0 或 0.1 * chunk_size,适当的重叠有助于保持语义的连续性。
另外上面加载的模型都是中文的 Embedding 模型,如果有其它语言需求,可以选择用多语言的 Bert 作为 Embedding。具体参考 sentence-transformers 可用的模型列表。
在输入给 LLM 之前,需要先检索相关的文档。比如我存储了'塞尔达王国之泪'的攻略,使用下面代码搜索'究极手'相关内容:
docs = db.similarity_search('究极手是干啥用的', k=5)
for doc in docs:
print(doc)
得到如下输出:
page_content='尝试用右手打开神殿大门但以失败告终,这是一个自称'劳鲁'的灵体出现在了林克的身后。劳鲁告诉林克需要去岛上的神庙中回复手臂的力量才能打开大门。...' metadata={'source': 'documents\王国之泪游侠攻略.txt'}
...
基于 LLM 的知识库问答需要一个特殊的 Prompt 模板来完成,具体如下:
根据下面的上下文(context)内容回答问题。
如果你不知道答案,就回答不知道,不要试图编造答案。
答案最多 3 句话,保持答案简洁。
总是在答案结束时说'谢谢你的提问!'
{context}
问题:{question}
注意:原稿中'简介'应为'简洁',此处已修正。
然后需要将检索到的文档注入到如下的 Prompt 里面,代码如下:
template = """
根据下面的上下文(context)内容回答问题。
如果你不知道答案,就回答不知道,不要试图编造答案。
答案最多 3 句话,保持答案简洁。
总是在答案结束时说'谢谢你的提问!'
{context}
问题:{question}"""
query = "究极手是干啥用的"
docs = db.similarity_search(query, k=5)
context = "\n".join([f"{idx + 1}.{doc.page_content}" for (idx, doc) in enumerate(docs)])
prompt = template.format(context=context, question=query)
print(prompt)
最后只需要将这个 Prompt 输入给 LLM 即可:
response = llm(f'Human:{prompt}\nAssistant:', stop=['Human:'])
print(response['choices'][0]['text'])
最后输出结果如下:
嗯,究极手是一种非常实用的能力,可以用于装配和拆解各种物品,以及对盾牌和武器进行合成拆卸。你可以用它来组合材料、砍树、打碎石块等,也可以在各种情况下进行优化和适应。
可以看到回答结果部分是正确的,且没有产生幻觉。
为了保证系统稳定运行,建议按照以下步骤配置环境:
pip install langchain chromadb llama-cpp-python sentence-transformers
如果在运行过程中遇到 OOM(Out Of Memory)错误,可以尝试以下方法:
n_ctx 参数,降低上下文窗口。如果检索到的文档不相关,可以调整以下参数:
k 值,检索更多候选文档。chunk_size,避免切分过碎导致语义丢失。如果 LLM 回答偏离事实,检查 Prompt 模板:
在 LLM 的加持下,知识库问答的实现变得非常简单。而且现在有诸如 LangChain、LlamaIndex 之类的框架集成了许多与 LLM 相关的操作,极大提升了开发效率。另外现在 LLM 的开源社区非常活跃,出现了诸如 Llama2、Mistral-7B 等优秀的开源模型。不管是参数量、资源消耗、训练速度等都降低到在消费级显卡上可完成的范围。
通过本文的介绍,读者可以掌握构建本地离线知识库问答系统的基本流程,包括文档处理、向量化检索、Prompt 构造及模型调用。这种方案不仅保护了数据隐私,还避免了 API 调用的费用,非常适合企业内部知识管理和私有化部署场景。未来随着模型能力的进一步提升,此类系统的准确性和响应速度还将继续优化。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online