From 1c8876b69f7fbe91f2e2bc36b213dfd8cb854385 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 10 Aug 2025 16:22:53 +0800 Subject: [PATCH] docs: add offline embedding server & model downloader --- docs/models_downloading.py | 69 ++++++++++++++++++++++++++++++++++++ docs/offline_embed_server.py | 45 +++++++++++++++++++++++ docs/overview.md | 33 +++++++++++++++++ docs/setup_macos_m4.sh | 53 +++++++++++++++++++++++++++ server/config/server.yaml | 35 +++++++++++------- ui/dist/index.html | 2 +- 6 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 docs/models_downloading.py create mode 100644 docs/offline_embed_server.py create mode 100644 docs/setup_macos_m4.sh diff --git a/docs/models_downloading.py b/docs/models_downloading.py new file mode 100644 index 0000000..8821831 --- /dev/null +++ b/docs/models_downloading.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +离线模型下载器(Hugging Face Hub + SOCKS5 自动支持) + +默认参数: +- MODEL_ID="BAAI/bge-m3" +- MODEL_DIR="./models/bge-m3" +- PROXY="socks5://127.0.0.1:1080" + +可用环境变量覆盖: +- export MODEL_ID="你的模型ID" +- export MODEL_DIR="/保存路径" +- export PROXY="socks5h://ip:port" # 为空表示直连 +""" + +import os +import sys +from pathlib import Path + +# ==== 自动安装 SOCKS 依赖 ==== +try: + import socks # PySocks +except ImportError: + print("📦 Installing SOCKS proxy support (requests[socks])...") + os.system(f"{sys.executable} -m pip install -U 'requests[socks]'") + import socks + +# ==== 自动安装 huggingface_hub ==== +try: + from huggingface_hub import snapshot_download +except ImportError: + print("📦 Installing huggingface_hub...") + os.system(f"{sys.executable} -m pip install -U huggingface_hub") + from huggingface_hub import snapshot_download + +# ==== 默认配置 ==== +DEFAULT_MODEL_ID = "BAAI/bge-m3" +DEFAULT_MODEL_DIR = "models/bge-m3" +DEFAULT_PROXY = "socks5://127.0.0.1:1080" + +# ==== 从环境变量读取 ==== +MODEL_ID = os.environ.get("MODEL_ID", DEFAULT_MODEL_ID) +MODEL_DIR = Path(os.environ.get("MODEL_DIR", DEFAULT_MODEL_DIR)) +PROXY = os.environ.get("PROXY", DEFAULT_PROXY) + +# ==== 设置代理 ==== +if PROXY: + os.environ["HTTP_PROXY"] = PROXY + os.environ["HTTPS_PROXY"] = PROXY + print(f"🌐 Using proxy: {PROXY}") +else: + print("🚫 No proxy configured, direct connection.") + +# ==== 创建保存目录 ==== +MODEL_DIR.parent.mkdir(parents=True, exist_ok=True) + +# ==== 下载模型 ==== +print(f"⬇️ Downloading model from Hugging Face...") +print(f" Model ID: {MODEL_ID}") +print(f" Save dir: {MODEL_DIR}") + +snapshot_download( + repo_id=MODEL_ID, + local_dir=str(MODEL_DIR), + local_dir_use_symlinks=False +) + +print(f"✅ Model cached to {MODEL_DIR}") + diff --git a/docs/offline_embed_server.py b/docs/offline_embed_server.py new file mode 100644 index 0000000..711c5c3 --- /dev/null +++ b/docs/offline_embed_server.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import os, sys, numpy as np +from pathlib import Path + +# 自动装依赖 +try: + from flask import Flask, request, jsonify + from fastembed import TextEmbedding + from huggingface_hub import snapshot_download +except ImportError: + os.system(f"{sys.executable} -m pip install -U flask fastembed numpy huggingface_hub") + from flask import Flask, request, jsonify + from fastembed import TextEmbedding + from huggingface_hub import snapshot_download + +# 模型路径 +MODEL_DIR = Path(os.getenv("BGE_M3_DIR", "models/bge-m3")) + +# 如果本地无模型,先下载 +if not MODEL_DIR.exists(): + print(f"⬇️ Downloading BGE-M3 to {MODEL_DIR} ...") + snapshot_download("BAAI/bge-m3", local_dir=str(MODEL_DIR), local_dir_use_symlinks=False) + +# 离线模式 +os.environ["HF_HOME"] = str(Path.cwd() / "hf_cache") +os.environ["HF_HUB_OFFLINE"] = "1" + +# 启动服务 +app = Flask(__name__) +model = TextEmbedding(str(MODEL_DIR)) + +@app.post("/v1/embeddings") +def embeddings(): + data = request.get_json(force=True) or {} + texts = [data["input"]] if isinstance(data.get("input"), str) else data.get("input", []) + vecs = [np.asarray(v, np.float32) / (np.linalg.norm(v) + 1e-12) for v in model.embed(texts)] + return jsonify({"object": "list", "data": [ + {"object": "embedding", "index": i, "embedding": v.tolist()} for i, v in enumerate(vecs) + ], "model": data.get("model", "BAAI/bge-m3")}) + +@app.get("/healthz") +def healthz(): return "ok", 200 + +if __name__ == "__main__": + app.run(host=os.getenv("EMBED_HOST", "0.0.0.0"), port=int(os.getenv("EMBED_PORT", 9000))) diff --git a/docs/overview.md b/docs/overview.md index 4917731..8811874 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -136,3 +136,36 @@ make init-db 使用 Markdown 编写(支持标题、列表、代码块等)。 可使用 plantuml 或 mermaid 绘制架构图并嵌入 Markdown。 + +## DEV + +1. 运行(首次会自动下载模型) +python offline_embed_server.py +2. 测试接口 +编辑 +curl -s http://127.0.0.1:9000/v1/embeddings \ + -H "Content-Type: application/json" \ + -d '{"model":"BAAI/bge-m3","input":["你好","PGVector 怎么建 HNSW?"]}' | jq . +3. 环境变量(可选) +export BGE_M3_DIR="/path/to/bge-m3" +export EMBED_HOST="127.0.0.1" +export EMBED_PORT=9100 +python offline_embed_server.py + +## Ollama API test + +用流式接收(推荐): + +curl http://127.0.0.1:11434/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "gpt-oss:20b", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Tell me three tips for optimizing HNSW in PostgreSQL."} + ], + "max_tokens": 512, + "stream": true + }' +这样会实时输出分块数据 + diff --git a/docs/setup_macos_m4.sh b/docs/setup_macos_m4.sh new file mode 100644 index 0000000..0a2cc06 --- /dev/null +++ b/docs/setup_macos_m4.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "==> 1. Xcode Command Line Tools" +xcode-select -p >/dev/null 2>&1 || xcode-select --install || true + +echo "==> 2. Homebrew" +if ! command -v brew >/dev/null 2>&1; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile + eval "$(/opt/homebrew/bin/brew shellenv)" +fi + +echo "==> 3. 基础工具" +brew update +brew install git gh wget curl jq cmake pkg-config tree htop tmux + +echo "==> 4. Go / Node / Yarn" +brew install go +# Node 推荐用 corepack 管理(pnpm/yarn) +brew install node +corepack enable || true +corepack prepare yarn@stable --activate || true + +echo "==> 5. PostgreSQL + pgvector" +brew install postgresql@16 +brew services start postgresql@16 +# pgvector 扩展(Homebrew 版已包含或单独提供) +brew install pgvector || true + +echo "==> 6. Redis" +brew install redis +brew services start redis + +echo "==> 7. Python 与虚拟环境" +brew install python@3.12 +python3 -m venv ~/.venvs/xcontrol && source ~/.venvs/xcontrol/bin/activate +pip install -U pip wheel + +echo "==> 8. RAG: fastembed + Flask(做本地 /v1/embeddings)" +pip install -U fastembed flask numpy huggingface_hub + +echo "==> 9. (可选)PyTorch + MPS(Apple GPU 加速,用于 Transformers 生成)" +# 官方 pip 已支持 MPS,一般直接安装即可(若失败可按官网指引重装) +pip install -U torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cpu + +echo "==> 10. (可选)Ollama(本地生成模型)" +if ! command -v ollama >/dev/null 2>&1; then + curl -fsSL https://ollama.com/install.sh | sh +fi + +echo "==> 完成 ✅ 请重新打开终端或执行:" +echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' diff --git a/server/config/server.yaml b/server/config/server.yaml index b0cc851..2f49b0b 100644 --- a/server/config/server.yaml +++ b/server/config/server.yaml @@ -19,22 +19,33 @@ sync: repo: proxy: socks5://127.0.0.1:1080 # 仅在同步仓库时使用代理 -provider: - - name: allama - base_url: http://localhost:11434 - token: "" +# For DEV +models: + embedder: + provider: "huggingface_hub" + models: "bge-m3" + endpoint: "http://127.0.0.1:9000/v1/embeddings" + generator: + provider: "ollama" models: - 'gpt-oss:20b' - - name: chutes - base_url: https://llm.chutes.ai - token: "cpk_xxxxxxxxxxxxxxxxxxxx" - models: - - 'moonshotai/Kimi-K2-Instruct' + endpoint: "http://127.0.0.1:11434/v1/chat/completions" + token: "" +# For PROD +#models: +# embedder: + #provider: "chutes" + #models: "bge-m3" + #endpoint: "https://chutes-baai-bge-m3.chutes.ai/embed/v1/embeddings" + #token: "cpk_xxxxxxxxxxxxxxxxxxxx" +# generator: + #provider: "chutes" + #endpoint: "https://llm.chutes.ai/v1/chat/completions" + #token: "cpk_xxxxxxxxxxxxxxxxxxxx" + #models: + # - 'moonshotai/Kimi-K2-Instruct' embedding: - base_url: http://localhost:11434 - token: "" - models: bge-m3 max_batch: 64 dimension: 1024 #维度 max_chars: 8000 diff --git a/ui/dist/index.html b/ui/dist/index.html index 0fb263c..dc8addf 100644 --- a/ui/dist/index.html +++ b/ui/dist/index.html @@ -1 +1 @@ -
XControl Admin
admin@example.com
A

Dashboard

Welcome to XControl admin panel.

\ No newline at end of file +
XControl Admin
admin@example.com
A

Dashboard

Welcome to XControl admin panel.

\ No newline at end of file