为什么你需要一个私有知识库
大语言模型的“幻觉”问题和知识时效性短板,正在成为企业落地 AI 的最大阻碍。当你的团队需要基于内部规章、产品手册或历史报告回答问题,仅靠提示词工程远远不够。检索增强生成(RAG) 是当前最务实的解法:它不微调模型,而是把外部文档切成块、向量化存储,在推理时检索最相关的片段拼入上下文,让模型“先查后答”。
本文将带你用 LangChain 作为编排框架、Chroma 作为向量数据库,从零搭建一个可运行的私有知识库问答系统。全程使用 Python 代码,所有组件均为开源。
核心架构与选型思路
一个最小可用的 RAG 系统包含四个环节:
| 环节 | 工具选择 | 作用 |
| 文档加载 | LangChain DirectoryLoader | 读取本地 PDF/Markdown/Word |
| 文本切分 | RecursiveCharacterTextSplitter | 按语义边界切块,避免截断句子 |
| 向量化 | OpenAIEmbeddings 或开源模型 | 将文本转为高维向量 |
| 检索+生成 | Chroma + ChatOpenAI | 相似度检索 + 大模型应答 |
选择 Chroma 的原因在于:它是轻量级嵌入式向量库,无需单独部署服务,支持持久化到本地磁盘,非常适合中小型知识库(单机百万级向量以内)。
实战代码:从加载到问答
第一步:安装依赖与准备文档
pip install langchain langchain-openai chromadb pypdf创建一个 docs/ 目录,放入你的企业知识文档(如 员工手册.pdf)。注意文件编码,PDF 建议用 pypdf 解析。
第二步:加载与切分文档
from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 加载所有 PDF 文档
loader = DirectoryLoader("./docs", glob="**/*.pdf", show_progress=True)
documents = loader.load()
# 切分参数:chunk_size 控制在 500 字符左右,overlap 保留上下文衔接
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=80,
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"共切分为 {len(chunks)} 个文本块")关键点:切分粒度直接影响检索质量。过小则语义不完整,过大则混入噪音。overlap 让相邻块有重叠,可缓解切分边界导致的召回遗漏。
第三步:向量化并写入 Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
# 使用 OpenAI 向量模型(也可换用开源模型如 BAAI/bge-small-zh)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 持久化到本地目录
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
vectorstore.persist()如果你不想调用 API,可改用 HuggingFaceEmbeddings 加载本地模型,但推理速度会慢一些,且需注意显存占用。
第四步:构建检索问答链
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI
# 初始化大模型,建议 temperature 设为 0 以保证回答准确性
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 检索器:返回 top-4 最相关块
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
return_source_documents=True # 便于调试查看引用来源
)第五步:启动问答测试
query = "员工年假超过 10 天需要什么审批流程?"
result = qa_chain.invoke({"query": query})
print("回答:", result["result"])
print("\n参考来源:")
for doc in result["source_documents"]:
print(doc.page_content[:100])如果回答中包含“根据手册第 X 节”,说明检索已准确命中。若答案含糊,可尝试调低 k 值或增大 chunk_overlap。
进阶优化:让知识库更聪明
基础流程跑通后,以下三项优化会显著提升体验:
- 混合检索:Chroma 默认使用余弦相似度,可叠加 BM25 关键词检索,用
EnsembleRetriever融合结果,能同时应对“语义模糊”和“专有名词”问题。 - 元数据过滤:给每个 chunk 打上来源文件名、章节标题等元数据,查询时用
filter限定范围(如仅在“销售制度”目录下检索),减少无关干扰。 - 引用溯源:返回
source_documents后,在前端展示原文来源,极大增强用户信任感。生产环境可将此信息渲染为脚注链接。
总结与行动建议
本文用约 60 行代码,完成了一个可落地的 RAG 知识库:文档加载 → 切块 → 向量化 → 检索生成,并给出了调优方向。这套框架已能处理数百页的内部文档,无需 GPU 即可运行。
如果你想更进一步,建议按以下顺序行动:
- 替换为本地向量模型(如
BAAI/bge-m3),彻底摆脱 API 依赖,适合数据敏感场景。 - 引入增量更新机制,用 Chroma 的
update_document方法替换旧文档,避免每次重建索引。 - 接入 Web UI(如 Streamlit 或 Gradio),让非技术同事也能使用。
RAG 的价值不在于代码本身,而在于它让模型“知道你的业务”。从今天开始,把你手头最常被问起的那个知识文档,变成 24 小时在线的问答助手吧。