从零搭建企业级本地知识库:FastAPI + ChromaDB + Ollama 完全离线方案

0 阅读6分钟

从零搭建企业级本地知识库:FastAPI + ChromaDB + Ollama 完全离线方案

目录


概述

本文介绍如何搭建一个完全离线的企业级本地知识库系统,核心特性:

  • 完全离线运行:无需联网,数据不出内网
  • 持久化存储:重启后数据不丢失
  • 向量检索:基于 ChromaDB 的语义搜索
  • 大模型对话:集成 Ollama 本地 LLM
  • 双端支持:Web 界面 + Streamlit 调试工具

适用场景:企业内部文档管理、敏感数据处理、无公网环境部署。

image.png


技术架构

┌─────────────────────────────────────────────────────────┐
│                    用户交互层                            │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐  │
│  │  Web 前端   │    │ Streamlit   │    │  REST API   │  │
│  │  (HTML/JS)  │    │  调试工具   │    │   接口      │  │
│  └──────┬──────┘    └──────┬──────┘    └─────────────┘  │
└─────────┼──────────────────┼─────────────────────────────┘
          │                  │
┌─────────▼──────────────────▼─────────────────────────────┐
│                    服务层 (FastAPI)                      │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐  │
│  │  文档上传   │───▶│  文本分块   │───▶│  向量存储   │  │
│  │  /api/upload│    │  chunk_text │    │  ChromaDB   │  │
│  └─────────────┘    └─────────────┘    └──────┬──────┘  │
│  ┌─────────────┐    ┌─────────────┐          │         │
│  │  对话接口   │◀───│  向量检索   │◀─────────┘         │
│  │  /api/chat  │    │  search()   │                     │
│  └──────┬──────┘    └─────────────┘                     │
└─────────┼───────────────────────────────────────────────┘
          │
┌─────────▼───────────────────────────────────────────────┐
│                    数据层                               │
│  ┌─────────────────┐    ┌───────────────────────────┐  │
│  │  ChromaDB 向量库 │    │  Ollama LLM API           │  │
│  │  /www/.../chroma │    │  http://host:11434        │  │
│  └─────────────────┘    │  deepseek-r1:1.5b         │  │
│  ┌─────────────────┐    └───────────────────────────┘  │
│  │  原始文档存储    │                                    │
│  │  /www/.../uploads│                                    │
│  └─────────────────┘                                    │
└─────────────────────────────────────────────────────────┘

环境准备

1. 系统要求

# Ubuntu 20.04+ / CentOS 7+
# Python 3.10+
# 内存:4GB+(根据模型大小调整)
# 磁盘:10GB+(用于存储向量数据)

2. 安装 Ollama

# 安装 Ollama
curl -fsSL https://ollama.com/install.sh | sh
​
# 拉取模型(以 deepseek-r1:1.5b 为例,约 1.1GB)
ollama pull deepseek-r1:1.5b
​
# 启动服务(默认监听 11434)
ollama serve

3. Python 依赖

# pyproject.toml
[project]
name = "knowledge-base"
version = "1.0.0"
dependencies = [
    "fastapi>=0.104.1",
    "uvicorn[standard]>=0.24.0",
    "chromadb>=0.4.18",
    "requests>=2.31.0",
    "pypdf2>=3.0.1",
    "numpy>=1.26.2",
    "python-multipart>=0.0.6",
    "python-dotenv>=1.0.0",
    "streamlit>=1.28.0",
]

核心模块解析

1. 配置管理(config.py)

关键:使用绝对路径,避免工作目录问题

# 宝塔部署必须使用绝对路径
CHROMA_DB_PATH = "/www/wwwroot/knowledge_base/chroma_db"
UPLOAD_DIR = "/www/wwwroot/knowledge_base/uploads"# Ollama 配置
OLLAMA_BASE_URL = "http://192.168.31.6:11434"
OLLAMA_MODEL = "deepseek-r1:1.5b"# 文本处理参数
CHUNK_SIZE = 500       # 每块 500 字符
CHUNK_OVERLAP = 50     # 重叠 50 字符(保持上下文)
TOP_K = 5              # 检索返回 5 个最相关片段

2. 离线向量化(vector_store.py)

不依赖 HuggingFace,完全离线

class VectorStore:
    def _embed_texts(self, texts: List[str]) -> List[List[float]]:
        """基于哈希的离线嵌入,无需下载模型"""
        vectors = []
        for t in texts:
            v = np.zeros(self._dim, dtype=np.float32)
            if not t:
                vectors.append(v.tolist())
                continue
            # 使用字符级哈希生成向量
            for i, char in enumerate(t):
                idx = (ord(char) + i * 31) % self._dim
                v[idx] += 1.0
            # L2 归一化
            norm = np.linalg.norm(v)
            if norm > 0:
                v = v / norm
            vectors.append(v.tolist())
        return vectors

哈希嵌入原理:通过字符编码位置生成确定性向量,相同文本总是产生相同向量,适合离线场景。

3. 文档处理流水线

PDF/TXT 上传
    ↓
textract 文本提取(支持 UTF-8/GBK 自动识别)
    ↓
chunk_text 智能分块(重叠窗口保持上下文)
    ↓
_embed_texts 向量化
    ↓
ChromaDB 持久化存储

4. RAG 对话流程

