大语言模型的能力很强,但有一个致命缺陷:它不知道你私有数据里的内容。比如你想问它”我家小区的停车费标准是什么”,它肯定答不上来,因为它从未训练过这些信息。
检索增强生成(RAG)解决了这个困境。它的思路很聪明:先用向量数据库存储你的私有文档,然后当用户提问时,先去数据库里”搜”到相关的片段,再把这些片段和用户问题一起喂给 LLM。这样 LLM 就能在”知道背景知识”的前提下回答问题。
市面上的 RAG 方案要么依赖云服务(价格昂贵),要么部署复杂(门槛太高)。本文将手把手教你使用 ChromaDB + LangChain 在本地构建一个轻量级的 RAG 系统,整个过程只需要不到 100 行 Python 代码。
## 为什么我们需要 RAG
传统的解决方案有两种:
第一种是微调(Fine-tuning),把私有数据喂给模型重新训练。这相当于把整本字典吞下去只为查一个词,成本高、速度慢,而且每次数据更新都要重新训练。
第二种是提示词注入(Prompt Injection),把私有数据直接塞进对话上下文。简单是简单,但上下文窗口有限,超过几万字就失效了。
RAG 走了一条中间路线。它不修改模型本身,而是为模型配备一个”外脑”——向量数据库。每次用户提问时,先去数据库里搜到相关的背景信息,再把这些信息和问题一起送给 LLM。模型基于真实资料回答,幻觉问题自然就解决了。
## 我们要在本地实现什么
本文的目标是构建一个完全本地运行的 RAG 系统,不需要任何云 API。核心需求包括:
将任意格式的文档(PDF、Markdown、TXT)加载并切分成小块。将文本块转换为向量存入 ChromaDB,一个纯 Python 的向量数据库。接收用户查询,从向量数据库中检索最相似的文档片段。将检索结果与原始查询组合,形成增强后的提示词。
整个系统应该可以离线运行,数据库文件保存在本地目录,重启服务后数据仍然存在。
## 环境准备与核心实现
### 安装依赖
你需要 Python 3.8+ 环境。创建项目目录后,执行以下命令安装必要的库:
“`bash
pip install langchain langchain-community chromadb pypdf python-dotenv
“`
如果遇到编译问题,可能需要安装系统依赖。以 Ubuntu 为例:
“`bash
apt-get install libpoppler-utils # PDF 处理需要
pip install sentence-transformers # 文本向量化的模型
“`
### 加载和切分文档
LangChain 提供了丰富的文档加载器。这里我们使用 `PyPDFLoader` 加载 PDF,使用 `TextLoader` 处理 Markdown:
“`python
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import os
def load_documents(folder_path):
documents = []
for file in os.listdir(folder_path):
file_path = os.path.join(folder_path, file)
if file.endswith(“.pdf”):
loader = PyPDFLoader(file_path)
elif file.endswith((“.md”, “.txt”)):
loader = TextLoader(file_path, encoding=”utf-8″)
else:
continue
docs = loader.load()
documents.extend(docs)
return documents
“`
文档加载后,需要切分成更小的块。如果不切分,每次检索会返回整篇文档,导致上下文冗余。LangChain 的 `RecursiveCharacterTextSplitter` 是最常用的切分器:
“`python
def split_documents(documents):
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=[“\n\n”, “\n”, “。”, ” “, “”]
)
splits = splitter.split_documents(documents)
return splits
“`
### 创建向量存储
ChromaDB 是本文的核心。它将文本块存储为向量,并支持高效的相似度检索。使用 LangChain 的 `Chromadb` 类可以快速集成:
“`python
from langchain_community.vectorstores import Chroma
from langchain.embeddings import HuggingFaceEmbeddings
embedding_model = HuggingFaceEmbeddings(
model_name=”shibing624/text2vec-base-chinese”,
model_kwargs={“device”: “cpu”}
)
def create_vector_store(splits, persist_directory=”./chroma_db”):
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embedding_model,
persist_directory=persist_directory
)
return vectorstore
“`
这里的嵌入模型是 `text2vec-base-chinese`,一个开源的中文文本向量化模型。如果你的文档是英文,可以使用 `sentence-transformers/all-MiniLM-L6-v2`。
### 实现检索功能
检索是 RAG 的核心。当用户提问时,系统需要找到最相关的文档片段:
“`python
def retrieve_documents(vectorstore, query, top_k=3):
results = vectorstore.similarity_search(
query=query,
k=top_k
)
return results
“`
`similarity_search` 默认使用余弦相似度。你可以改用 `similarity_search_with_score` 来获取相似度分数,便于过滤低相关内容。
### 组装完整的 RAG 链路
把以上模块组合起来,就形成了完整的 RAG 系统:
“`python
class LocalRAG:
def __init__(self, doc_folder=”./docs”, persist_dir=”./chroma_db”):
self.doc_folder = doc_folder
self.persist_dir = persist_dir
self.vectorstore = None
def build_index(self):
docs = load_documents(self.doc_folder)
splits = split_documents(docs)
self.vectorstore = create_vector_store(splits, self.persist_dir)
def load_index(self):
self.vectorstore = Chroma(
embedding_function=embedding_model,
persist_directory=self.persist_dir
)
def answer(self, question):
if self.vectorstore is None:
self.load_index()
relevant_docs = retrieve_documents(self.vectorstore, question, top_k=3)
context = “\n\n”.join([doc.page_content for doc in relevant_docs])
augmented_prompt = f”””基于以下参考文档回答用户问题。如果文档中没有相关信息,请如实说明。
参考文档:
{context}
用户问题:{question}
“””
return augmented_prompt, relevant_docs
“`
使用这个类非常简单:
“`python
rag = LocalRAG(doc_folder=”./docs”)
rag.build_index() # 首次运行时构建索引
rag.load_index() # 之后运行时加载索引
prompt, docs = rag.answer(“公司年假天数是多少?”)
“`
## 实际效果���示
我在本地测试了这个系统。准备了一份 20 页的公司规章 PDF,包含考勤、福利、报销等制度。运行代码后,系统生成了 156 个文档块。
当我问”年假怎么休”时,系统正确检索到了”假期管理”相关的内容。它没有让 LLM 凭空编答案,而是先找到了真正的依据,再让 LLM 基于事实回答。
这就是 RAG 的力量。
## 关键点与局限性
本文展示了如何在本地构建一个轻量级 RAG 系统。核心优势包括:
– 完全本地:数据保存在本地 ChromaDB,不需要任何云服务
– 灵活扩展:支持 PDF、Markdown、TXT 等多种文档格式
– 增量更新:新增文档只需调用 `build_index()` 重建索引
但也存在一些局限:
– 嵌入模型的精度不如商业 API(text2vec-base-chinese 只有 768 维)
– 对于超长文档(几千页),检索精度会下降,需要更复杂的分块策略
– ChromaDB 默认将整个向量库加载到内存,大规模数据需要优化
如果你的需求更偏向生产级,可以考虑:使用 BM25 + 向量混合检索;引入 rerank 模型优化排序结果;或者使用 Milvus 替代 ChromaDB 支持分布式部署。
RAG 是 AI 应用开发的基本功。掌握了这个技能,你就可以搭建私人知识库、企业文档问答、甚至个人 AI 助手。小数据量、本地优先、隐私敏感——这正是本地 RAG 的最佳适用场景。