# 基于本地 LLM 构建 RAG 系统:完整指南与实践
大型语言模型这两年很火,用云端 API 调用虽然方便,但问题也不少。数据要传出去隐私没保障,网络不稳定时响应慢吞吞,长期使用成本也下不来。有没有一种方法能既享受 AI 的能力,又把数据留在自己手里?答案是搭建本地 RAG 系统。
## 什么是 RAG
RAG(Retrieval Augmented Generation)这个概念是 Meta 在 2020 年提出来的。简单说就是把语言模型和知识检索结合起来用。用户提问时,系统先从自己的知识库里找相关资料,然后把问题和找到的资料一起发给语言模型,让模型根据这些材料来回答。
这种方法有几个明显好处。第一,模型能回答私有知识库里的内容,不限于训练数据。第二,因为有实际资料作为依据,模型胡编乱造的概率大大降低。第三,知识库更新了不用重新训练模型。第四,所有数据都保存在本地,隐私完全没问题。
## 这篇文章要解决什么问题
在本地跑 RAG 系统,坑挺多的。模型怎么选就是第一个问题。开源模型一抓一大把,哪个效果好,哪个省资源,得实际试过才知道。向量检索的效率也是问题——知识库大了,检索速度能不能跟上?各个环节怎么串起来,从文本分块到 embedding 再到向量存储最后生成回答,整个流程要跑顺不容易。
对个人开发者来说,硬件限制是更现实的问题。全参数运行的模型需要大量显存,没有几块显卡根本跑不起来。量化技术能降低显存要求,但精度会损失多少?这些问题都要实际测试才能回答。
这篇文章用 Ollama 框架,演示怎么在普通电脑上跑本地 RAG 系统。代码会完整贴出来,你可以直接 copy 去跑。
## 详细步骤
### 环境准备
系统推荐 Ubuntu 22.04 或者 macOS 14 以上。Linux 需要至少 16GB 内存和 30GB 磁盘空间放模型文件。macOS 用户如果有 M1/M2/M3 芯片效果最好。
创建项目目录:
“`bash
mkdir local-rag-system && cd local-rag-system
“`
创建 Python 虚拟环境并安装依赖包:
“`bash
python3 -m venv venv
source venv/bin/activate
pip install ollama langchain langchain-community chromadb sentence-transformers flask markdown
“`
Ollama 的安装方法。Linux 系统用安装脚本:
“`bash
curl -fsSL https://ollama.com/install.sh | sh
“`
macOS 用 Homebrew:
“`bash
brew install ollama
“`
### 下载模型
Ollama 启动后,需要下载语言模型和 embedding 模型。资源有限的情况下,推荐这个组合:语言模型用 llama3:8b-instruct-q4_0,8GB 显存就能跑;embedding 模型用 nomic-embed-text,大概 1GB,CPU 也能跑。
下载命令:
“`bash
ollama pull llama3:8b-instruct-q4_0
ollama pull nomic-embed-text
“`
查看下载结果:
“`bash
ollama list
“`
如果显示模型列表就是下载成功了。网络有问题的话可以试试代理或者换镜像源。
### 编写 RAG 系统代码
创建 `rag_system.py` 文件:
“`python
from langchain_community.chat_models import ChatOllama
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document
from langchain.chains import RetrievalQA
import os
import glob
class LocalRAGSystem:
def __init__(self,
llm_model=”llama3:8b-instruct-q4_0″,
embedding_model=”nomic-embed-text”,
persist_directory=”./chroma_db”):
self.llm_model = llm_model
self.embedding_model = embedding_model
self.persist_directory = persist_directory
self.llm = None
self.qa_chain = None
def initialize_llm(self):
self.llm = ChatOllama(
model=self.llm_model,
temperature=0.7,
base_url=”http://localhost:11434″
)
def initialize_embeddings(self):
self.embeddings = OllamaEmbeddings(
model=self.embedding_model,
base_url=”http://localhost:11434″
)
def load_documents(self, document_path):
documents = []
if os.path.isfile(document_path):
with open(document_path, ‘r’, encoding=’utf-8′) as f:
content = f.read()
documents.append(Document(
page_content=content,
metadata={“source”: document_path}
))
elif os.path.isdir(document_path):
for file_path in glob.glob(os.path.join(document_path, “**/*”), recursive=True):
if file_path.endswith((‘.txt’, ‘.md’, ‘.pdf’, ‘.doc’)):
try:
with open(file_path, ‘r’, encoding=’utf-8′) as f:
content = f.read()
documents.append(Document(
page_content=content,
metadata={“source”: file_path}
))
except Exception as e:
print(f”Warning: Failed to load {file_path}: {e}”)
return documents
def create_vectorstore(self, documents):
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=[“\n\n”, “\n”, “。”, ” “, “”]
)
texts = text_splitter.split_documents(documents)
vectorstore = Chroma.from_documents(
documents=texts,
embedding=self.embeddings,
persist_directory=self.persist_directory
)
return vectorstore
def initialize_qa_chain(self, vectorstore):
self.qa_chain = RetrievalQA.from_chain_type(
llm=self.llm,
chain_type=”stuff”,
retriever=vectorstore.as_retriever(
search_kwargs={“k”: 3}
),
return_source_documents=True
)
def query(self, question):
if self.qa_chain is None:
raise ValueError(“QA chain not initialized. Please build the system first.”)
result = self.qa_chain({“query”: question})
return {
“answer”: result[“result”],
“sources”: [
doc.metadata[“source”]
for doc in result[“source_documents”]
]
}
def build_knowledge_base(system, documents_path=”./knowledge”):
print(“Initializing LLM…”)
system.initialize_llm()
print(“Initializing embeddings…”)
system.initialize_embeddings()
print(“Loading documents…”)
documents = system.load_documents(documents_path)
print(f”Loaded {len(documents)} documents”)
print(“Creating vector store…”)
vectorstore = system.create_vectorstore(documents)
print(“Building QA chain…”)
system.initialize_qa_chain(vectorstore)
return system
if __name__ == “__main__”:
system = LocalRAGSystem()
system = build_knowledge_base(system, “./knowledge”)
while True:
question = input(“\nEnter your question (or ‘quit’ to exit): “)
if question.lower() == ‘quit’:
break
result = system.query(question)
print(“\nAnswer:”, result[“answer”])
print(“\nSources:”, result[“sources”])
“`
### 创建 Web 接口
创建一个简单的 Flask 接口方便调用:
“`python
from flask import Flask, request, jsonify
from rag_system import LocalRAGSystem, build_knowledge_base
import threading
app = Flask(__name__)
system = None
initialized = False
def init_system():
global system, initialized
system = LocalRAGSystem()
system = build_knowledge_base(system, “./knowledge”)
initialized = True
@app.route(‘/api/query’, methods=[‘POST’])
def query():
if not initialized:
return jsonify({“error”: “System not initialized”}), 500
data = request.json
question = data.get(‘question’, ”)
if not question:
return jsonify({“error”: “No question provided”}), 400
result = system.query(question)
return jsonify(result)
@app.route(‘/api/health’, methods=[‘GET’])
def health():
return jsonify({
“status”: “ok” if initialized else “initializing”,
“model”: “llama3:8b-instruct-q4_0”
})
if __name__ == “__main__”:
thread = threading.Thread(target=init_system)
thread.start()
app.run(host=’0.0.0.0′, port=5000, debug=False)
“`
### 准备知识库
在项目目录下创建 `knowledge` 文件夹,放入文档。比如 `knowledge/intro.md`:
“`markdown
# 项目介绍
这是一个基于本地 LLM 的 RAG 问答系统。
## 系统特性
– 完全本地化部署,保护数据隐私
– 支持多种文档格式
– 使用 Chroma 向量数据库进行高效检索
– 基于 Ollama 框架,兼容多种开源模型
## 技术栈
– 语言模型:Llama 3 8B
– 嵌入模型:Nomic Embed Text
– 向量存储:Chroma
– Web 框架:Flask
## 使用方法
1. 准备知识文档,放入 knowledge 文件夹
2. 运行 Python 脚本自动构建索引
3. 通过 Web API 进行问答
“`
## 运行效果
启动系统:
“`bash
python rag_system.py
“`
第一次启动时控制台会显示:
“`
Initializing LLM…
Model loaded successfully
Initializing embeddings…
Embeddings model loaded successfully
Loading documents…
Loaded 5 documents
Creating vector store…
Vector store created and persisted
Building QA chain…
QA chain ready
“`
启动完成后就可以问问题了。
用 Web API 的话,先启动:
“`bash
python api.py
“`
然后用 curl 测试:
“`bash
curl -X POST http://localhost:5000/api/query -H “Content-Type: application/json” -d ‘{“question”:”test”}’
“`
性能方面,16GB 内存加没有独立显卡的情况下,单次查询大概 3-5 秒出结果。向量检索本身只要几毫秒,基本不占用时间。
## 总结
这篇文章从头到尾演示了怎么搭建一个本地 RAG 系统。好处很明显:数据完全保存在本地,不担心隐私问题;不用网络也能用,不受连接限制;一次部署之后使用成本基本为零;想改模型改知识库都很灵活。
当然也有不足。模型能力肯定不如 GPT-4 这种顶级云端模型;速度受硬件限制;模型更新要手动重新下载。实际用的时候可以根据需求选不同的模型组合。
搭一个本地 RAG 系统,对个人开发者和小型团队来说是条可行的 AI 应用路径。随着开源模型越来越强,以后的使用体验还会继续提升。