创建个人知识库(lamaIndex + ChromaDB + 本地开源模型)

0 阅读6分钟

我来为你创建一个完整的、零成本的Windows环境搭建指南,使用LlamaIndex + ChromaDB + 本地开源模型。


一、环境准备(Conda)

1. 创建虚拟环境

# 打开Anaconda Prompt或CMD
conda create -n rag python=3.11 -y
conda activate rag

2. 安装核心依赖(一次性复制粘贴)

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

# 从 llama-index 0.10 版本开始,向量存储被拆分为独立的集成包:
pip install llama-index-vector-stores-chroma

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

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

# 本地大模型(Ollama)
pip install llama-index-llms-ollama==0.5.0

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

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

二、安装Ollama(本地大模型)

步骤

  1. 下载安装包:ollama.com/download/wi…
  2. 双击安装(默认装到C盘,无需配置)
  3. 打开 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               # 重建索引

四、完整代码

1. 配置文件 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片段

2. 核心模块 rag_engine.py

"""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. 配置本地嵌入模型(首次会自动下载,约100MB)
        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  # 创造性 vs 确定性平衡
        )
        
        # 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} 文件夹")
        
        # 加载文档(自动识别PDF/Word/TXT)
        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)
        }

3. 交互式查询 query.py

"""交互式查询界面"""

from rag_engine import RAGEngine

def main():
    # 初始化(首次会下载模型,约100MB嵌入模型)
    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()

4. 索引重建工具 rebuild.py

"""重建索引(文档更新后使用)"""

import shutil
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__":
    import os
    main()

5. 批量导入工具 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()

五、快速开始步骤

1. 创建项目文件夹

# 在D盘或E盘创建(避免C盘权限问题)
mkdir D:\rag_project
cd D:\rag_project

# 创建文件夹
mkdir data
mkdir chroma_db

2. 保存代码

将上面的5个代码文件保存到 D:\rag_project 目录。

3. 放入测试文档

随便找几个PDF/Word/TXT文件,放入 data 文件夹。

4. 运行

创建索引


六、预期输出

🚀 正在初始化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,或转换为TXT

八、零成本确认清单

  • ✅ LlamaIndex:开源免费

  • ✅ ChromaDB:本地运行,免费

  • ✅ 嵌入模型:本地下载,免费

  • ✅ 大模型:Ollama本地运行,免费

  • ✅ 文档解析:开源库,免费

总成本:0元,无需任何API Key