# 1. 用户提问 → 向量化查询
query_embedding = vector_store._embed_texts([query])
​
# 2. 向量检索 TOP_K 相关片段
results = chroma_collection.query(
    query_embeddings=query_embedding,
    n_results=TOP_K,
    include=["documents", "metadatas", "distances"]
)
​
# 3. 构建 Prompt 上下文
context = "\n\n".join([f"[文档 {i+1}] {doc}" for i, doc in enumerate(docs)])
prompt = f"基于以下文档回答问题:\n{context}\n\n问题:{query}"# 4. 调用 Ollama 生成回答
ollama.chat(model=MODEL, messages=[...], stream=True)

部署实战

宝塔面板部署步骤

步骤 1:创建项目目录
mkdir -p /www/wwwroot/knowledge_base
cd /www/wwwroot/knowledge_base
步骤 2:上传代码
# 项目结构
/www/wwwroot/knowledge_base/
├── src/
│   └── knowledge_base/
│       ├── __init__.py
│       ├── main.py          # FastAPI 主入口
│       ├── config.py        # 配置(使用绝对路径)
│       ├── vector_store.py  # ChromaDB 封装
│       ├── document_processor.py
│       ├── ollama_client.py
│       └── app_streamlit.py
├── templates/
│   └── index.html           # Web 前端
├── chroma_db/               # 向量数据库(自动创建)
├── uploads/                 # 上传文件存储(自动创建)
├── run.py                   # 启动脚本
└── requirements.txt
步骤 3:配置 Python 项目(宝塔)
  1. 宝塔面板 → 网站 → Python 项目 → 添加项目
  2. 项目路径:/www/wwwroot/knowledge_base
  3. 启动方式:python run.py
  4. 端口:8000
  5. 关键:启动用户必须对数据目录有写权限
步骤 4:目录权限设置
# 确保 www 用户(Gunicorn 运行用户)有权限
chown -R www:www /www/wwwroot/knowledge_base
chmod -R 755 /www/wwwroot/knowledge_base
​
# 数据目录需要写权限
chmod -R 775 /www/wwwroot/knowledge_base/chroma_db
chmod -R 775 /www/wwwroot/knowledge_base/uploads
步骤 5:Nginx 反向代理
server {
    listen 80;
    server_name kb.yourdomain.com;
    
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
步骤 6:调试端点验证

部署后访问 /api/health 验证:

{
  "status": "ok",
  "model_status": "available",
  "knowledge_base": {
    "total_documents": 3,
    "total_chunks": 45,
    "sources": ["doc1.pdf", "doc2.txt"]
  },
  "debug": {
    "chroma_db_path": "/www/wwwroot/knowledge_base/chroma_db",
    "path_exists": true
  }
}

常见问题

Q1:重启后数据丢失?

原因:使用了内存字典而非持久化存储,或路径配置错误。

解决方案

# 错误:使用内存存储
knowledge_base = {}  # 重启清空!# 正确:使用 ChromaDB 持久化
from .vector_store import vector_store
stats = vector_store.get_stats()  # 从磁盘读取

检查 config.py 使用绝对路径:

CHROMA_DB_PATH = "/www/wwwroot/knowledge_base/chroma_db"  # 正确
CHROMA_DB_PATH = "./chroma_db"  # 错误!Gunicorn 工作目录不确定

Q2:Gunicorn 日志不显示 print?

解决方案:使用文件日志或 API 调试端点

# 在 main.py 中添加健康检查端点
@app.get("/api/health")
async def health_check():
    return {
        "config": {
            "chroma_path": CHROMA_DB_PATH,
            "path_exists": os.path.exists(CHROMA_DB_PATH),
            "collection_count": vector_store.collection.count()
        }
    }

Q3:宝塔部署后 ChromaDB 权限错误?

# 修复权限
chown -R www:www /www/wwwroot/knowledge_base/chroma_db
# 或者放宽权限(测试用)
chmod -R 777 /www/wwwroot/knowledge_base/chroma_db

Q4:Ollama 连接失败?

# 检查 Ollama 监听地址
ollama serve  # 默认仅本地# 修改 systemd 服务监听所有接口
# /etc/systemd/system/ollama.service
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"

性能优化

1. 向量索引优化

# ChromaDB 自动使用 HNSW 索引
# 大数据集(>10万条)时调整参数
collection = client.get_or_create_collection(
    "knowledge_base",
    metadata={
        "hnsw:space": "cosine",
        "hnsw:construction_ef": 128,
        "hnsw:search_ef": 64
    }
)

2. 分块策略优化

文档类型CHUNK_SIZECHUNK_OVERLAP说明
技术文档800100大段落保持完整
聊天记录30050短对话密集
代码文件50050函数级分割
论文文献1000200保留引用上下文

3. 并发处理

# Gunicorn 配置(宝塔 → 项目设置 → 启动参数)
# workers = (2 × CPU核心数) + 1
# 4核服务器:9 个 worker# gunicorn.conf.py
workers = 9
worker_class = "uvicorn.workers.UvicornWorker"
keepalive = 5
timeout = 120

总结

本文介绍了基于 FastAPI + ChromaDB + Ollama 的完全离线知识库方案,关键点:

  1. 离线优先:哈希嵌入 + Ollama 本地模型,无需外网
  2. 数据持久:ChromaDB 持久化 + 绝对路径配置
  3. 部署简单:宝塔面板一键部署,支持 Gunicorn 多进程
  4. 调试友好:内置健康检查端点和 Streamlit 工具