本地开源模型构建个人知识库教程

作者:技术拾光时间:2026-07-29 21:08:20浏览:544

介绍在Windows上搭建完全本地RAG系统的方案。核心采用LlamaIndex、ChromaDB和Ollama本地模型,无需联网与API密钥。详细说明了从创建虚拟环境、安装依赖、配置模型到构建项目结构和核心代码的完整步骤,并提供了交互查询与索引管理功能。

想在Windows系统上构建一个完全本地的RAG(检索增强生成)应用,同时希望控制成本并降低技术复杂度?本方案将提供一套零成本、步骤清晰的完整指南,涵盖从环境搭建到代码运行的每个环节。方案核心基于LlamaIndex、ChromaDB和Ollama本地大模型,确保全过程无需联网、无需调用任何付费API。

环境准备:使用Conda

首先,需要一个独立的Python环境来管理项目依赖。Conda工具能有效避免不同项目间的版本冲突。

创建虚拟环境

打开Anaconda Prompt或系统命令行,执行以下命令来创建并激活名为“rag”的虚拟环境:

# 创建Python 3.11的虚拟环境
conda create -n rag python=3.11 -y
# 激活环境
conda activate rag

安装核心依赖

激活环境后,一次性安装所有必要的库。以下版本经过兼容性测试。

# 基础框架
pip install llama-index==0.12.0

# 向量存储集成包(自0.10版本起独立)
pip install llama-index-vector-stores-chroma

# 向量数据库
pip install chromadb==0.5.20

# 本地嵌入模型(针对中文优化)
pip install llama-index-embeddings-huggingface==0.4.0

# 本地大模型集成
pip install llama-index-llms-ollama==0.5.0

# 文档解析支持
pip install pypdf # 用于PDF文件
pip install python-docx # 用于Word文件

# 其他工具
pip install tiktoken # 用于Token计算

安装Ollama本地大模型

Ollama简化了本地大模型的下载与管理流程。

安装步骤

  1. 下载安装包:访问Ollama官网(ollama.com),下载Windows版本并完成安装。建议使用默认安装路径。
  2. 拉取模型:安装完成后,打开CMD或PowerShell,拉取一个适合您电脑配置的模型。例如:
# 拉取轻量级模型(约3GB,适合8G内存)
ollama pull llama3.2
# 或拉取对中文支持较好的模型(约4GB)
ollama pull qwen2.5:7b

安装完成后,可通过以下命令验证:

ollama list
# 成功后将显示已拉取的模型,如:llama3.2 或 qwen2.5:7b

项目目录结构

建议按以下结构组织项目文件夹,便于清晰管理:

rag_project/
├── data/          # 存放待处理的文档(PDF/Word/TXT等)
│   ├── 员工手册.pdf
│   └── 产品说明.docx
├── chroma_db/     # 向量数据库存储目录(程序自动创建)
├── app.py         # 主程序(可选,用于集成)
├── query.py       # 交互式查询脚本
└── rebuild.py     # 重建索引工具

核心代码文件

将以下代码文件保存到您的项目目录中。

配置文件 `config.py`

集中管理项目参数,方便修改。

"""配置参数 - 全部免费,零API Key"""
import os

# 路径配置
DATA_DIR = "./data"          # 文档文件夹
CHROMA_PATH = "./chroma_db"  # 向量库本地存储路径
COLLECTION_NAME = "my_knowledge"  # 集合名称

# 模型配置(完全本地运行)
EMBED_MODEL = "BAAI/bge-small-zh-v1.5"  # 中文嵌入模型,约100MB
LLM_MODEL = "llama3.2"                  # 或 "qwen2.5:7b"

# RAG参数
CHUNK_SIZE = 500    # 文本分块大小(字符)
CHUNK_OVERLAP = 50  # 块间重叠字符数(保持上下文连贯)
TOP_K = 3           # 检索最相关的Top 3个文本片段

核心引擎 `rag_engine.py`

封装RAG系统的初始化、索引构建和查询逻辑。

"""RAG引擎 - 封装所有逻辑"""
import os
import chromadb
from config import *
from llama_index.core import (VectorStoreIndex, SimpleDirectoryReader,
                              Settings, StorageContext, Document)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.ollama import Ollama

