LlamaIndex RAG 本地部署+API服务,快速搭建一个知识库检索助手

在大模型应用落地过程中,"数据私有化""无网络依赖""低成本部署"成为企业和开发者的核心诉求------传统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 本地终端问答模式

  1. 将需要检索的文档(PDF/TXT/DOCX)放入 data/ 目录;
  2. 执行命令:python main.py --mode local
  3. 首次运行会自动下载模型(vicuna-7b-v1.5、all-MiniLM-L6-v2),耐心等待(模型较大,约13GB);
  4. 模型加载完成后,在终端输入问题即可进行问答,输入quit退出,输入reload刷新索引(新增文档后使用)。

5.2 API服务模式

  1. 将需要检索的文档(PDF/TXT/DOCX)放入 data/ 目录;
  2. 执行命令:python main.py --mode api
  3. API服务启动后,访问http://localhost:8000/docs 可查看API文档(自动生成),可直接在网页测试接口;
  4. 接口调用示例(以单轮问答为例,使用Postman或curl): curl -X POST "http://localhost:8000/api/single-qa" -H "Content-Type: application/json" -d '{"query": "文档的核心内容是什么?"}'

六、关键优化与扩展建议

6.1 模型替换(按需调整)

默认使用轻量化模型,可根据硬件配置替换为更优模型:

  • LLM模型:替换 config.pyLLM_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 中的版本安装依赖。
相关推荐
MX_93592 小时前
SpringMVC请求参数
java·后端·spring·servlet·apache
gujunge2 小时前
Spring with AI (6): 记忆保持——会话与长期记忆
ai·大模型·llm·openai·qwen·rag·spring ai·deepseek
忆想不到的晖3 小时前
Codex 探索:别急着调 Prompt,先把工作流收住
后端·agent·ai编程
程序员陆业聪3 小时前
Claude Code 深度拆解:它凭什么被称为「最接近真实工程师」的 AI 编码工具
ai编程
weixin_408099673 小时前
【实战对比】在线 OCR 识别 vs OCR API 接口:从个人工具到系统集成该怎么选?
图像处理·人工智能·后端·ocr·api·图片文字识别·文字识别ocr
Victor3564 小时前
MongoDB(73)如何设置用户权限?
后端
Victor3564 小时前
MongoDB(74)什么是数据库级别和集合级别的访问控制?
后端
布列瑟农的星空4 小时前
Minimax发布2.7了,它的编程能力提升了多少?
ai编程
计算机学姐5 小时前
基于SpringBoot的咖啡店管理系统【个性化推荐+数据可视化统计+配送信息】
java·vue.js·spring boot·后端·mysql·信息可视化·tomcat