从零实现本地 RAG 检索系统:ChromaDB + LangChain 实战指南

大语言模型的能力很强,但有一个致命缺陷:它不知道你私有数据里的内容。比如你想问它”我家小区的停车费标准是什么”,它肯定答不上来,因为它从未训练过这些信息。

检索增强生成(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 的最佳适用场景。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