用 LangChain 搭建本地知识库问答
企业有 1000+ 文档,员工找个信息要花半天?用 LangChain 搭建知识库问答系统,3 分钟搞定!
为什么需要知识库问答?
上个月给一个公司做培训,他们 HR 说:
"员工天天问同样的问题:请假流程、报销标准、五险一金..."
我花了 2 天,用 LangChain 搭了个知识库问答机器人。
现在员工有问题直接问 AI,3 秒钟出答案,还能告诉你出自哪个文档。
HR 小姐姐说效率提升了 10 倍不止。
🛠️ 环境准备
1. 安装依赖
bash
pip install langchain langchain-openai langchain-community chromadb pypdf python-docx
2. 准备文档
创建 docs/ 文件夹,放入你的文档:
docs/
├── 员工手册.pdf
├── 报销流程.docx
├── 请假制度.txt
└── ...
3. 配置 API Key
.env 文件:
bash
OPENAI_API_KEY=sk-xxxxxxxx
# 或用 DeepSeek(推荐,便宜 10 倍)
DEEPSEEK_API_KEY=sk-xxxxxxxx
💻 核心代码
场景 1:加载多种格式文档
python
from langchain_community.document_loaders import (
PyPDFLoader,
Docx2txtLoader,
TextLoader,
DirectoryLoader
)
def load_documents(folder_path):
"""加载文件夹里的所有文档"""
print(f"📚 正在加载 {folder_path} 里的文档...")
# 加载 PDF
pdf_loader = DirectoryLoader(
folder_path,
glob="**/*.pdf",
loader_cls=PyPDFLoader
)
pdf_docs = pdf_loader.load()
print(f"✅ 加载了 {len(pdf_docs)} 个 PDF 页面")
# 加载 Word
docx_loader = DirectoryLoader(
folder_path,
glob="**/*.docx",
loader_cls=Docx2txtLoader
)
docx_docs = docx_loader.load()
print(f"✅ 加载了 {len(docx_docs)} 个 Word 文档")
# 加载 TXT
txt_loader = DirectoryLoader(
folder_path,
glob="**/*.txt",
loader_cls=TextLoader
)
txt_docs = txt_loader.load()
print(f"✅ 加载了 {len(txt_docs)} 个 TXT 文件")
# 合并所有文档
all_docs = pdf_docs + docx_docs + txt_docs
print(f"📊 总共加载 {len(all_docs)} 个文档片段")
return all_docs
if __name__ == '__main__':
docs = load_documents('docs')
踩坑提醒:
- PDF 加载慢?用
PyPDFLoader比PDFMinerLoader快 10 倍 - Word 乱码?确保是
.docx格式,老.doc不支持 - 中文乱码?TXT 文件加
encoding='utf-8'
场景 2:智能分割文档
python
from langchain.text_splitter import (
CharacterTextSplitter,
RecursiveCharacterTextSplitter,
ChineseTextSplitter
)
def split_documents(docs):
"""智能分割文档"""
print("✂️ 正在分割文档...")
# 方案 1:按字符分割(适合英文)
# splitter = CharacterTextSplitter(
# chunk_size=1000,
# chunk_overlap=200
# )
# 方案 2:递归分割(推荐,适合混合内容)
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
separators=["\n\n", "\n", "。", "!", "?", ";", " "]
)
# 方案 3:中文专用(最推荐)
# splitter = ChineseTextSplitter(
# chunk_size=1000,
# chunk_overlap=200
# )
split_docs = splitter.split_documents(docs)
print(f"✅ 分割完成,共 {len(split_docs)} 个片段")
# 显示第一个片段示例
print(f"\n📝 片段示例:\n{split_docs[0].page_content[:200]}...")
return split_docs
if __name__ == '__main__':
from langchain_community.document_loaders import TextLoader
loader = TextLoader('docs/员工手册.txt', encoding='utf-8')
docs = loader.load()
split_docs = split_documents(docs)
分割参数调优:
chunk_size: 每个片段的大小(500-2000)chunk_overlap: 片段重叠部分(100-300)- 重叠越多,上下文越完整,但向量越多
场景 3:创建向量索引
python
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import os
from dotenv import load_dotenv
load_dotenv()
def create_vector_index(docs):
"""创建向量索引"""
print("🔧 正在创建向量索引...")
# 初始化 Embedding 模型
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small", # 便宜又快
# model="text-embedding-ada-002" # 旧版
)
# 创建向量存储(本地)
vectorstore = Chroma.from_documents(
documents=docs,
embedding=embeddings,
persist_directory="./chroma_db" # 保存到本地
)
print(f"✅ 向量索引创建完成")
print(f"📊 共 {vectorstore._collection.count()} 个向量")
return vectorstore
if __name__ == '__main__':
# 假设已有 split_docs
vectorstore = create_vector_index(split_docs)
踩坑记录:
- 第一次慢?正常,1000 页文档大概 5-10 分钟
- 第二次快?有缓存,秒建
- 想删除重建?
rm -rf chroma_db
场景 4:智能问答(带引用)
python
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
def create_qa_bot(vectorstore):
"""创建问答机器人"""
print("🤖 正在初始化问答机器人...")
# 初始化 LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
# 自定义提示词模板
template = """
你是一个专业的知识库助手,根据提供的文档片段回答用户问题。
【要求】
1. 只根据提供的文档片段回答,不要瞎编
2. 如果文档里没有答案,就说"文档中没有相关信息"
3. 回答要简洁明了
4. 最后要注明引用来源
【文档片段】
{context}
【用户问题】
{question}
请用中文回答:
"""
prompt = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
# 创建问答链
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 3} # 返回最相关的 3 个片段
),
chain_type="stuff",
return_source_documents=True # 返回引用来源
)
print("✅ 问答机器人已启动")
return qa_chain
def chat_with_bot(qa_chain):
"""与机器人对话"""
print("\n💬 开始对话(输入 quit 退出)\n")
while True:
question = input("👤 你:")
if question.lower() == 'quit':
print("👋 再见!")
break
# 获取答案
result = qa_chain({"query": question})
# 显示答案
print(f"🤖 AI: {result['result']}\n")
# 显示引用来源
if result['source_documents']:
print("📚 引用来源:")
for i, doc in enumerate(result['source_documents'], 1):
source = doc.metadata.get('source', '未知')
page = doc.metadata.get('page', '')
print(f" {i}. {source}{' 第' + str(page) + '页' if page else ''}")
print(f" 内容:{doc.page_content[:100]}...")
print()
if __name__ == '__main__':
# 加载向量存储
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
# 创建机器人
qa_chain = create_qa_bot(vectorstore)
# 开始对话
chat_with_bot(qa_chain)
运行效果:
👤 你:请假流程是什么?
🤖 AI: 根据员工手册,请假流程如下:
1. 提前 3 天在 OA 系统提交申请
2. 直属领导审批
3. HR 备案
4. 紧急情况可电话请假,事后补手续
📚 引用来源:
1. docs/员工手册.pdf 第 15 页
内容:第三章 考勤管理 第二节 请假流程...
2. docs/请假制度.txt
内容:员工请假需提前申请,经领导批准后方可休假...
🎯 完整项目:企业知识库系统
python
"""
企业知识库问答系统
功能:
1. 支持 PDF/Word/TXT 多种格式
2. 智能分割和索引
3. 问答带引用来源
4. 支持多轮对话
5. 可增量更新文档
"""
from langchain_community.document_loaders import (
PyPDFLoader, Docx2txtLoader, TextLoader, DirectoryLoader
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
import os
from dotenv import load_dotenv
load_dotenv()
class KnowledgeBaseQA:
def __init__(self, docs_folder="docs", persist_dir="./chroma_db"):
self.docs_folder = docs_folder
self.persist_dir = persist_dir
self.vectorstore = None
self.qa_chain = None
self.memory = ConversationBufferMemory(
return_messages=True
)
def load_and_index(self):
"""加载文档并创建索引"""
print("📚 步骤 1: 加载文档...")
# 加载所有文档
loaders = [
DirectoryLoader(self.docs_folder, glob="**/*.pdf", loader_cls=PyPDFLoader),
DirectoryLoader(self.docs_folder, glob="**/*.docx", loader_cls=Docx2txtLoader),
DirectoryLoader(self.docs_folder, glob="**/*.txt", loader_cls=TextLoader)
]
all_docs = []
for loader in loaders:
docs = loader.load()
all_docs.extend(docs)
print(f" ✅ 加载 {len(docs)} 个文档")
print(f"\n✂️ 步骤 2: 分割文档...")
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", "!", "?", ";", " "]
)
split_docs = splitter.split_documents(all_docs)
print(f" ✅ 分割为 {len(split_docs)} 个片段")
print(f"\n🔧 步骤 3: 创建向量索引...")
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
self.vectorstore = Chroma.from_documents(
documents=split_docs,
embedding=embeddings,
persist_directory=self.persist_dir
)
print(f" ✅ 索引创建完成,共 {self.vectorstore._collection.count()} 个向量")
# 创建问答链
self._create_qa_chain()
def _create_qa_chain(self):
"""创建问答链"""
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
template = """
你是一个专业的企业知识库助手。
【要求】
1. 只根据提供的文档回答,不要编造
2. 如果文档中没有答案,明确告知用户
3. 回答要简洁、准确、专业
4. 最后注明引用来源
【对话历史】
{history}
【文档内容】
{context}
【用户问题】
{question}
请用中文回答:
"""
prompt = PromptTemplate(
template=template,
input_variables=["history", "context", "question"]
)
self.qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=self.vectorstore.as_retriever(search_kwargs={"k": 3}),
chain_type="stuff",
return_source_documents=True
)
def chat(self, question):
"""对话"""
if not self.qa_chain:
return "⚠️ 请先调用 load_and_index() 加载文档"
result = self.qa_chain({"query": question})
response = {
'answer': result['result'],
'sources': []
}
# 提取引用来源
for doc in result['source_documents']:
source = {
'file': doc.metadata.get('source', '未知'),
'page': doc.metadata.get('page', ''),
'content': doc.page_content[:200]
}
response['sources'].append(source)
return response
def update_documents(self):
"""增量更新文档"""
print("🔄 增量更新文档...")
# 简单实现:重新加载所有文档
# 进阶:只加载新增/修改的文档
self.load_and_index()
print("✅ 更新完成")
def main():
"""主程序"""
print("=" * 60)
print("🤖 企业知识库问答系统")
print("=" * 60)
# 初始化系统
kb = KnowledgeBaseQA(docs_folder="docs", persist_dir="./chroma_db")
# 加载文档(第一次运行)
kb.load_and_index()
print("\n💬 开始对话(输入命令:help/quit/clear/update)\n")
while True:
user_input = input("👤 你:")
if user_input.lower() == 'quit':
print("👋 感谢使用,再见!")
break
elif user_input.lower() == 'help':
print("""
📋 帮助:
- 直接输入问题,AI 会基于文档回答
- 输入 'clear' 清空对话历史
- 输入 'update' 重新加载文档
- 输入 'quit' 退出程序
""")
continue
elif user_input.lower() == 'clear':
kb.memory.clear()
print("✅ 对话历史已清空")
continue
elif user_input.lower() == 'update':
kb.update_documents()
continue
# 获取回答
response = kb.chat(user_input)
# 显示回答
print(f"\n🤖 AI: {response['answer']}\n")
# 显示引用
if response['sources']:
print("📚 引用来源:")
for i, src in enumerate(response['sources'], 1):
page_info = f" 第{src['page']}页" if src['page'] else ""
print(f" {i}. {src['file']}{page_info}")
print()
if __name__ == '__main__':
main()
⚠️ 血泪教训
- 文档格式要统一,编码用 UTF-8,不然中文乱码
- 分割参数要调优,太大丢失上下文,太小碎片化
- 向量数据库要持久化,不然每次重启都要重建
- 引用来源很重要,用户需要知道答案从哪来
💡 进阶玩法
1. 混合搜索(相似度 + 关键词)
python
retriever = vectorstore.as_retriever(
search_type="mmr", # 最大边界相关
search_kwargs={
"k": 3,
"fetch_k": 20,
"lambda_mult": 0.5
}
)
2. 多向量检索(Parent Document)
python
from langchain.retrievers import ParentDocumentRetriever
parent_splitter = CharacterTextSplitter(chunk_size=2000, chunk_overlap=0)
child_splitter = CharacterTextSplitter(chunk_size=400, chunk_overlap=200)
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=InMemoryDocstore(),
child_splitter=child_splitter,
parent_splitter=parent_splitter
)
3. 增量更新
python
# 只添加新文档
new_docs = loader.load()
new_splits = splitter.split_documents(new_docs)
vectorstore.add_documents(new_splits)
🌟 下篇预告
下一篇:《LangChain Memory 详解:让 AI 记住对话历史》
- 5 种记忆类型详解
- 长短期记忆结合
- 对话摘要压缩
- 企业级对话管理系统
觉得有用?点赞 + 关注不迷路! 🎉
标签: #LangChain #RAG #知识库 #AI #Python #向量数据库