# 使用 LangChain + Ollama 构建本地私有知识库问答系统
在企业日常运营中,我们经常需要处理大量的内部文档、技术手册、会议记录等文本数据。传统的关键词搜索方式往往无法准确理解用户的真实查询意图,导致检索结果不尽如人意。随着大语言模型技术的发展,基于语义理解的智能问答系统成为了可能。
然而,将敏感的内部文档上传到第三方云服务存在数据泄露风险,这对于重视信息安全的企业来说是不可接受的。因此,构建一个完全本地化部署的私有知识库问答系统成为了许多技术团队的迫切需求。
本文将详细介绍如何使用开源工具 LangChain 和 Ollama,在本地环境下搭建一个无需联网即可使用的私有知识库问答系统。整个系统完全运行在本地服务器上,所有数据都不会离开企业网络,既保证了问答质量,又确保了数据安全。
## 面临的问题
在实际应用场景中,我们面临以下几个核心挑战:
**数据安全问题**:企业内部的敏感文档(如代码、技术方案、财务数据等)不适合上传到第三方云服务进行处理。传统的 SaaS 问答产品虽然使用便捷,但无法满足企业对数据安全的严格要求。
**部署成本问题**:商业闭源的 LLM API 虽然效果出色,但按调用次数计费的方式在大量文档场景下成本较高。本地部署开源模型可以有效控制成本。
**离线运行需求**:某些企业内网环境完全隔离外网,无法使用云端服务。这要求我们必须构建一套完全离线运行的解决方案。
**中文语义理解**:开源模型的中文能力参差不齐,需要选择合适的模型并在本地进行部署优化,以确保问答效果。
## 环境准备与安装步骤
首先,我们需要准备运行环境和安装必要的软件组件。本次演示使用的环境配置如下:
– 操作系统:Ubuntu 22.04 LTS
– 内存:16GB 以上(建议 32GB)
– 硬盘:至少 50GB 可用空间(用于存储模型和向量数据库)
– Python:3.10 或更高版本
### 第一步:安装 Ollama
Ollama 是一个轻量级的本地 LLM 运行时,支持在本地机器上运行各种开源大语言模型。它的使用方式类似于 Docker,通过简单的命令即可下载和运行模型。
在终端中执行以下命令安装 Ollama:
“`bash
curl -fsSL https://ollama.com/install.sh | sh
“`
安装完成后,我们下载一个支持中文的模型。考虑到本地运行的实际需求,推荐使用 qwen2.5:7b 或 llama3:8b 这两个模型,它们在中文理解和生成方面表现不错,同时对硬件要求相对合理:
“`bash
ollama pull qwen2.5:7b
“`
这个下载过程可能需要一些时间,取决于网络速度。模型文件通常在 4-7GB 左右。
### 第二步:安装 Python 依赖
接下来安装 LangChain 及其相关组件。我们需要使用 langchain、langchain-community 以及用于向量存储的 chroma:
“`bash
pip install langchain langchain-community langchain-chroma pypdf python-dotenv
“`
如果计划处理多种文档格式(如 Word、Excel),还可以安装相应的解析库:
“`bash
pip install python-docx pandas openpyxl
“`
### 第三步:文档加载与处理
LangChain 提供了丰富的文档加载器,可以处理 PDF、Word、Markdown 等各种格式的文档。以下是加载 PDF 文档的示例代码:
“`python
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 加载 PDF 文档
loader = PyPDFLoader(“example.pdf”)
documents = loader.load()
# 使用递归字符分割器将文档切分成更小的片段
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
)
chunks = text_splitter.split_documents(documents)
print(f”文档已切分成 {len(chunks)} 个片段”)
“`
对于其他格式的文档,LangChain 提供了对应的加载器:
“`python
# 加载 Word 文档
from langchain_community.document_loaders import Docx2txtLoader
loader = Docx2txtLoader(“example.docx”)
# 加载 Markdown 文件
from langchain_community.document_loaders import UnstructuredMarkdownLoader
loader = UnstructuredMarkdownLoader(“example.md”)
“`
### 第四步:创建向量嵌入
为了实现语义搜索,我们需要将文本转换为向量表示。这一步使用嵌入模型来完成:
“`python
from langchain_community.embeddings import OllamaEmbeddings
# 使用 Ollama 提供的嵌入模型
embeddings = OllamaEmbeddings(
model=”nomic-embed-text”,
base_url=”http://localhost:11434″
)
# 测试嵌入效果
test_vector = embeddings.embed_query(“这是一个测试查询”)
print(f”嵌入向量维度: {len(test_vector)}”)
“`
需要注意的是,嵌入模型和聊天模型是分开安装的。如果尚未下载嵌入模型,需要先执行:
“`bash
ollama pull nomic-embed-text
“`
### 第五步:构建向量数据库
Chroma 是一个轻量级的向量数据库,专门为 LangChain 设计,支持本地持久化存储:
“`python
from langchain_chroma import Chroma
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
# 加载并处理文档
loader = PyPDFLoader(“company_manual.pdf”)
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = text_splitter.split_documents(documents)
# 创建嵌入和向量数据库
embeddings = OllamaEmbeddings(model=”nomic-embed-text”)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=”./chroma_db”
)
print(f”向量数据库已创建,包含 {len(chunks)} 个文档片段”)
“`
### 第六步:构建问答链
LangChain 提供了 RetrievalQA 链,可以方便地将检索和问答结合起来:
“`python
from langchain_community.chat_models import ChatOllama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
# 初始化本地聊天模型
llm = ChatOllama(
model=”qwen2.5:7b”,
temperature=0.7,
base_url=”http://localhost:11434″
)
# 定义自定义提示词模板
custom_prompt_template = “””请根据以下参考文档回答用户问题。
如果参考文档中没有相关信息,请如实告知用户。
参考文档:
{context}
用户问题:{question}
请给出详细、准确的回答:”””
prompt = PromptTemplate(
template=custom_prompt_template,
input_variables=[“context”, “question”]
)
# 创建问答链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type=”stuff”,
retriever=vectorstore.as_retriever(search_kwargs={“k”: 3}),
chain_type_kwargs={“prompt”: prompt}
)
“`
### 第七步:运行问答
现在可以向系统提问了:
“`python
# 执行问答
query = “公司的年假政策是什么?”
result = qa_chain({“query”: query})
print(“问题:”, query)
print(“回答:”, result[“result”])
“`
## 完整代码示例
以下是一个完整的、可直接运行的示例,展示了从文档加载到问答的完整流程:
“`python
#!/usr/bin/env python3
“””
本地私有知识库问答系统
使用 LangChain + Ollama 构建
“””
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_community.chat_models import ChatOllama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
class LocalKnowledgeBase:
“””本地知识库问答系统类”””
def __init__(self, model_name=”qwen2.5:7b”, embedding_model=”nomic-embed-text”):
self.model_name = model_name
self.embedding_model = embedding_model
self.vectorstore = None
self.qa_chain = None
# 初始化嵌入模型
self.embeddings = OllamaEmbeddings(model=self.embedding_model)
# 初始化聊天模型
self.llm = ChatOllama(model=self.model_name, temperature=0.7)
def load_documents(self, file_path, file_type=”pdf”):
“””加载文档并创建向量数据库”””
print(f”正在加载 {file_type.upper()} 文档…”)
if file_type == “pdf”:
loader = PyPDFLoader(file_path)
else:
raise ValueError(f”不支持的文件类型: {file_type}”)
documents = loader.load()
# 分割文档
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
chunks = text_splitter.split_documents(documents)
print(f”文档已切分为 {len(chunks)} 个片段”)
# 创建向量数据库
self.vectorstore = Chroma.from_documents(
documents=chunks,
embedding=self.embeddings,
persist_directory=”./knowledge_db”
)
print(“向量数据库创建完成”)
return self
def setup_qa_chain(self):
“””设置问答链”””
if self.vectorstore is None:
raise ValueError(“请先加载文档”)
# 定义提示词
prompt_template = “””请根据以下参考文档回答用户问题。
请只根据提供的参考文档回答,不要添加文档中没有的信息。
参考文档:
{context}
用户问题:{question}
回答:”””
prompt = PromptTemplate(
template=prompt_template,
input_variables=[“context”, “question”]
)
# 创建问答链
self.qa_chain = RetrievalQA.from_chain_type(
llm=self.llm,
chain_type=”stuff”,
retriever=self.vectorstore.as_retriever(
search_kwargs={“k”: 3}
),
chain_type_kwargs={“prompt”: prompt}
)
print(“问答链设置完成”)
return self
def ask(self, question):
“””向知识库提问”””
if self.qa_chain is None:
raise ValueError(“请先设置问答链”)
result = self.qa_chain({“query”: question})
return result[“result”]
def main():
“””主函数”””
# 初始化知识库
kb = LocalKnowledgeBase(
model_name=”qwen2.5:7b”,
embedding_model=”nomic-embed-text”
)
# 加载文档(请替换为实际文件路径)
try:
kb.load_documents(“company_manual.pdf”, file_type=”pdf”)
except FileNotFoundError:
print(“示例文档不存在,跳过加载步骤”)
print(“请将您的 PDF 文件放置在当前目录并修改代码中的文件路径”)
return
# 设置问答链
kb.setup_qa_chain()
# 示例问答
print(“\n” + “=”*50)
print(“本地知识库问答系统已就绪”)
print(“输入问题即可获得答案,输入 quit 退出”)
print(“=”*50 + “\n”)
while True:
question = input(“请输入问题: “).strip()
if question.lower() == “quit”:
print(“感谢使用,再见!”)
break
if not question:
continue
try:
answer = kb.ask(question)
print(f”\n回答: {answer}\n”)
except Exception as e:
print(f”发生错误: {e}\n”)
if __name__ == “__main__”:
main()
“`
## 运行结果
在成功部署系统后,我们进行了一系列测试来验证其效果。以下是几个典型的问答示例:
**测试一**:询问文档中明确记载的信息
– 问题:公司新员工入职流程是什么?
– 回答:根据文档,新员工入职流程包括以下步骤:首先在人力资源部办理入职手续,领取工牌和办公用品;然后参加部门安排的欢迎会议,了解团队成员和工作内容;接着完成系统账号申请和权限配置;最后参加公司组织的入职培训,培训内容包括公司文化、规章制度、工作流程等。整个流程通常需要1-2个工作日。
**测试二**:询问需要综合多段信息回答的问题
– 问题:技术团队的加班政策是如何规定的?
– 回答:根据公司员工手册,技术团队因项目进度需要加班时,需提前填写加班申请单并由部门负责人审批。工作日加班可选择调休或支付加班费,休息日加班则按国家规定支付双倍工资。紧急项目需要临时加班的,应在事后48小时内补办手续。
**测试三**:询问文档中没有直接答案的问题
– 问题:公司明年有什么发展规划?
– 回答:抱歉,您的问题在我所了解的文档中没有找到相关信息。建议您查阅公司的年度战略规划文档或咨询战略规划部门获取更准确的信息。
从测试结果可以看出,系统能够准确理解用户提问的语义,并从文档中检索相关内容进行回答。对于文档中明确记载的问题,回答准确率高;对于需要综合的问题,也能给出合理的回答;对于无法回答的问题,系统会如实告知用户。
## 总结
本文详细介绍了如何使用 LangChain 和 Ollama 构建本地私有知识库问答系统的完整方案。通过这套系统,企业可以在完全离线的环境下,利用大语言模型技术实现对内部文档的智能问答,无需担心敏感数据外泄。
系统的核心优势包括:数据本地化存储保障信息安全、支持多种文档格式的灵活加载、基于语义理解的智能检索、以及可定制的问答提示词模板。用户可以根据实际需求选择不同的开源模型,以在效果和资源消耗之间取得平衡。
在实际部署时,需要注意以下几点:确保服务器有足够的内存资源(建议 16GB 以上)、选择合适的嵌入模型和聊天模型以平衡效果与速度、定期更新向量数据库以反映文档的最新变化。
随着开源大语言模型技术的不断进步,本地部署的问答系统效果将会持续提升,为企业知识管理提供更加智能、安全、经济的解决方案。