基于本地 LLM 构建 RAG 系统:完整指南与实践

# 基于本地 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 应用路径。随着开源模型越来越强,以后的使用体验还会继续提升。

暂无评论

发送评论 编辑评论


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