class RAGEngine:
    def __init__(self):
        """初始化:加载模型、连接数据库"""
        print("? 正在初始化RAG引擎...")

        # 1. 配置本地嵌入模型(首次运行会自动下载)
        print(f"? 加载嵌入模型: {EMBED_MODEL}")
        Settings.embed_model = HuggingFaceEmbedding(
            model_name=EMBED_MODEL,
            trust_remote_code=True
        )

        # 2. 配置本地大模型(通过Ollama)
        print(f"? 连接Ollama模型: {LLM_MODEL}")
        Settings.llm = Ollama(
            model=LLM_MODEL,
            request_timeout=120.0,
            temperature=0.7  # 控制创造性,0更确定,1更随机
        )

        # 3. 连接ChromaDB(本地文件存储)
        print(f"? 连接向量数据库: {CHROMA_PATH}")
        self.chroma_client = chromadb.PersistentClient(path=CHROMA_PATH)
        self.collection = self.chroma_client.get_or_create_collection(
            name=COLLECTION_NAME,
            metadata={"hnsw:space": "cosine"}  # 使用余弦相似度检索
        )
        self.vector_store = ChromaVectorStore(chroma_collection=self.collection)

        # 4. 构建或加载索引
        self.index = self._build_index()
        print(" 初始化完成!\n")

    def _build_index(self):
        """构建或加载向量索引"""
        storage_context = StorageContext.from_defaults(vector_store=self.vector_store)

        # 检查是否已有数据
        if self.collection.count() > 0:
            print(f"? 发现已有索引,加载 {self.collection.count()} 条记录")
            return VectorStoreIndex.from_vector_store(
                vector_store=self.vector_store,
                storage_context=storage_context
            )

        # 新建索引
        print("? 创建新索引...")
        if not os.path.exists(DATA_DIR) or not os.listdir(DATA_DIR):
            raise FileNotFoundError(f"请先将文档放入 {DATA_DIR} 文件夹")

        # 加载文档(自动识别格式)
        print(f"? 正在读取文档...")
        documents = SimpleDirectoryReader(
            DATA_DIR,
            required_exts=[".pdf", ".docx", ".doc", ".txt", ".md"],
            filename_as_id=True
        ).load_data()
        print(f"  找到 {len(documents)} 个文件")

        # 设置文档处理管道(分块)
        parser = SentenceSplitter(
            chunk_size=CHUNK_SIZE,
            chunk_overlap=CHUNK_OVERLAP,
            paragraph_separator="\n\n"
        )

        # 构建索引(自动完成分块、向量化、存储)
        index = VectorStoreIndex.from_documents(
            documents,
            storage_context=storage_context,
            transformations=[parser],
            show_progress=True
        )
        print(f" 索引创建完成,共 {self.collection.count()} 个片段")
        return index

    def query(self, question: str) -> dict:
        """查询知识库"""
        # 配置检索器
        retriever = self.index.as_retriever(
            similarity_top_k=TOP_K,
            vector_store_query_mode="default"
        )

        # 创建查询引擎
        query_engine = self.index.as_query_engine(
            retriever=retriever,
            response_mode="compact",  # 紧凑模式,节省token
            verbose=False
        )

        # 执行查询
        print(f" 查询: {question}")
        response = query_engine.query(question)

        return {
            "answer": str(response),
            "sources": [node.node.metadata.get("file_name", "未知")
                       for node in response.source_nodes],
            "chunks": [node.text[:200] + "..." for node in response.source_nodes]
        }

    def add_document(self, file_path: str):
        """动态添加单个文档"""
        from llama_index.core import SimpleDirectoryReader
        documents = SimpleDirectoryReader(input_files=[file_path]).load_data()
        for doc in documents:
            self.index.insert(doc)
        print(f" 已添加: {os.path.basename(file_path)}")

    def get_stats(self):
        """获取知识库统计"""
        return {
            "文档片段数": self.collection.count(),
            "嵌入模型": EMBED_MODEL,
            "大模型": LLM_MODEL,
            "存储路径": os.path.abspath(CHROMA_PATH)
        }

交互查询脚本 `query.py`

提供命令行交互界面,用于向知识库提问。

"""交互式查询界面"""
from rag_engine import RAGEngine

def main():
    # 初始化(首次运行会下载嵌入模型)
    engine = RAGEngine()

    # 显示统计
    print(" 知识库状态:")
    for k, v in engine.get_stats().items():
        print(f"  {k}: {v}")
    print("\n" + "="*50)

    # 交互循环
    print(" 输入问题(输入 'quit' 退出,输入 'stats' 查看统计)\n")
    while True:
        try:
            question = input(" 你的问题: ").strip()
            if question.lower() == 'quit':
                print(" 再见!")
                break
            elif question.lower() == 'stats':
                print(engine.get_stats())
                continue
            elif not question:
                continue

            # 执行查询
            result = engine.query(question)
            print(f"\n 回答:\n{result['answer']}\n")
            print(" 参考来源:")
            for i, (src, chunk) in enumerate(zip(result['sources'], result['chunks']), 1):
                print(f"  [{i}] {src}")
                print(f"     片段: {chunk}\n")
            print("-"*50)
        except KeyboardInterrupt:
            print("\n 再见!")
            break
        except Exception as e:
            print(f" 错误: {e}")

