# OpenViking 本地部署复刻文档（Windows + Docker + GLM + 火山方舟 Embedding）

> 目标读者：AI Agent（或人）。按顺序执行，每阶段末尾有验证检查点。
> 本文档记录 2026-09 在 Windows 10 / Docker Desktop 上从零到端到端跑通、并接入 ZCode 的完整过程，含全部踩坑。
> 参考版本：OpenViking v0.4.17.1。

---

## 0. 方案总览与决策点


| 决策                | 选定方案                                        | 原因                                                         |
| ------------------- | ----------------------------------------------- | ------------------------------------------------------------ |
| 部署方式            | Docker Compose（官方镜像）                      | 环境隔离，自带 VikingBot + Web Studio                        |
| VLM（摘要生成）     | 智谱 GLM`glm-5.3-flash`（Coding Plan 国内端点） | 用户已有 Key，国内直连                                       |
| Embedding（向量化） | 火山方舟`doubao-embedding-vision-251215`        | 官方推荐模型，1024 维多模态，文本/图片可索引（支持以图搜图） |
| 鉴权                | `api_key` 模式（root key 管理 + user key 数据） | Docker 必须绑`0.0.0.0`，dev 模式会拒绝                       |
| 镜像拉取            | 南大镜像`ghcr.nju.edu.cn` 拉取后重打标签        | ghcr.io blob CDN 在 Docker VM 内无法解析                     |
| Agent 接入          | agent-plugins 的 stdio 代理注册为 MCP           | 配置文件零密钥，凭据运行时从 ovcli.conf 读取                 |

**架构**：容器内 openviking-server (1933) → 调智谱 GLM（摘要）+ 火山方舟 Ark（向量）两个公网 API。数据全部持久化在宿主机 `~/.openviking/`。

**需要的材料**：

