
本文介绍如何搭建一个完全离线的企业级本地知识库系统,核心特性:
适用场景:企业内部文档管理、敏感数据处理、无公网环境部署。

┌─────────────────────────────────────────────────────────┐
│ 用户交互层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 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│ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘# Ubuntu 20.04+ / CentOS 7+
# Python 3.10+
# 内存:4GB+(根据模型大小调整)
# 磁盘:10GB+(用于存储向量数据)# 安装 Ollama
curl -fsSL https://ollama.com/install.sh | sh
# 拉取模型(以 deepseek-r1:1.5b 为例,约 1.1GB)
ollama pull deepseek-r1:1.5b
# 启动服务(默认监听 11434)
ollama serve# 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",
]关键:使用绝对路径,避免工作目录问题
# 宝塔部署必须使用绝对路径
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 个最相关片段不依赖 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哈希嵌入原理:通过字符编码位置生成确定性向量,相同文本总是产生相同向量,适合离线场景。
PDF/TXT 上传
↓
textract 文本提取(支持 UTF-8/GBK 自动识别)
↓
chunk_text 智能分块(重叠窗口保持上下文)
↓
_embed_texts 向量化
↓
ChromaDB 持久化存储# 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)mkdir -p /www/wwwroot/knowledge_base
cd /www/wwwroot/knowledge_base# 项目结构
/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/www/wwwroot/knowledge_basepython run.py8000# 确保 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/uploadsserver {
listen 80;
server_name yourdomain;
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;
}
}部署后访问 /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
}
}原因:使用了内存字典而非持久化存储,或路径配置错误。
解决方案:
# 错误:使用内存存储
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 工作目录不确定解决方案:使用文件日志或 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()
}
}# 修复权限
chown -R www:www /www/wwwroot/knowledge_base/chroma_db
# 或者放宽权限(测试用)
chmod -R 777 /www/wwwroot/knowledge_base/chroma_db# 检查 Ollama 监听地址
ollama serve # 默认仅本地
# 修改 systemd 服务监听所有接口
# /etc/systemd/system/ollama.service
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"# ChromaDB 自动使用 HNSW 索引
# 大数据集(>10万条)时调整参数
collection = client.get_or_create_collection(
"knowledge_base",
metadata={
"hnsw:space": "cosine",
"hnsw:construction_ef": 128,
"hnsw:search_ef": 64
}
)文档类型 | CHUNK_SIZE | CHUNK_OVERLAP | 说明 |
|---|---|---|---|
技术文档 | 800 | 100 | 大段落保持完整 |
聊天记录 | 300 | 50 | 短对话密集 |
代码文件 | 500 | 50 | 函数级分割 |
论文文献 | 1000 | 200 | 保留引用上下文 |
# 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 的完全离线知识库方案,关键点: