# 内网自建 Embedding 部署指南（Self-hosted Embedding Server）

> 本文件是便于交付/运维的仓库副本；**权威来源**为意图图（KG）元素
> `offline-embedding-deployment-001`。相关框架能力见组件
> `configurable-embedding-provider-001`。
>
> 适用场景：内网无法直接访问云端 embedding 服务（或不愿依赖云端），希望在本机
> 自建一个 OpenAI 兼容的 embedding 端点，供 ArchGraph 的语义检索使用。
> 内网可联网（可经代理）下载依赖与模型；**不使用容器**，纯原生运行。

---

## 1. 工作原理

ArchGraph 的语义检索分两层：**向量索引在 Neo4j 内**（原生 vector index，cosine），
**嵌入计算走 HTTP**。云端方案把嵌入指向阿里云百炼；本方案把嵌入指向本机自建服务，
索引维度与写入/查询协议保持不变。

```
defaultSemanticRetrieval
   │  resolveApprovedLiveConfiguration  (ARGO_EMBEDDING_PROFILE=openai-compatible)
   ▼
createLiveEmbeddingProviderClient
   │  POST {ARGO_EMBEDDING_BASE_URL}/embeddings
   │  body { input, model, dimensions }   ← 查询侧自动加 ARGO_EMBEDDING_QUERY_INSTRUCTION
   ▼
自建服务 (FastAPI + sentence-transformers, CPU)
   │  返回 { data:[{ embedding:[...1536] }] }
   ▼
Neo4j vector index (cosine, 1536)
```

要点：

- 协议是 **OpenAI 兼容** `POST /v1/embeddings`：请求 `{input, model, dimensions}`，
  响应 `data[].embedding`。
- **文档侧**（建索引）用策展文本原样嵌入；**查询侧**才加指令前缀。指令前缀用于
  gte-Qwen2 这类 instruction 模型，配置在 `ARGO_EMBEDDING_QUERY_INSTRUCTION`。
- 维度锁定 **1536**（`ARGO_EMBEDDING_DIMENSIONS`）；它与 Neo4j 向量索引维度一致。

---

## 2. 前置条件

| 项 | 要求 |
|---|---|
| 操作系统 | Windows（脚本为 PowerShell 5.1+）。Linux/macOS 参照「附录 A 手动步骤」 |
| Python | **3.10 – 3.12**（3.12 已验证；**3.14 无 PyTorch 轮子**，不可用） |
| Node.js | ≥ 18（仅用于 `probe`/`verify` 脚本；框架本身也需要） |
| ArchGraph 框架 | **需 `archgraph-argo` ≥ `0.24.2`**：`0.24.0` 起含 `openai-compatible` profile（提交 `44e2739`），`0.24.2` 起含语义检索向量投影修复（大图谱 OOM）；更早版本请改用本仓库或后续发布版 |
| Neo4j | 框架既有实例（支持向量索引，Neo4j ≥ 5.11） |
| 网络 | 可联网，必要时经代理；HF 直连慢/被墙时用镜像 `https://hf-mirror.com` |
| 硬件 | CPU 可跑（1.5B 模型约需 6 GB 内存）；显存非必需 |

---

## 3. 文件清单

均位于 `sandbox/local-embedding/`：

| 文件 | 作用 |
|---|---|
| `server.py` | FastAPI 服务：`POST /v1/embeddings`、`GET /health` |
| `requirements.txt` | 钉版依赖（`transformers==4.44.2` 等） |
| `setup.ps1` | **一键引导**：建 venv → 装依赖 → 装 `flash_attn` CPU stub → 下载模型 |
| `run_server.ps1` | 启动服务（离线、512 token、`127.0.0.1:8080`） |
| `stop.ps1` | 停止服务（按端口） |
| `download_model.py` | 下载模型（可续传、自动重试） |
| `probe.ps1` / `probe_http.js` | 验证 OpenAI 协议与向量维度/语义健全性 |
| `verify_framework_client.js` | 验证**框架真实 client + `openai-compatible` profile** 端到端 |
| `eval_local_vs_cloud.js` / `eval-report.json` | 召回 A/B 评测（同 golden set 对比云端） |

---

## 4. 快速开始

在仓库根目录执行（或先 `cd sandbox\local-embedding`）：

### 4.1 一键引导

```powershell
# 有代理（并走 HF 镜像）：
powershell -ExecutionPolicy Bypass -File .\setup.ps1 `
    -Proxy http://127.0.0.1:7890 -HfEndpoint https://hf-mirror.com

