在大模型应用落地过程中,"数据私有化""无网络依赖""低成本部署"成为企业和开发者的核心诉求------传统RAG方案要么依赖第三方API(存在数据泄露风险、高调用成本),要么部署复杂、门槛过高,难以快速落地。为此,我们基于LlamaIndex框架,打造了这套全本地RAG项目模板,无需调用OpenAI等第三方API,无需复杂配置,整合本地模型部署、本地向量库存储、API服务搭建三大核心能力,适配PDF、TXT、DOCX等常见文档格式,支持单轮问答、多轮对话、文档批量导入,兼顾易用性与可扩展性。
一、项目说明
本项目是基于 LlamaIndex 构建的 RAG(检索增强生成)完整模板,实现本地模型部署 (无需调用OpenAI API)、本地向量库存储 、API接口服务,支持批量导入文档、自然语言问答、多轮对话,可直接部署运行,适配PDF、TXT、DOCX等常见文档格式。
核心优势:轻量化部署、无网络依赖(模型和数据均本地存储)、可灵活替换模型/向量库、API可直接对接前端。
二、环境准备
2.1 系统要求
- 系统:Windows 10+/Linux/Ubuntu(推荐Linux,部署更便捷)
- 内存:≥16G(本地模型运行最低要求,32G更佳)
- 显卡:支持CUDA(可选,加速模型推理,无显卡则用CPU推理,速度较慢)
2.2 依赖安装
创建虚拟环境(推荐,避免依赖冲突),执行以下命令安装所有依赖:
shell
# 1. 创建虚拟环境(Python 3.8+)
python -m venv rag-venv
# 激活虚拟环境(Windows)
rag-venv\Scripts\activate
# 激活虚拟环境(Linux/Mac)
source rag-venv/bin/activate
# 2. 安装核心依赖
pip install llama-index==0.10.35 # 稳定版本,兼容性好
pip install llama-index-llms-huggingface # 本地LLM集成
pip install llama-index-embeddings-huggingface # 本地Embedding模型
pip install llama-index-vector-stores-chroma # 本地向量库(轻量易用)
pip install fastapi uvicorn # API服务搭建
pip install pydantic==2.5.2 # 数据校验(适配LlamaIndex)
pip install python-multipart # 文档上传支持
pip install PyPDF python-docx # 文档解析支持
pip install torch torchvision torchaudio # 模型运行依赖(根据系统选择是否安装CUDA版本)
三、项目结构
项目采用模块化设计,结构清晰,可直接复制使用,后续可根据需求扩展:
bash
llamaindex-rag-project/
├── config/ # 配置文件目录
│ └── config.py # 模型、向量库、API配置
├── data/ # 文档存储目录(可放入PDF/TXT/DOCX)
├── vector_db/ # 本地向量库存储目录(自动生成)
├── src/ # 核心代码目录
│ ├── __init__.py
│ ├── rag_engine.py # RAG核心逻辑(文档加载、索引构建、问答)
│ └── api_server.py # API服务(问答、文档上传接口)
├── main.py # 项目入口(本地运行/API启动)
└── requirements.txt # 依赖清单(可用于快速安装)
四、核心代码实现
4.1 配置文件(config/config.py)
统一配置模型、向量库、文档路径,可灵活修改,无需改动核心代码:
Python
# config/config.py
import os
# 项目路径配置
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DATA_DIR = os.path.join(BASE_DIR, "data") # 文档存放目录
VECTOR_DB_DIR = os.path.join(BASE_DIR, "vector_db") # 向量库存储目录
os.makedirs(DATA_DIR, exist_ok=True)
os.makedirs(VECTOR_DB_DIR, exist_ok=True)
# 本地LLM配置(默认使用轻量化模型,可替换为其他模型)
LLM_CONFIG = {
"model_name": "lmsys/vicuna-7b-v1.5", # 轻量化开源模型,适合本地部署
"temperature": 0.1, # 生成答案的随机性,越低越精准
"max_new_tokens": 512, # 最大生成token数
"device": "cuda" if torch.cuda.is_available() else "cpu", # 自动选择设备(CPU/GPU)
}
# Embedding模型配置(用于文本向量化)
EMBEDDING_CONFIG = {
"model_name": "all-MiniLM-L6-v2", # 轻量高效,适合本地使用
"device": "cuda" if torch.cuda.is_available() else "cpu",
}
# 向量库配置(Chroma,本地轻量向量库)
VECTOR_STORE_CONFIG = {
"persist_dir": VECTOR_DB_DIR,
"collection_name": "rag_collection", # 向量库集合名称
}
# API服务配置
API_CONFIG = {
"host": "0.0.0.0", # 允许外部访问
"port": 8000, # API端口
}
# 文档加载配置(支持的文档格式)
DOCUMENT_LOADER_CONFIG = {
"supported_formats": [".pdf", ".txt", ".docx"],
}
4.2 RAG核心逻辑(src/rag_engine.py)
实现文档加载、索引构建、单轮问答、多轮对话核心功能,封装成工具类,方便调用:
Python
# src/rag_engine.py
from llama_index.core import (
SimpleDirectoryReader,
VectorStoreIndex,
ServiceContext,
ChatEngine,
)
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.vector_stores.chroma import ChromaVectorStore
from chromadb import PersistentClient
from config.config import (
DATA_DIR,
VECTOR_STORE_CONFIG,
LLM_CONFIG,
EMBEDDING_CONFIG,
DOCUMENT_LOADER_CONFIG,
)
import os
class RAGEngine:
def __init__(self):
# 1. 初始化本地LLM
self.llm = self._init_llm()
# 2. 初始化Embedding模型
self.embedding = self._init_embedding()
# 3. 初始化服务上下文(整合LLM和Embedding)
self.service_context = ServiceContext.from_defaults(
llm=self.llm,
embed_model=self.embedding,
chunk_size=512, # 文本分块大小,根据模型调整
chunk_overlap=50, # 分块重叠度,提升检索连贯性
)
# 4. 初始化向量库
self.vector_store = self._init_vector_store()
# 5. 加载文档并构建索引(首次运行会构建,后续直接加载)
self.index = self._build_index()
# 6. 初始化聊天引擎(支持多轮对话)
self.chat_engine = self._init_chat_engine()
def _init_llm(self):
"""初始化本地LLM模型"""
llm = HuggingFaceLLM(
model_name=LLM_CONFIG["model_name"],
temperature=LLM_CONFIG["temperature"],
max_new_tokens=LLM_CONFIG["max_new_tokens"],
device_map=LLM_CONFIG["device"],
# 模型加载参数(优化本地运行速度)
model_kwargs={"torch_dtype": "auto", "load_in_8bit": True},
)
return llm
def _init_embedding(self):
"""初始化Embedding模型"""
embedding = HuggingFaceEmbedding(
model_name=EMBEDDING_CONFIG["model_name"],
device=EMBEDDING_CONFIG["device"],
)
return embedding
def _init_vector_store(self):
"""初始化Chroma本地向量库"""
client = PersistentClient(path=VECTOR_STORE_CONFIG["persist_dir"])
vector_store = ChromaVectorStore(
client=client,
collection_name=VECTOR_STORE_CONFIG["collection_name"],
)
return vector_store
def _build_index(self):
"""加载文档、构建索引(首次构建,后续直接加载)"""
# 加载指定目录下的所有支持格式文档
reader = SimpleDirectoryReader(
input_dir=DATA_DIR,
required_exts=DOCUMENT_LOADER_CONFIG["supported_formats"],
recursive=True, # 递归加载子目录文档
)
documents = reader.load_data()
# 构建向量索引(如果向量库已有数据,会自动加载,无需重新构建)
index = VectorStoreIndex.from_documents(
documents=documents,
service_context=self.service_context,
vector_store=self.vector_store,
show_progress=True, # 显示构建进度
)
# 持久化索引(确保下次运行无需重新构建)
index.storage_context.persist(persist_dir=VECTOR_STORE_CONFIG["persist_dir"])
return index
def single_qa(self, query: str) -> str:
"""单轮问答(无上下文记忆)"""
query_engine = self.index.as_query_engine(service_context=self.service_context)
response = query_engine.query(query)
return str(response)
def chat_qa(self, query: str, chat_history: list = None) -> str:
"""多轮对话(有上下文记忆)"""
if chat_history is None:
chat_history = []
# 调用聊天引擎,传入历史对话和当前查询
response = self.chat_engine.chat(query, chat_history=chat_history)
# 更新对话历史
chat_history.append((query, str(response)))
return str(response), chat_history
def reload_index(self):
"""重新加载文档并构建索引(当新增文档后调用)"""
self.index = self._build_index()
self.chat_engine = self._init_chat_engine()
return "索引重新构建完成"
def _init_chat_engine(self):
"""初始化聊天引擎(支持多轮对话)"""
chat_engine = self.index.as_chat_engine(
service_context=self.service_context,
chat_mode="context", # 基于上下文对话
memory_key="chat_history",
verbose=True, # 显示对话日志(可选)
)
return chat_engine
4.3 API服务(src/api_server.py)
基于FastAPI搭建API接口,支持单轮问答、多轮对话、文档上传、索引刷新,可直接对接前端或其他服务:
Python
# src/api_server.py
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from src.rag_engine import RAGEngine
from config.config import DATA_DIR, API_CONFIG
import os
import shutil
# 初始化FastAPI应用
app = FastAPI(title="LlamaIndex RAG API", version="1.0.0")
# 初始化RAG引擎(全局单例,避免重复加载模型)
rag_engine = RAGEngine()
# 数据模型(用于API请求参数校验)
class SingleQuery(BaseModel):
query: str
class ChatQuery(BaseModel):
query: str
chat_history: list = None # 格式:[(用户提问1, 机器人回答1), (用户提问2, 机器人回答2)]
# 1. 单轮问答接口
@app.post("/api/single-qa", summary="单轮问答(无上下文)")
async def single_qa(query: SingleQuery):
try:
if not query.query.strip():
raise HTTPException(status_code=400, detail="提问不能为空")
response = rag_engine.single_qa(query.query)
return JSONResponse({"code": 200, "message": "success", "data": {"answer": response}})
except Exception as e:
return JSONResponse({"code": 500, "message": str(e), "data": None})
# 2. 多轮对话接口
@app.post("/api/chat-qa", summary="多轮对话(有上下文)")
async def chat_qa(query: ChatQuery):
try:
if not query.query.strip():
raise HTTPException(status_code=400, detail="提问不能为空")
answer, chat_history = rag_engine.chat_qa(query.query, query.chat_history)
return JSONResponse({
"code": 200,
"message": "success",
"data": {"answer": answer, "chat_history": chat_history}
})
except Exception as e:
return JSONResponse({"code": 500, "message": str(e), "data": None})
# 3. 文档上传接口(上传后自动刷新索引)
@app.post("/api/upload-document", summary="上传文档(支持PDF/TXT/DOCX)")
async def upload_document(file: UploadFile = File(...)):
try:
# 校验文件格式
file_ext = os.path.splitext(file.filename)[1].lower()
if file_ext not in [".pdf", ".txt", ".docx"]:
raise HTTPException(status_code=400, detail="仅支持PDF、TXT、DOCX格式")
# 保存文件到data目录
file_path = os.path.join(DATA_DIR, file.filename)
with open(file_path, "wb") as f:
shutil.copyfileobj(file.file, f)
# 刷新索引(加载新上传的文档)
rag_engine.reload_index()
return JSONResponse({"code": 200, "message": "文档上传成功,索引已刷新", "data": {"filename": file.filename}})
except Exception as e:
return JSONResponse({"code": 500, "message": str(e), "data": None})
# 4. 索引刷新接口(手动刷新)
@app.get("/api/reload-index", summary="手动刷新索引(新增文档后调用)")
async def reload_index():
try:
message = rag_engine.reload_index()
return JSONResponse({"code": 200, "message": message, "data": None})
except Exception as e:
return JSONResponse({"code": 500, "message": str(e), "data": None})
# 5. 健康检查接口
@app.get("/api/health", summary="服务健康检查")
async def health_check():
return JSONResponse({"code": 200, "message": "服务正常运行", "data": None})
# 启动API服务
def run_api():
import uvicorn
uvicorn.run(
app="src.api_server:app",
host=API_CONFIG["host"],
port=API_CONFIG["port"],
reload=True, # 开发环境开启热重载,生产环境关闭
)
4.4 项目入口(main.py)
统一入口,支持本地运行问答、启动API服务,按需选择:
Python
# main.py
from src.rag_engine import RAGEngine
from src.api_server import run_api
import argparse
def local_qa_demo():
"""本地问答演示(无需启动API,直接在终端交互)"""
print("="*50)
print("LlamaIndex RAG 本地问答演示")
print("提示:输入'quit'退出问答,输入'reload'刷新索引")
print("="*50)
rag_engine = RAGEngine()
chat_history = []
while True:
query = input("\n请输入你的问题:")
if query.lower() == "quit":
print("退出问答,再见!")
break
if query.lower() == "reload":
message = rag_engine.reload_index()
print(f"系统提示:{message}")
continue
# 多轮对话模式
answer, chat_history = rag_engine.chat_qa(query, chat_history)
print(f"\n回答:{answer}")
if __name__ == "__main__":
# 解析命令行参数,选择运行模式
parser = argparse.ArgumentParser(description="LlamaIndex RAG 项目入口")
parser.add_argument(
"--mode",
type=str,
default="local",
choices=["local", "api"],
help="运行模式:local(本地终端问答)、api(启动API服务)"
)
args = parser.parse_args()
if args.mode == "local":
local_qa_demo()
elif args.mode == "api":
run_api()
4.5 依赖清单(requirements.txt)
ini
llama-index==0.10.35
llama-index-llms-huggingface==0.1.10
llama-index-embeddings-huggingface==0.1.7
llama-index-vector-stores-chroma==0.1.2
fastapi==0.109.0
uvicorn==0.25.0
pydantic==2.5.2
python-multipart==0.0.6
PyPDF==3.17.4
python-docx==1.1.0
torch==2.1.2
chromadb==0.4.24
transformers==4.36.2
sentence-transformers==2.2.2
五、运行步骤
5.1 本地终端问答模式
- 将需要检索的文档(PDF/TXT/DOCX)放入
data/目录; - 执行命令:
python main.py --mode local; - 首次运行会自动下载模型(vicuna-7b-v1.5、all-MiniLM-L6-v2),耐心等待(模型较大,约13GB);
- 模型加载完成后,在终端输入问题即可进行问答,输入
quit退出,输入reload刷新索引(新增文档后使用)。
5.2 API服务模式
- 将需要检索的文档(PDF/TXT/DOCX)放入
data/目录; - 执行命令:
python main.py --mode api; - API服务启动后,访问
http://localhost:8000/docs可查看API文档(自动生成),可直接在网页测试接口; - 接口调用示例(以单轮问答为例,使用Postman或curl):
curl -X POST "http://localhost:8000/api/single-qa" -H "Content-Type: application/json" -d '{"query": "文档的核心内容是什么?"}'
六、关键优化与扩展建议
6.1 模型替换(按需调整)
默认使用轻量化模型,可根据硬件配置替换为更优模型:
- LLM模型:替换
config.py中LLM_CONFIG["model_name"],如"baichuan2-7b-chat"、"Qwen-7B-Chat"(均为开源中文友好模型); - Embedding模型:替换
EMBEDDING_CONFIG["model_name"],如"m3e-base"(中文向量化效果更好)。
6.2 性能优化
- 有GPU的情况下,确保安装CUDA版本的torch,模型推理速度可提升5-10倍;
- 调整
rag_engine.py中的chunk_size(分块大小),根据文档长度调整(长文档可设为1024,短文档设为256); - 模型加载时启用
load_in_8bit(已配置),减少内存占用。
6.3 功能扩展
- 新增文档格式支持:在
DOCUMENT_LOADER_CONFIG中添加格式,同时安装对应解析依赖; - 替换向量库:将Chroma替换为FAISS、Pinecone(需修改
rag_engine.py中向量库初始化逻辑); - 添加权限控制:在API接口中添加Token验证,适配生产环境;
- 前端对接:通过API接口对接Vue/React前端,实现可视化问答界面。
七、常见问题解决
- 问题1:模型下载缓慢/失败? 解决:手动下载模型(从Hugging Face官网),放入
~/.cache/huggingface/models目录,重新运行。 - 问题2:CPU推理速度过慢? 解决:安装CUDA,使用GPU推理;或替换为更小的模型(如
"vicuna-4b-v1.5")。 - 问题3:上传文档后无法检索到内容? 解决:上传文档后,调用
/api/reload-index接口刷新索引,或重启服务。 - 问题4:依赖冲突? 解决:使用虚拟环境,严格按照
requirements.txt中的版本安装依赖。