if __name__ == "__main__":
    main()

索引重建工具 `rebuild.py`

当更新`data`文件夹中的文档后,使用此脚本清空旧索引并重新构建。

"""重建索引(文档更新后使用)"""
import shutil
import os
from config import CHROMA_PATH
from rag_engine import RAGEngine

def main():
    print(" 警告: 这将删除现有索引并重建")
    confirm = input("确认删除并重建 (yes/no): ")
    if confirm.lower() != 'yes':
        print("已取消")
        return

    # 删除旧数据库
    if os.path.exists(CHROMA_PATH):
        shutil.rmtree(CHROMA_PATH)
        print(f" 已删除: {CHROMA_PATH}")

    # 重新初始化(自动重建)
    engine = RAGEngine()
    print(" 重建完成")

if __name__ == "__main__":
    main()

批量导入工具 `batch_import.py`

用于快速导入大批量文档。

"""批量导入文件夹中的文档"""
import os
from rag_engine import RAGEngine

def main():
    engine = RAGEngine()
    folder = input("请输入要导入的文件夹路径: ").strip()
    if not os.path.exists(folder):
        print(" 路径不存在")
        return

    files = [f for f in os.listdir(folder)
             if f.endswith(('.pdf', '.docx', '.doc', '.txt', '.md'))]
    print(f"找到 {len(files)} 个文档")

    for f in files:
        full_path = os.path.join(folder, f)
        try:
            engine.add_document(full_path)
        except Exception as e:
            print(f" 导入失败 {f}: {e}")

    print(f"\n 导入完成,当前共 {engine.get_stats()['文档片段数']} 个片段")

if __name__ == "__main__":
    main()

快速开始步骤

准备好代码后,按以下步骤运行。

创建项目文件夹

建议在非系统盘(如D盘或E盘)创建项目,避免权限问题。

# 在D盘创建项目文件夹
mkdir D:\rag_project
cd D:\rag_project
# 创建必要的子文件夹
mkdir data
mkdir chroma_db

保存代码文件

将上述五个Python文件(`config.py`, `rag_engine.py`, `query.py`, `rebuild.py`, `batch_import.py`)保存到`D:\rag_project`目录。

放入测试文档

准备若干PDF、Word或TXT格式的文档,例如公司制度、产品手册等,放入`data`文件夹。

运行程序

在`D:\rag_project`目录下打开命令行(确保已激活`rag`虚拟环境),运行查询脚本:

python query.py

程序将首次初始化,下载嵌入模型并构建索引,之后即可开始提问。

运行效果示例

成功运行后,命令行将显示类似以下的交互过程:

 正在初始化RAG引擎...
 加载嵌入模型: BAAI/bge-small-zh-v1.5
 连接Ollama模型: llama3.2
 连接向量数据库: ./chroma_db
 创建新索引...
 正在读取文档...
  找到 3 个文件
 索引创建完成,共 15 个片段
 初始化完成!

 知识库状态:
  文档片段数: 15
  嵌入模型: BAAI/bge-small-zh-v1.5
  大模型: llama3.2
  存储路径: D:\rag_project\chroma_db

==================================================
 输入问题(输入 'quit' 退出,输入 'stats' 查看统计)

 你的问题: 公司的年假有多少天?
 查询: 公司的年假有多少天?

 回答:
根据《员工手册》规定,入职满1年可享受5天带薪年假,满10年可享受10天...

 参考来源:
  [1] 员工手册.pdf
      片段: 第五章 休假制度 5.1 年假规定:员工累计工作已满1年不满10年的,年休假5天...
  [2] 人力资源政策.docx
      片段: 年假申请流程:提前3天在OA系统提交申请,经部门主管审批...
--------------------------------------------------

常见问题与解决方法

问题 解决方法
Ollama connection refused 确保Ollama后台服务已启动(任务栏应有图标),或在命令行运行 ollama serve
中文显示乱码 在CMD窗口输入 chcp 65001 切换到UTF-8编码
内存不足 尝试拉取更小的模型:ollama pull llama3.2:1b(约需1GB内存)
导入PDF失败 尝试安装 pip install pypdf2,或将PDF文件转换为TXT格式

零成本确认清单

  • LlamaIndex:开源框架,免费使用。
  • ChromaDB:向量数据库本地运行,免费。
  • 嵌入模型:从Hugging Face本地下载,免费。
  • 大模型:通过Ollama在本地运行,免费。
  • 文档解析库:如pypdf,开源免费。

总成本:0元。整个过程无需任何API密钥,所有计算和存储均在本地计算机完成。