- Docker Desktop（含 Compose）
- Node ≥ 18（仅 Agent 接入需要；本机路径 `D:\ProgramFiles\node.exe`）
- LLM模型：智谱 GLM Coding Plan API Key（bigmodel.cn）
- Emboding模型：火山引擎方舟 Ark API Key，且已开通 `doubao-embedding-vision-251215`（[Embedding 控制台](https://console.volcengine.com/ark/region:cn-beijing/openManagement?advancedActiveKey=model&tab=Embedding)）\本地部署开源模型，推荐：BGE-M3、Qwen3-Embedding
- 网络代理脚本或可用代理节点（克隆 GitHub 仓库时）

**耗时参考**：仓库克隆 5–15 分钟（视代理），镜像拉取约 2 GB，全流程 10-30分钟。

---

## 1. 克隆仓库

```bash
git clone --depth 1 https://gh.felicity.ac.cn/https://github.com/volcengine/OpenViking.git D:\OpenViking
# 若该代理节点失效，换 gh.llkk.cc，或用南大镜像方式（见 §3 的镜像前缀思路）：
# git clone --depth 1 https://ghproxy.net/https://github.com/volcengine/OpenViking.git D:\OpenViking
```

> **坑 1：直连 GitHub 会中断。** `git clone https://github.com/...` 在大陆网络下报
> `curl 56 schannel: server closed abruptly` / `early EOF`，留下残缺 `.git`。
> 重试前先 `rm -rf D:\OpenViking\.git`。若目录报 `Device or resource busy`（shell 占用），清空内容即可，空目录可直接往里克隆。

**验证**：`git -C D:\OpenViking log --oneline -1` 有输出，`ls` 可见 `docker-compose.yml`、`openviking/`、`docs/`。

---

## 2. 准备火山方舟 Embedding

1. 在[方舟控制台 Embedding 页](https://console.volcengine.com/ark/region:cn-beijing/openManagement?advancedActiveKey=model&tab=Embedding)开通 `doubao-embedding-vision-251215`，并在「API Key 管理」创建 API Key（是方舟的 API Key，不是火山引擎 AK/SK）。
2. 验证端点与维度：

```bash
# 注意端点是 /embeddings/multimodal（SDK 路径），不是 /embeddings/multimodal/embeddings
# 普通文本端点 /api/v3/embeddings 对 vision 模型会报 "does not support this api"，属正常
curl -s https://ark.cn-beijing.volces.com/api/v3/embeddings/multimodal \
  -H "Authorization: Bearer <你的方舟APIKey>" -H "Content-Type: application/json" \
  -d '{"model":"doubao-embedding-vision-251215","input":[{"type":"text","text":"部署验证"}]}' \
  | python -c "import json,sys;print(len(json.load(sys.stdin)['data']['embedding']))"
```

**验证**：输出 `2048`。这个维度数要写进 ov.conf 的 `dimension`，必须先确认。

> **坑 1c（2026-09 实测）：默认维度是 2048 不是 1024。** OpenViking 调 SDK 时不传 `dimensions` 参数，
> 模型默认输出 2048 维。若 ov.conf 写 1024 会与实际向量维度不符（本文档旧版写的 1024 已作废）。

> **坑 1b：模型未开通报 404/模型不存在。** 新版方舟可直接用模型名调用（无需创建 `ep-` 接入点）；
> 若报模型不存在，回控制台该页开通。若账号仍走接入点模式，`model` 字段填 `ep-xxxx` 也可，`dimension` 不变。

---

## 3. 拉取镜像

```bash
# 不要直接 docker pull ghcr.io/volcengine/openviking:latest
docker pull ghcr.nju.edu.cn/volcengine/openviking:latest
docker tag ghcr.nju.edu.cn/volcengine/openviking:latest ghcr.io/volcengine/openviking:latest
docker pull caddy:2
```

> **坑 2：ghcr.io 拉取失败。** 宿主机能解析域名，但 Docker VM 内
> `pkg-containers.githubusercontent.com`（blob CDN）报 `no such host`。这是国内典型问题。
> 解决即上：南大镜像拉取 + `docker tag` 重命名，compose 文件无需改动。
> **以后升级镜像也必须走这条路径**，直接 pull 官方地址会再次失败。
> `docker pull` 失败不会立刻可见——它发生在后台拉取 blob 时，要用 `docker images` 确认镜像真的存在。

**验证**：`docker images` 看到 `ghcr.io/volcengine/openviking:latest`（约 2.1 GB）和 `caddy:2`（约 89 MB）。

---

## 4. 写配置文件（三个）

### 4.1 服务端配置 `C:\Users\<USER>\.openviking\ov.conf`

```json
{
  "server": {
    "host": "0.0.0.0",
    "port": 1933,
    "auth_mode": "api_key",
    "root_api_key": "${OV_ROOT_API_KEY}"
  },
  "storage": {
    "workspace": "/app/.openviking/workspace",
    "agfs": { "backend": "local" },
    "vectordb": { "backend": "local" }
  },
  "embedding": {
    "dense": {
      "provider": "volcengine",
      "model": "doubao-embedding-vision-251215",
      "api_key": "${ARK_API_KEY}",
      "api_base": "https://ark.cn-beijing.volces.com/api/v3",
      "dimension": 2048,
      "input": "multimodal"
    }
  },
  "vlm": {
    "provider": "glm",
    "model": "glm-5.3-flash",
    "api_key": "${GLM_API_KEY}",
    "api_base": "https://open.bigmodel.cn/api/coding/paas/v4"
  }
}
```

> **坑 3：dev 鉴权模式与 Docker 端口映射冲突（必踩）。** 默认 `auth_mode` 为 `dev`（无鉴权），
> 服务器启动时安全检查发现 `host=0.0.0.0` 直接退出，容器进入重启循环，日志报：
> `SECURITY: server.auth_mode='dev' requires server.host to be localhost`。
> 而容器入口脚本固定绑 `0.0.0.0`，不能改成 127.0.0.1。**必须切 `api_key` 模式**。
> 注意：启动日志里出现模型/健康检查通过的信息不等于启动成功，判断标准是容器是否循环 `Restarting (1)`——是则看 `docker logs` 找 SECURITY 报错行。

> `${GLM_API_KEY}` 占位符由容器入口的 `os.path.expandvars` 展开为环境变量值——Key 不落配置文件。

> GLM 端点按 Key 类型二选一：
>
> - 智谱 Coding Plan（国内包月）：`https://open.bigmodel.cn/api/coding/paas/v4`
> - Z.AI 国际版（OpenViking 默认值）：`https://api.z.ai/api/coding/paas/v4`
>   模型 ID 用小写：`glm-5.3-flash` / `glm-4.6v`。纯文本处理用 flash 即可；要处理图片/PDF 视觉内容需带视觉的模型（`glm-4.6v` 或 `glm-5v-turbo`）。

> Embedding 用 `input: "multimodal"` 时可索引文本、图片（PNG/JPG）和混合内容，以图搜图需要该模式；
> 若只用纯文本索引，可改 `input: "text"`（图片仍会索引其 summary）。
> **换 Embedding 模型 = 全量重建向量**（维度或模型一变，旧向量全部作废），见 §9 运维一节。

### 4.2 密钥 `D:\OpenViking\.env`

```bash
# 智谱 Key（用户手填，不要经 Agent 转手）
GLM_API_KEY=<用户的智谱CodingPlan_Key>
# 火山方舟 Key（用户手填，Embedding 用）
ARK_API_KEY=<用户的火山方舟API_Key>
# 服务 root 管理密钥（随机生成即可）：
#   python -c "import secrets; print('ov-' + secrets.token_hex(24))"
OV_ROOT_API_KEY=ov-xxxxxxxx
# 数据访问用户 Key（§6 创建后回填）：
OV_USER_API_KEY=<register-user 返回的 user_key>
```

> `.env` 已在仓库 `.gitignore` 中（第 71 行），不会误提交。

### 4.3 Compose 本地覆盖 `D:\OpenViking\docker-compose.override.yml`

```yaml
services:
  openviking:
    ports: !override
      - "127.0.0.1:${OPENVIKING_SERVER_PORT:-1933}:${OPENVIKING_SERVER_PORT:-1933}"
    environment:
      GLM_API_KEY: ${GLM_API_KEY}
      ARK_API_KEY: ${ARK_API_KEY}
      OV_ROOT_API_KEY: ${OV_ROOT_API_KEY}
  caddy:
    ports: !override
      - "127.0.0.1:1934:1934"
```

> 该文件与主 compose 自动合并，上游 `docker-compose.yml` 不改。端口只绑 `127.0.0.1`，不暴露局域网。
> 模型调用全部走公网 API（智谱 + 方舟），容器无需访问宿主机服务，不需要 `extra_hosts`。
> 写完跑 `docker compose config` 验证合并结果（检查 ports/GLM_API_KEY/ARK_API_KEY 三处）。

---

## 5. 启动与自检

```bash
cd D:\OpenViking
docker compose up -d
sleep 30
curl http://localhost:1933/health        # 期望 {"status":"ok",...,"auth_mode":"api_key"}
docker ps | grep openviking              # 期望 Up (healthy)；Restarting 循环 = 配置错，看 docker logs
docker exec openviking openviking-server doctor
```

`doctor` 期望：Config/Python/AGFS/Authentication/**Embedding(volcengine/doubao-embedding-vision-251215 probe ok)**/VLM/Disk 全 PASS。唯一预期 WARN 是 VikingBot 未配 key（可选功能，忽略；不用可在 .env 加 `OPENVIKING_WITH_BOT=0`）。

> **坑 4：Embedding 的 PASS 是真实探测**（容器带 Key 实际调了一次方舟 embeddings 接口），这一项绿了说明 Key、开通状态、公网连通都通；报 401/404 回 §2 检查 Key 与模型开通。

---

## 6. 创建数据访问用户（root key 不能读写数据）

> **坑 5（必踩）：`[PERMISSION_DENIED] ROOT API keys cannot access tenant-scoped data APIs`。**
> root key 只能做管理操作（建账号/用户、`--sudo` 命令）。所有 `add-resource`/`find` 等数据操作必须用**用户 Key**。

```bash
# 首次运行会要求先选语言：docker exec openviking ov language zh-CN
# 在 default 账号下注册用户并取回 user_key（输出 JSON 里的 user_key 字段，一次性机会，及时保存）
# 前提：容器内需先有 ovcli.conf（--sudo 要求 root_api_key）。若不存在，先引导一份：
#   docker exec openviking bash -c 'cat > /app/.openviking/ovcli.conf <<EOF
#   {"url":"http://127.0.0.1:1933","api_key":"'"$OV_ROOT_API_KEY"'","root_api_key":"'"$OV_ROOT_API_KEY"'"}
#   EOF'
# 注意 ovcli.conf 是扁平结构（url/api_key/root_api_key 在顶层，不是 profiles 嵌套），且不做 ${VAR} 展开。
docker exec openviking ov admin register-user default admin --sudo -o json
# 若报 ALREADY_EXISTS，列表里挑用户或换个 user-id：
docker exec openviking ov admin list-users default --sudo -o json

# 把 user_key 写进 .env 的 OV_USER_API_KEY，然后配置容器内 CLI 身份（key 经 stdin，不进 shell 历史）：
printf '%s' "<user_key>" | docker exec -i openviking ov config add custom \
  --name local --url http://127.0.0.1:1933 --api-key-stdin --force --activate -o json
```

**验证**：`docker exec openviking ov status` 顶部"当前配置 local"，queue/vikingdb/models 组件健康。

---

## 7. 端到端验证（用本地文件，不要用 GitHub URL）

```bash
# 本机放几个 md 文件到宿主机挂载目录：
mkdir -p C:\Users\<USER>\.openviking\import   # 容器内对应 /app/.openviking/import/
cp 某几个.md C:\Users\<USER>\.openviking\import\

# Git Bash 必须加 MSYS_NO_PATHCONV=1（见坑 6）；PowerShell/CMD 不用
MSYS_NO_PATHCONV=1 docker exec openviking ov add-resource /app/.openviking/import \
  --parent-auto-create viking://resources/ov_docs --wait

# 检索验证
MSYS_NO_PATHCONV=1 docker exec openviking ov tree viking://resources/ov_docs -L 3
docker exec openviking ov find "文档的主题是什么"
docker exec openviking ov observer models   # 期望看到 glm-5.3-flash 与 doubao-embedding-vision-251215 都有 Calls
```

> **坑 6：Git Bash 路径改写。** Git Bash 会把 `/app/...` 自动改写成 `D:/ProgramFiles/Git/app/...`，
> 命令里所有容器路径前必须加 `MSYS_NO_PATHCONV=1`。命令回显路径已经变长就是中招了。

> **坑 7：`--wait` 报"请求超时"≠失败。** 入库除文件本身外还要重算父目录的 L0/L1 概览，多个 GLM 调用
> 叠加常超 CLI 等待时间。任务在服务端继续跑。**正确判断方式**：`ov status` 看"处理中"归零、
> "错误"为 0；或直接 `ov tree` 看产物。日常导入建议不加 `--wait`。

**验证**：`ov find` 返回带 `viking://resources/...` URI 的语义结果；`ov observer models` 中 GLM 和 doubao-embedding-vision-251215 都有调用计数；处理中归零且错误 0。

---

## 8. 接入 ZCode（MCP）

前提：§5 服务健康、§6 用户 Key 已配好（写进了 `~/.openviking/ovcli.conf`）。

### 8.1 注册 MCP 服务器

在 `C:\Users\<USER>\.zcode\cli\config.json` 顶层加（与既有 `hooks` 平级）：

```json
"mcp": {
  "servers": {
    "openviking": {
      "type": "stdio",
      "command": "D:\\ProgramFiles\\node.exe",
      "args": ["D:\\OpenViking\\agent-plugins\\servers\\mcp-proxy.mjs"],
      "enabled": true,
      "timeoutMs": 60000
    }
  }
}
```

> 用仓库自带 `agent-plugins/servers/mcp-proxy.mjs`（stdio→HTTP 代理），**配置文件里没有密钥**：
> 代理运行时按 `OPENVIKING_*` 环境变量 → `~/.openviking/ovcli.conf` → `~/.openviking/ov.conf` 的顺序取凭据。
> node 路径用绝对路径（沿 hooks 里已有写法），避免桌面端 PATH 问题。
> 改完 `python -c "import json;json.load(open(r'...\config.json',encoding='utf-8'))"` 验证 JSON 合法。

**启动前可手工探活**（模拟 MCP 握手，stdin 要保持打开几秒再发 tools/list）：

```bash
cd D:\OpenViking\agent-plugins
( printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
  sleep 3
  printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
  sleep 6 ) | node servers/mcp-proxy.mjs
```

期望输出两行 JSON：`serverInfo` 和 15 个工具（find/search/read/list/tree/grep/glob/remember/write/edit/add_resource/forget/health/list_watches/cancel_watch）。

### 8.2 安装记忆技能

```bash
cp -r D:\OpenViking\agent-plugins\skills\openviking-memory C:\Users\<USER>\.agents\skills\
```

重启 ZCode → Settings → MCP 应显示 openviking 已连接。新会话验证："用 openviking 搜一下……"应触发 `find` 工具调用。

> **坑 8：服务端日志的 `validation errors for ClientRequest`（input_value='server/discover'）是噪音。**
> 部分客户端发非标准 MCP 方法，SDK 拒收后打 WARNING，不影响正常工具调用，忽略。
> 判断接入是否真的在工作：日志出现 `Processing request of type CallToolRequest`。

---

## 9. 接入生命周期钩子（自动召回 + 自动捕获）

> 仓库自带官方 ZCode 记忆插件 `examples/zcode-memory-plugin`（hooks.json：SessionStart 注入用户画像、
> UserPromptSubmit 语义召回、PreToolUse 拦截 viking:// 误访问、Stop 后台捕获对话并 commit 会话）。
> 官方安装脚本 `bash examples/memory-plugin-shared/install.sh --harness zcode` 仅支持 macOS/Linux
> （开头 `uname -s` 检查直接退出），Windows 上按下述等价步骤手动完成（2026-09 实测通过）。

**① 拷贝插件到安装位**（官方安装器的目标位置，避免直接依赖仓库路径）：

```bash
OV=~/.openviking/agent-integrations
mkdir -p "$OV/zcode" "$OV/memory-plugin-shared/lib"
tar --exclude node_modules --exclude .git -C D:/OpenViking/examples/zcode-memory-plugin -cf - . | tar -C "$OV/zcode" -xf -
# 15 个共享运行时文件从 examples/memory-plugin-shared/lib/ 拷入 $OV/memory-plugin-shared/lib/（见 install.sh assemble_agent_integration）
```

**② 修 `~/.zcode/cli/config.json`**（改前备份）：

- `mcp.servers.openviking`：command 用 node 绝对路径（`C:\\Program Files\\nodejs\\node.exe`），
  args 指向 `$OV/zcode/servers/mcp-proxy.mjs`。**坑 9：手写 JSON 时反斜杠丢失**（曾出现
  `"C:Program Files\nodejs\node.exe"`，`\n` 成了换行符），MCP 静默连不上、会话里注册不了任何
  openviking 工具——判断方法：本会话工具列表里没有 find/remember 就是中招。
- `hooks`：`enabled: true`（配置文件钩子默认禁用，必设）。四个事件全部用
  `type: "process"`（command+args+timeoutMs，无 shell，Windows 最稳；官方模板的
  `OPENVIKING_*` 环境变量前缀只影响 user-agent，省掉无妨，但保留在 MCP 条目的 env 里
  可让官方升级/卸载脚本识别该条目）。时间单位：process 用 `timeoutMs`（毫秒）
  30000/20000/5000/30000；command 用 `timeout`（秒）。

**③ 验证**（全部实测通过）：

```bash
node -e "JSON.parse(require('fs').readFileSync(process.env.HOME+'/.zcode/cli/config.json','utf8'));console.log('OK')"
# uri-guard 应输出 deny JSON：
echo '{"tool_name":"Read","tool_input":{"file_path":"viking://resources/x"}}' | node ~/.openviking/agent-integrations/zcode/scripts/uri-guard.mjs
# auto-recall 应输出 additionalContext（服务端冷启动首两次可能超时空输出，重试即好）：
echo '{"session_id":"t1","cwd":"D:/OpenViking","prompt":"OpenViking 的部署方式"}' | node ~/.openviking/agent-integrations/zcode/scripts/auto-recall.mjs
```

Stop 钩子经 `~/.zcode/cli/rollout/model-io-<sessionId>.jsonl`（权威增量对话源）后台去重捕获，
每轮结束把新增 user/assistant 消息 `add` 进 `viking://~/sessions/zc-<sessionId>` 并 commit；
游标状态在 `~/.openviking/hook-state/zcode/`。实测 ZCode 会热加载 hooks 配置，无需重启；
MCP 工具列表则要到**新会话**才会出现 15 个 openviking 工具。

---

## 10. 收尾清单

- [ ]  `curl http://localhost:1933/health` → `"status":"ok"`
- [ ]  `docker ps` → openviking `Up (healthy)`，无重启循环
- [ ]  `doctor` 全 PASS（VikingBot WARN 可接受）
- [ ]  `ov status` 处理中 0、错误 0
- [ ]  `ov find` 能召回，`ov observer models` 中 VLM/Embedding 均有调用
- [ ]  Web Studio：`http://localhost:1933/studio`，用 `.env` 里 `OV_USER_API_KEY` 登录
- [ ]  ZCode MCP 已连接，15 工具，`CallToolRequest` 出现在服务端日志
- [ ]  三个密钥各就各位：GLM（.env，用户手填）、OV_ROOT_API_KEY（.env，随机生成）、OV_USER_API_KEY（.env + ovcli.conf）

**交付物与路径**：


| 项                               | 路径                                                                    |
| -------------------------------- | ----------------------------------------------------------------------- |
| 服务端配置                       | `C:\Users\<USER>\.openviking\ov.conf`                                   |
| 全部密钥                         | `D:\OpenViking\.env`                                                    |
| Compose 覆盖                     | `D:\OpenViking\docker-compose.override.yml`                             |
| 数据（持久化）                   | `C:\Users\<USER>\.openviking\workspace\`                                |
| CLI 凭据（Agent/ZCode 代理读取） | `C:\Users\<USER>\.openviking\ovcli.conf`                                |
| MCP 代理脚本                     | `D:\OpenViking\agent-plugins\servers\mcp-proxy.mjs`（**不要移动仓库**） |

**日常运维**：

- 启停：`docker compose up -d / stop`（`restart: unless-stopped` 已设，开机自启）
- 看日志：`docker logs openviking --since 10m`
- 导入本地文件：先拷到 `~/.openviking/import/`，再 `ov add-resource /app/.openviking/import/...`
- 导入网页/仓库 URL：容器内访问外网同样受 DNS 限制，先在宿主机 clone/download，再按本地文件导入
- **升级镜像**：`docker pull ghcr.nju.edu.cn/volcengine/openviking:latest && docker tag ... ghcr.io/volcengine/openviking:latest && docker compose up -d`
- 改 VLM 模型：改 `ov.conf` 的 `vlm.model`（小写 ID）→ `docker restart openviking` → `doctor` 确认 → 导入测试文档 → `ov observer models` 看新模型计数
- **改 Embedding 模型/维度（换模型、换维度都算）**：改 `ov.conf` 的 `embedding.dense` → 重启容器 → 必须重建向量：`ov reindex viking://resources --mode semantic_and_vectors`（语义产物+向量全刷；只刷向量用 `vectors_only`；清理源文件已删的孤儿向量用 `--mode prune_orphans --dry-run` 先预览）。旧模型的存量向量在新维度下不可用，不重建则检索结果错误
- 丢一个用户 Key：`ov admin regenerate-key default admin --sudo`（旧 Key 立即失效）

**已知限制**（不修，记录在案）：

1. Docker VM 的 GitHub/ghcr DNS 污染 → 一切外网资源经宿主机中转。
2. VikingBot 未配 key（WARN）→ 不影响核心功能，需要时再按官方文档配。
3. `glm-5.3-flash` 视觉能力未验证 → 图片类资源若摘要异常，切回 `glm-4.6v`。
4. Embedding 走方舟公网 API（按 token 计费）→ 离线不可用；首次全量导入是大头，日常增量可忽略。若必须离线，回退方案是 Ollama bge-m3（provider 改 `ollama`，`api_base` 指向 `http://host.docker.internal:11434/v1`，compose 补回 `extra_hosts`）。