# 能直连 huggingface.co 时：
powershell -ExecutionPolicy Bypass -File .\setup.ps1
```

`setup.ps1` 是幂等的，可重复执行。常用参数：`-PythonExe`（指定 Python 3.10–3.12）、
`-PipIndex`（内网 pip 镜像）、`-SkipModel`（只建环境）。

> 首次会下载约 7 GB 权重到 `sandbox/local-embedding/hf/`。若下载中断，直接重跑
> `setup.ps1`（`download_model.py` 会续传）。

### 4.2 启动服务

```powershell
# 前台：
powershell -ExecutionPolicy Bypass -File .\run_server.ps1

# 或后台：
Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File','.\run_server.ps1' -WindowStyle Hidden
```

### 4.3 健康检查

```powershell
Invoke-WebRequest http://127.0.0.1:8080/health -UseBasicParsing
# => {"status":"ok","model":"Alibaba-NLP/gte-Qwen2-1.5B-instruct","dimensions":1536}
```

---

## 5. 配置 ArchGraph

编辑 `~/.argo/.env`（Windows：`%USERPROFILE%\.argo\.env`）：

```dotenv
# --- 自建 embedding profile ---
ARGO_EMBEDDING_PROFILE=openai-compatible
ARGO_EMBEDDING_BASE_URL=http://127.0.0.1:8080/v1
ARGO_EMBEDDING_MODEL=Alibaba-NLP/gte-Qwen2-1.5B-instruct
ARGO_EMBEDDING_PROVIDER=self-hosted-openai-compatible
ARGO_EMBEDDING_MODEL_VERSION=local-2026-09-19
ARGO_EMBEDDING_DIMENSIONS=1536
# 指令前缀：单行 .env 里用 \n 转义，框架会解码成真正的换行；只作用于查询侧
ARGO_EMBEDDING_QUERY_INSTRUCTION=Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:
# 自建端点通常不校验鉴权；给占位值即可
ARGO_EMBEDDING_API_KEY=local-placeholder
QWEN_KEY=local-placeholder

# --- Neo4j（沿用既有值）---
ARGO_NEO4J_DATABASE_URL=neo4j://127.0.0.1:7687
ARGO_NEO4J_DATABASE_USERNAME=neo4j
ARGO_NEO4J_DATABASE_PASSWORD=<your-password>
```

注意：

- `QWEN_KEY` 是**预检必填项**（即便本地端点不用它），放非空占位值即可。
- `.env` 仍须满足框架的 ACL 预检：文件在 git 中被忽略且未跟踪、当前用户可读、
  `Everyone`/`BUILTIN\Users` 等无读权限；只允许白名单键。
- 改 `.env` 后**必须重启宿主/MCP 进程**（运行中的 MCP 会缓存配置），再执行下一步。

---

## 6. 重建语义索引

provider/model/version 变化后，已持久化的语义记录与当前配置不匹配，需要**重新嵌入**
以刷新索引与 readiness（维度不变，无需改索引结构）。

1. 重启宿主/MCP 进程（加载新 `.env`）。
2. 运行 `argo-init`（初始化工作区 / 语义生命周期），触发全量语义回填。
3. 确认语义生命周期达到 `Aligned`。

重建完成后，检索即使用本地模型产出的向量。

---

## 7. 验证

```powershell
# 协议 + 维度 + 语义健全性 + 框架 client 端到端
powershell -ExecutionPolicy Bypass -File .\probe.ps1
```

预期：

- `/health` 返回 `dimensions: 1536`；
- 相关文本 cosine 明显高于不相关文本；
- `framework client vector length: 1536`。

随后可在 MCP 侧做一次语义检索（如 `memory_search` / `getSystemArchitecture`）确认可用。

---

## 8. 已知权衡（召回对比）

同 golden set（705 查询）、同策展嵌入文本、精确 per-channel cosine 的 A/B：

| 指标 | 云端 qwen3.7 | 本地 gte-Qwen2-1.5B | Δ |
|---|---|---|---|
| ALL recall@1 | 95.3 | **93.6** | −1.7 |
| ALL recall@5 | 99.6 | 99.1 | −0.5 |
| ALL MRR | 0.973 | 0.959 | −0.014 |
| name recall@1 | 92.9 | 89.5 | −3.4 |
| desc recall@1 | 98.9 | **99.6** | **+0.7** |
| Element recall@1 | 93.7 | 93.3 | −0.4 |
| Relationship recall@1 | 100 | 99.0 | −1.0 |
| **View recall@1** | 98.1 | **89.7** | **−8.4** |

结论：本地模型可用，但相对云端存在**真实召回退化**，主要缺口在 **View** 与 **name**；
`desc` 反而略优。已核实 View 文本都很短，故缺口是模型质量而非截断（**已知并接受**，
2026-09-19 人类裁决）。若要缩窄缺口，可选：启用 hybrid（向量+BM25，无需新模型，
利好 name）、二阶段 rerank（需本地 reranker）、或换更强模型。

---

## 9. 回滚

把 `.env` 改回云端 approved profile，重启 MCP，再跑一次 `argo-init`：

```dotenv
ARGO_EMBEDDING_PROFILE=approved
```

`approved` 为默认值，也可直接删除 `ARGO_EMBEDDING_PROFILE` 行。

---

## 10. 故障排查

| 现象 | 原因 / 处理 |
|---|---|
| `pip install torch` 找不到轮子 | Python 版本过新（3.14 无 torch 轮子）→ 用 3.10–3.12；`setup.ps1 -PythonExe <path>` |
| 报 `'DynamicCache' object has no attribute 'get_usable_length'` | gte-Qwen2 远程码与新版 transformers 不兼容 → 必须 `transformers==4.44.2`（已在 `requirements.txt` 钉死） |
| 报缺少 `flash_attn` | CUDA-only 包被静态 import → `setup.ps1` 已装 CPU stub；若手建 venv 需补 `flash_attn` stub |
| 下载 huggingface.co 超时 | 用代理 `-Proxy`，或镜像 `-HfEndpoint https://hf-mirror.com` |
| 大文件下载中断 | 重跑 `setup.ps1` 续传；不要启用 `hf_transfer`（不支持续传） |
| 首次请求很慢 | 模型加载 + 首次前向；`EMBED_MAX_SEQ` 默认 512 以控 CPU 时延 |
| `.env` 被拒（ACL / unknown key） | 修 ACL（去 Everyone/Users 读权限、保持 ignored+untracked）；只用白名单键 |
| 检索仍走云端 | 未重启 MCP，或 `ARGO_EMBEDDING_PROFILE` 未生效；重启后再跑 `argo-init` |
| 改配置后向量没更新 | 未重建索引；运行 `argo-init` 直到 `Aligned` |

