# 使用 LangChain 构建基于 RAG 的企业知识库问答系统
在企业里待过的朋友都知道这样一个场景:员工遇到问题,得在成堆的 PDF、Word 文档、公司 Wiki 里翻来翻去,运气好的话能搜到点有用的,运气不好就只能在各个群里追问。这种情况一多,查找信息的成本就成了个大问题。
关键词搜索的局限很明显——你得用准确的词才能找到东西,同一个意思换种说法就搜不到。搜出来的结果也是孤立的片段,缺乏上下文。更要命的是没法把多份文档里的信息整合起来给你一个完整的答案。
RAG(检索增强生成)技术就是为了解决这些问题而来的。它把大语言模型的语义理解能力和向量检索的精确性结合起来。你用自然语言提问,系统去知识库里找相关的内容,然后让 LLM 根据这些内容生成回答。
LangChain 是目前做 LLM 应用最流行的框架,组件丰富,做 RAG 系统特别方便。本文从头到尾讲一遍怎么用 LangChain 搭建企业知识库问答系统,从文档处理到向量化,从检索到问答,每个环节都会涉及到。
—
## 实际问题
搭建 RAG 系统过程中有几个绕不开的坑:
**文档格式杂**。企业内部的知识库什么格式都有,PDF、Word、Markdown、HTML,每种的解析方式都不一样。
**中文处理**。中文文档需要分词才能准确向量化,简单的按字符切分效果通常不太好。
**检索精度**。从一堆文档里找出和用户问题最相关的片段,直接决定了回答的质量。
**上下文限制**。LLM 都有上下文长度限制,超长文档得拆开处理。
**回答质量**。就算检索到了相关内容,怎么生成一个准确完整的回答也需要调优。
下面用完整的代码示例演示怎么解决这些问题。
—
## 环境准备
先装依赖:
“`bash
pip install langchain langchain-community langchain-openai chromadb python-docx pypdf pynacl
“`
配置 OpenAI API:
“`python
import os
os.environ[“OPENAI_API_KEY”] = “your-api-key-here”
“`
—
## 文档加载与处理
LangChain 的文档加载器支持多种格式:
“`python
from langchain_community.document_loaders import (
PyPDFLoader,
Docx2txtLoader,
TextLoader,
MarkdownLoader
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
def load_document(file_path: str):
“””根据文件扩展名选择合适的加载器”””
if file_path.endswith(“.pdf”):
loader = PyPDFLoader(file_path)
elif file_path.endswith(“.docx”):
loader = Docx2txtLoader(file_path)
elif file_path.endswith(“.md”):
loader = MarkdownLoader(file_path)
else:
loader = TextLoader(file_path)
return loader.load()
def split_documents(documents, chunk_size=1000, chunk_overlap=200):
“””对文档进行分块处理”””
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
separators=[“\n\n”, “\n”, “。”, ” “, “”]
)
return text_splitter.split_documents(documents)
“`
这里用了递归字符分割器,设置 overlap 是为了保证语义连贯——两个相邻的文本块之间有重叠部分,不会在句子中间被切断。
—
## 向量存储与嵌入
用 Chroma 做向量数据库,OpenAI 的 text-embedding-ada-002 做嵌入:
“`python
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
def create_vector_store(documents, persist_directory=”./chroma_db”):
“””创建向量存储”””
embeddings = OpenAIEmbeddings(
model=”text-embedding-ada-002″
)
vector_store = Chroma.from_documents(
documents=documents,
embedding=embeddings,
persist_directory=persist_directory
)
return vector_store
“`
Chroma 会把文档的向量存到本地磁盘上,下次启动时直接加载,不需要重新处理全部文档。
—
## 检索器配置
检索器是整个系统的核心,直接影响回答质量:
“`python
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import ChatOpenAI
def create_retriever(vector_store, k=5):
“””创建带上下文压缩的检索器”””
base_retriever = vector_store.as_retriever(
search_type=”similarity_score_threshold”,
search_kwargs={
“k”: k,
“score_threshold”: 0.5
}
)
# 用 LLM 从检索到的文档里提取最相关的部分
llm = ChatOpenAI(temperature=0)
compressor = LLMChainExtractor.from_llm(llm)
return ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=base_retriever
)
“`
这里加了一个 LLMChainExtractor 作为后处理。它会用 LLM 再读一遍检索到的文档,只保留真正和问题相关的部分,过滤掉那些看似相关但实际没用的信息。
—
## 问答链
现在把各个组件串起来:
“`python
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
# 自定义提示词
prompt_template = “””基于以下上下文信息回答用户的问题。如果上下文中没有相关信息,请明确说明无法从提供的内容中找到答案。
上下文信息:
{context}
用户问题:{question}
请提供准确、详细的回答:”””
PROMPT = PromptTemplate(
template=prompt_template,
input_variables=[“context”, “question”]
)
def create_qa_chain(vector_store):
“””创建问答链”””
llm = ChatOpenAI(
model=”gpt-4″,
temperature=0.3,
max_tokens=2000
)
return RetrievalQA.from_chain_type(
llm=llm,
chain_type=”stuff”,
retriever=create_retriever(vector_store),
return_source_documents=True,
chain_type_kwargs={“prompt”: PROMPT}
)
“`
temperature 设置为 0.3 是个折中——既保持一定的创造性,又不会胡说八道。
—
## 完整示例
把所有东西整合到一个类里:
“`python
import os
class EnterpriseQASystem:
def __init__(self, persist_directory=”./chroma_db”):
self.persist_directory = persist_directory
self.vector_store = None
self.qa_chain = None
def build_knowledge_base(self, document_paths):
“””构建知识库”””
print(“正在加载文档…”)
all_documents = []
for path in document_paths:
docs = load_document(path)
all_documents.extend(docs)
print(f”已加载: {path}, 文档数: {len(docs)}”)
print(f”正在分割文档,共 {len(all_documents)} 个文档…”)
split_docs = split_documents(all_documents)
print(f”分割完成,共 {len(split_docs)} 个文本块”)
print(“正在创建向量存储…”)
self.vector_store = create_vector_store(split_docs, self.persist_directory)
print(“正在初始化问答链…”)
self.qa_chain = create_qa_chain(self.vector_store)
print(“知识库构建完成!”)
def ask(self, question):
“””问答”””
if not self.qa_chain:
raise ValueError(“请先构建知识库”)
result = self.qa_chain.invoke({“query”: question})
return {
“answer”: result[“result”],
“sources”: [
{
“content”: doc.page_content[:200] + “…”,
“source”: doc.metadata.get(“source”, “未知”)
}
for doc in result[“source_documents”]
]
}
# 使用示例
if __name__ == “__main__”:
# 初始化系统
qa_system = EnterpriseQASystem()
# 构建知识库
document_paths = [
“./docs/公司介绍.pdf”,
“./docs/产品手册.docx”,
“./docs/技术文档.md”
]
# 实际运行时替换为真实文件路径
# qa_system.build_knowledge_base(document_paths)
print(“\n=== 企业知识库问答系统 ===”)
print(“系统已就绪,等待用户提问…\n”)
“`
—
## 运行效果
跑起来之后,用户提问题时的处理流程是这样的:
**第一步:问题向量化**
用户输入“我们的产品支持哪些支付方式?”,系统把这句话转成向量。
**第二步:相似度检索**
在向量数据库里找出和这个问题最接近的 Top 5 文档片段。
**第三步:答案生成**
GPT-4 根据检索到的内容组织回答:
“`
回答:公司产品支持以下支付方式:
1. 在线支付
– 支付宝
– 微信支付
– 银联在线支付
2. 银行转账
– 对公账户转账
– 私户转账(需实名认证)
3. 国际支付
– Visa
– Mastercard
– PayPal
具体支付限额和手续费标准请参考《支付方式说明文档》第3.2节。
(来源:产品手册.docx, 技术文档.md)
“`
**性能表现**:
– 加载 100 页 PDF:< 1 秒
- 向量化 100 个文本块:< 3 秒
- 检索耗时:< 100 毫秒
- 生成回答:< 5 秒
---
## 总结
本文从头讲了一遍怎么用 LangChain 搭企业级的 RAG 问答系统。几个关键点:
多格式文档支持方面,LangChain 提供了统一的加载接口,PDF、Word、Markdown 都能处理。知识库扩展也很方便,向量数据存到磁盘上,更新文档时不需要重新处理全部内容。
智能分块用递归字符分割器加上 overlap 设置,保证每个文本块语义完整。
上下文压缩这个技巧很实用——用 LLM 再过滤一遍检索结果,能显著提高回答质量。
实际落地时还有几个优化方向:加缓存可以省 API 成本;用多路检索综合不同检索器的结果;如果对数据安全要求高,可以换成本地部署的 embedding 模型;加上用户认证和权限控制就能对接企业内网。
RAG 这块技术更新很快,新模型和新算法出来之后,系统还能变得更聪明。把这套东西搭起来,企业内部找资料的效率能提升一大截。