从零搭建企业级本地知识库:FastAPI + ChromaDB + Ollama 完全离线方案
目录
概述
本文介绍如何搭建一个完全离线的企业级本地知识库系统,核心特性:
- 完全离线运行:无需联网,数据不出内网
- 持久化存储:重启后数据不丢失
- 向量检索:基于 ChromaDB 的语义搜索
- 大模型对话:集成 Ollama 本地 LLM
- 双端支持:Web 界面 + Streamlit 调试工具
适用场景:企业内部文档管理、敏感数据处理、无公网环境部署。
技术架构
┌─────────────────────────────────────────────────────────┐
│ 用户交互层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 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 项目(宝塔)
- 宝塔面板 → 网站 → Python 项目 → 添加项目
- 项目路径:
/www/wwwroot/knowledge_base - 启动方式:
python run.py - 端口:
8000 - 关键:启动用户必须对数据目录有写权限
步骤 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_SIZE | CHUNK_OVERLAP | 说明 |
|---|---|---|---|
| 技术文档 | 800 | 100 | 大段落保持完整 |
| 聊天记录 | 300 | 50 | 短对话密集 |
| 代码文件 | 500 | 50 | 函数级分割 |
| 论文文献 | 1000 | 200 | 保留引用上下文 |
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 的完全离线知识库方案,关键点:
- 离线优先:哈希嵌入 + Ollama 本地模型,无需外网
- 数据持久:ChromaDB 持久化 + 绝对路径配置
- 部署简单:宝塔面板一键部署,支持 Gunicorn 多进程
- 调试友好:内置健康检查端点和 Streamlit 工具