---

## 11. 配置参考

| 变量 | 取值 | 说明 |
|---|---|---|
| `ARGO_EMBEDDING_PROFILE` | `approved`（默认）/ `openai-compatible` | 选 `openai-compatible` 才读取下列自建端点值 |
| `ARGO_EMBEDDING_BASE_URL` | `http://127.0.0.1:8080/v1` | OpenAI 兼容端点，无尾斜杠 |
| `ARGO_EMBEDDING_MODEL` | `Alibaba-NLP/gte-Qwen2-1.5B-instruct` | 须 1536 维 |
| `ARGO_EMBEDDING_PROVIDER` | `self-hosted-openai-compatible` | 记录在证据中的标签 |
| `ARGO_EMBEDDING_MODEL_VERSION` | `local-2026-09-19` | 版本/限定标签（仅证据） |
| `ARGO_EMBEDDING_DIMENSIONS` | `1536` | 与向量索引一致 |
| `ARGO_EMBEDDING_QUERY_INSTRUCTION` | 指令前缀（单行，`\n` 转义） | 仅查询侧；文档侧不加 |
| `ARGO_EMBEDDING_API_KEY` | 占位/可选 | 作为 Bearer；缺省回落 `QWEN_KEY` |
| `QWEN_KEY` | 占位 | 预检必填；本地端点不用 |
| `EMBED_MODEL_ID` / `EMBED_MAX_SEQ` | 服务端环境变量 | 覆盖模型 / 最大序列长度（默认 512） |

---

## 附录 A：手动步骤（等价于脚本）

```powershell
# 1) venv（用 Python 3.12）
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pip
.\.venv\Scripts\python.exe -m pip install -r requirements.txt

# 2) flash_attn CPU stub（写入 .venv\Lib\site-packages\flash_attn\）
#    __init__.py / bert_padding.py   —— 见 setup.ps1 中的内容

# 3) 下载模型（可加代理 / 镜像）
$env:HF_HOME = "$PWD\hf"
$env:HF_ENDPOINT = "https://hf-mirror.com"
.\.venv\Scripts\python.exe download_model.py

# 4) 启动
.\.venv\Scripts\python.exe -m uvicorn server:app --host 127.0.0.1 --port 8080
```

服务端环境变量：`HF_HOME`、`HF_HUB_OFFLINE=1`、`EMBED_MODEL_ID`、`EMBED_MAX_SEQ`。
