Skills に戻る
huaweicloud/huaweicloud-skillsチェック済み

SKILL DETAIL

huawei-cloud-openviking-embedding-switch

huaweicloud/huaweicloud-skills/huawei-cloud-openviking-embedding-switch

This skill switches OpenViking's embedding model to a local llama-server (or any OpenAI-compatible embedding endpoint) running inside a bwrap sandbox managed by job-env-manager. It handles the full lifecycle: detect current config, validate the target embedding endpoint, modify ov.conf, delete incompatible vectordb index when dimension changes, restart the openviking-server process in the sandbox, and verify the new collection dimension. Use this skill when the user wants to: (1) switch the OpenViking embedding model, (2) change the embedding dimension, (3) fix EmbeddingRebuildRequiredError after a dimension mismatch, (4) rebuild the vectordb index after an embedding model change, or (5) use a local llama-server for OpenViking embeddings. Trigger words include: "切换OpenViking embedding", "OpenViking embedding模型", "OpenViking向量化模型", "openviking embedding switch", "change openviking embedding model", "配置openviking embedding", "openviking llama embedding", "bge embedding openviking", "切换向量化模型", and "OpenViking模型切换".

インストール · 236出典を見る

Installation

npx skills add https://github.com/huaweicloud/huaweicloud-skills --skill huawei-cloud-openviking-embedding-switch

スキルファイル

SKILL.md

最終同期 · 2026/08/29

demo/example-input.json
{
  "example": "Example input for switching the OpenViking embedding model",
  "skill_name": "huawei-cloud-openviking-embedding-switch",
  "action": "switch_embedding",
  "model_name": "bge-small-zh-v1.5",
  "llama_port": 18200,
  "target_dimension": 512,
  "current_provider": "tokenhub",
  "current_dimension": 1024,
  "restart_method": "kill + exec (NOT stop/start)",
  "trigger_words": ["切换OpenViking embedding", "OpenViking embedding模型", "OpenViking向量化模型", "openviking embedding switch", "change openviking embedding model", "配置openviking embedding", "openviking llama embedding", "bge embedding openviking", "切换向量化模型", "OpenViking模型切换"]
}
references/acceptance-criteria.md
# Acceptance Criteria

Criteria for a successful embedding model switch.

## Pre-Switch

- [ ] OpenViking environment state is `running` (checked via job-env-manager API)
- [ ] Target llama-server responds to `/v1/embeddings`
- [ ] Dimension measured from the endpoint (not assumed from user input)

## Switch Execution

- [ ] `switch-embedding-model.sh <model_name> <llama_port> <dimension>` exits 0
- [ ] `ov.conf.bak` created before modification
- [ ] `ov.conf` `embedding.dense` matches target: provider, model, api_base, dimension
- [ ] If dimension changed: `vectordb/context` deleted before restart
- [ ] Restart used kill + exec, not stop/start
- [ ] Stale locks (`.openviking.pid`, vectordb `LOCK`) cleaned

## Post-Switch

- [ ] `GET /health` returns `healthy=true` (within 30s)
- [ ] New server PID differs from old PID
- [ ] `collection_meta.json` `Dimension` equals target
- [ ] Log shows no `Traceback`, `Application startup failed`, `EmbeddingRebuildRequiredError`, or `DataDirectoryLocked`
- [ ] A semantic search call returns results with the new model

## Rollback Behavior (if verification fails)

- [ ] `ov.conf.bak` restored
- [ ] Script exits with non-zero code
- [ ] Error reason reported clearly (health / PID / log / dimension)
references/config-reference.md
# ov.conf Embedding Configuration Reference

## Full ov.conf Structure (Relevant Sections)

```json
{
  "storage": {
    "workspace": "/workspace/data"
  },
  "embedding": {
    "dense": {
      "provider": "openai",
      "model": "bge-small-zh-v1.5",
      "api_key": "not-needed",
      "api_base": "http://127.0.0.1:18200/v1",
      "dimension": 512,
      "batch_size": 64
    },
    "max_concurrent": 3,
    "max_retries": 5
  },
  "vlm": {
    "provider": "openai",
    "model": "glm-5.2",
    "api_base": "https://tokenhub.developer.huaweicloud.com/v2",
    "temperature": 0.0,
    "max_retries": 5,
    "api_key": "..."
  },
  "server": {
    "host": "127.0.0.1",
    "port": 1933
  }
}
```

## embedding.dense Field Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `provider` | string | Yes | Embedding provider type. Use `"openai"` for any OpenAI-compatible endpoint (including llama-server). |
| `model` | string | Yes | Model name as recognized by the endpoint. For llama-server, this must match the `--model` flag or the model filename (without `.gguf`). |
| `api_key` | string | Yes | API key for authentication. For local llama-server, use any non-empty string (e.g. `"not-needed"`). |
| `api_base` | string | Yes | Base URL of the embedding API. For llama-server: `http://127.0.0.1:{port}/v1`. For TokenHub: `https://tokenhub.developer.huaweicloud.com/v2`. |
| `dimension` | integer | Yes | Vector dimension of the model. **Must match the actual model output dimension**, otherwise vector operations will fail. |
| `batch_size` | integer | No | Number of texts to embed in a single API call. Default 64. Reduce if the embedding server has memory constraints. |

## embedding Top-Level Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `max_concurrent` | integer | 3 | Maximum concurrent embedding API calls. |
| `max_retries` | integer | 5 | Maximum retries on embedding API failure. |

## server Field Reference

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `host` | string | `127.0.0.1` | Server bind address. |
| `port` | integer | 1933 | Server bind port. |

## Dimension Change Impact

When `dimension` changes:

| Component | Affected | Action Required |
|-----------|----------|----------------|
| `vectordb/context/collection_meta.json` | Yes — stores `Dimension` field | Delete (will be recreated on server start) |
| `vectordb/context/index/default/index_meta.json` | Yes — stores `VectorIndex.Dimension` | Delete (will be recreated) |
| `vectordb/context/store/` | Yes — RocksDB store with old-dimension vectors | Delete (will be recreated) |
| `vectordb/context/index/default/versions/` | Yes — index version data | Delete (will be recreated) |
| `viking/` metadata | No — user/session metadata is dimension-independent | Preserve |

**Simplest approach:** `rm -rf ${SANDBOX_DIR}/data/vectordb/context` — deletes everything under context, server recreates from scratch.

## start.sh Override Behavior

The `start.sh` script in the sandbox's `process_dir/` has logic that can overwrite `ov.conf` on environment start/restart:

| Condition | What gets overwritten |
|-----------|---------------------|
| `JOB_ENV_MODEL_API_KEY` env var is set | `embedding.dense.api_key`, `embedding.dense.api_base`, `vlm.api_key`, `vlm.api_base` |
| `AK` + `SK` env vars are set (no `JOB_ENV_MODEL_API_KEY`) | Fetches from TokenHub: `embedding.dense.api_key`, `embedding.dense.api_base`, `embedding.dense.model`, `vlm.api_key`, `vlm.api_base` |
| Neither set | **ov.conf preserved as-is** |

To check what env vars the openviking environment has:

```bash
curl -s http://127.0.0.1:8090/api/v1/env-templates/openviking | python3 -m json.tool
```
references/dataflow-diagram.md
# Data Flow Diagram

```mermaid
flowchart TD
    A[Agent: Switch Embedding Model] --> B[Step 1: Detect Current Config]
    B --> C[Step 2: Validate Target Endpoint]
    C --> D{Endpoint reachable?}
    D -- No --> D1[STOP: Start llama-server first]
    D -- Yes --> E[Step 3: Modify ov.conf]
    E --> F[Step 4: Check Dimension Change]
    F --> G{Dimension changed?}
    G -- Yes --> H[Delete vectordb/context]
    G -- No --> I[Skip index deletion]
    H --> I
    I --> J[Step 5: Restart Server]
    J --> J1[Kill old server process from host]
    J1 --> J2[Start new server via exec API]
    J2 --> K[Step 6: Verify]
    K --> K1{Health OK?}
    K1 -- No --> K2[Check troubleshooting.md]
    K1 -- Yes --> K3{Dimension correct?}
    K3 -- No --> K2
    K3 -- Yes --> K4{No errors in log?}
    K4 -- No --> K2
    K4 -- Yes --> L[✅ Switch Complete]

    style D1 fill:#f66,color:#fff
    style L fill:#6f6,color:#fff
    style H fill:#f96
    style J1 fill:#f96
```

## Embedding Request Flow (Runtime)

```mermaid
sequenceDiagram
    participant Client as Client
    participant OV as OpenViking Server<br/>(:1933)
    participant VDB as VectorDB<br/>(vectordb/context)
    participant Llama as llama-server<br/>(:18200)

    Client->>OV: Search query
    OV->>Llama: POST /v1/embeddings<br/>{model: bge-small-zh-v1.5,<br/>input: query}
    Llama-->>OV: {embedding: [0.007, 0.024, ...]}<br/>(512 dimensions)
    OV->>VDB: Vector search<br/>(512-dim query vector)
    VDB-->>OV: Matching contexts
    OV-->>Client: Search results
```

## Config Update Flow

```mermaid
sequenceDiagram
    participant Agent as Agent
    participant FS as Host Filesystem<br/>(ov.conf)
    participant JEM as job-env-manager<br/>(:8090)
    participant Sandbox as bwrap Sandbox<br/>(openviking)
    participant Server as openviking-server<br/>(:1933)

    Agent->>FS: Read ov.conf
    Agent->>FS: Write modified ov.conf<br/>(embedding.dense → llama endpoint)
    Agent->>FS: rm -rf vectordb/context<br/>(if dimension changed)
    Agent->>JEM: GET /envs/openviking<br/>(find server PID)
    Agent->>FS: kill old server PID
    Agent->>JEM: POST /envs/openviking/exec<br/>(start new server)
    JEM->>Sandbox: exec: nohup openviking-server<br/>--config /workspace/process_dir/ov.conf
    Sandbox->>Server: Process starts
    Server->>FS: Read ov.conf (via /workspace bind)
    Server->>FS: Create vectordb/context<br/>(new 512-dim collection)
    Server->>Server: Listen on :1933
    Agent->>Server: GET /health
    Server-->>Agent: {healthy: true}
```
references/guardrails.md
# Guardrails

Safety rules for the OpenViking embedding model switch skill. These rules are mandatory — violations corrupt the running OpenViking server or its data.

## 1. Sandbox Execution

- **Never run `openviking-server` directly on the host.** All operations go through the job-env-manager REST API (`http://127.0.0.1:8090`).
- All in-sandbox commands use `POST /api/v1/envs/openviking/exec`.
- If the OpenViking environment state is not `running`, do not proceed — report and suggest `POST /api/v1/envs/openviking/start`.

## 2. Restart Sequence

- **Never use `stop` + `start`.** `start.sh` overwrites `ov.conf` with TokenHub credentials on environment restart, silently reverting the embedding configuration.
- The only valid restart is: kill old process → clean stale locks (`.openviking.pid`, vectordb `LOCK`) → start via `exec` API → verify.
- Escalate to `kill -9` only after SIGTERM fails to release port 1933 within ~10s.

## 3. Validation Before Modification

- The target embedding endpoint must respond to `/v1/embeddings` **before** any config change.
- **Never trust user-supplied dimension.** Measure it from the endpoint response (`len(data[0].embedding)`) and auto-correct with a warning.
- Do not delete vectordb data unless the dimension actually changed.

## 4. Rollback

- `ov.conf` is backed up to `ov.conf.bak` before modification.
- If health check fails, PID is unchanged, or log shows startup errors, restore `ov.conf.bak` and exit with error.
- Never leave the server in a state where the old config is lost and the new config fails.

## 5. Scope Limitation

- Only the `embedding.dense` section is modified. The `vlm` section is out of scope — never touch it.
- Do not modify `start.sh`, env vars, or other job-env-manager configuration as part of this skill's workflow (documented as a note only).
references/iam-policies.md
# IAM Policies / Access Permissions

## Overview

This skill switches OpenViking's embedding model via the job-env-manager REST API. It does not access Huawei Cloud services, so no Huawei Cloud IAM policies are required. The permissions below are the equivalent access controls for this environment.

## Minimum Required Permissions

| Resource | Permission | Reason |
|----------|-----------|--------|
| job-env-manager | REST API `http://127.0.0.1:8090` | Query env state, `exec` API for in-sandbox commands |
| OpenViking sandbox | Read/write `ov.conf`, `vectordb/context` | Modify embedding config, rebuild index |
| llama-server sandbox | Access `http://127.0.0.1:${LLAMA_PORT}/v1/embeddings` | Validate target embedding endpoint, measure dimension |
| OpenViking server | Access `http://127.0.0.1:1933/health` | Health verification after restart |
| Host | Execute `curl`, `python3`, `kill` | Script prerequisites |

## Authentication

- **Dev mode (default)**: no API key needed — job-env-manager accepts anonymous access on the host loopback.
- If the environment requires an API key, it must come from environment variables — never hardcode it and never ask the user to type it in chat.

## Security Rules

- **NEVER** run `openviking-server` directly on the host — always use the `exec` API.
- **NEVER** use `stop`/`start` for restart — `start.sh` overwrites `ov.conf` with TokenHub credentials.
- **NEVER** delete vectordb data unless the dimension actually changed.
- Always back up `ov.conf` to `ov.conf.bak` before modifying; rollback on failed verification.
references/related-commands.md
# Related Commands

Common commands for the embedding model switch workflow.

## job-env-manager REST API

| Command | Purpose |
|---------|---------|
| `curl -s http://127.0.0.1:8090/api/v1/envs/openviking` | Get environment details (state, cwd) |
| `curl -s -X POST http://127.0.0.1:8090/api/v1/envs/openviking/start` | Start the OpenViking environment |
| `curl -s -X POST http://127.0.0.1:8090/api/v1/envs/openviking/stop` | **Forbidden for restart** — re-runs start.sh, overwrites ov.conf |
| `curl -s --max-time 15 -X POST http://127.0.0.1:8090/api/v1/envs/openviking/exec -H 'Content-Type: application/json' -d '{"cmd":[...]}'` | Execute a command inside the sandbox |

## Embedding Endpoint

| Command | Purpose |
|---------|---------|
| `curl -s http://127.0.0.1:${PORT}/v1/embeddings -H "Content-Type: application/json" -d '{"model":"${MODEL}","input":"test"}'` | Validate embedding endpoint and measure dimension |

## Server Health and Process

| Command | Purpose |
|---------|---------|
| `curl -s http://127.0.0.1:1933/health` | Check server health |
| `ss -tlnp \| grep 1933` | Check if port 1933 is in use (and which PID) |
| `kill <PID>` / `kill -9 <PID>` | Stop the old server process (SIGTERM then escalate) |

## Data Inspection

| Command | Purpose |
|---------|---------|
| `cat ${SANDBOX_DIR}/process_dir/ov.conf` (or equivalent path) | Inspect current embedding config |
| `python3 -c "import json; d=json.load(open('.../collection_meta.json')); print(d['Dimension'])"` | Check collection dimension |
| `grep -ci "Traceback\|Application startup failed\|EmbeddingRebuildRequiredError\|DataDirectoryLocked" <log>` | Check startup errors (expected: 0) |

## Execution Examples

```bash
# Full switch (model, llama port, dimension)
bash scripts/switch-embedding-model.sh bge-small-zh-v1.5 18200 512

# Execute commands inside the sandbox
curl -s --max-time 15 -X POST http://127.0.0.1:8090/api/v1/envs/openviking/exec \
  -H 'Content-Type: application/json' \
  -d '{"cmd":["bash","-c","ls /workspace/process_dir/ov.conf"]}'
```
references/troubleshooting.md
# Troubleshooting

## Problem 1: EmbeddingRebuildRequiredError on Server Startup

**Symptom:**
```
openviking.storage.errors.EmbeddingRebuildRequiredError: Existing collection embedding dimension (1024) does not match current configuration (512).
```

**Cause:** `collection_meta.json` still records the old dimension. vectordb index not fully deleted before restart.

**Fix:**
```bash
SANDBOX_DIR=$(curl -s http://127.0.0.1:8090/api/v1/envs/openviking \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['cwd'])")
rm -rf "${SANDBOX_DIR}/data/vectordb/context"
# Then restart the server (Step 5 in SKILL.md)
```

---

## Problem 2: ov.conf Overwritten After Environment Restart

**Symptom:** After `stop` + `start`, `ov.conf` reverts to TokenHub defaults.

**Cause:** `start.sh` overwrites `ov.conf` using `JOB_ENV_MODEL_API_KEY` or `AK`/`SK` env vars.

**Fix:** Do NOT use stop/start. Instead: modify `ov.conf` → kill server from host → start via `exec` API. If stop/start was already used, re-apply Step 3 then Step 5.

---

## Problem 3: exec API "No such process" When Trying to Kill Server

**Symptom:** `kill` via exec API fails with "No such process".

**Cause:** exec API runs in a different PID namespace. Host PID doesn't exist there.

**Fix:** Kill from the host directly:
```bash
SERVER_PID=$(ps aux | grep vsbin-openviking-server | grep -v grep | awk '{print $2}' | head -1)
kill "$SERVER_PID"
```

---

## Problem 4: Server Fails to Bind Port 1933 (Port Conflict)

**Symptom:**
```
uvicorn.error - ERROR - [Errno 98] error while attempting to bind on address ('127.0.0.1', 1933): address already in use
```

**Cause:** Previous server process not fully terminated. SIGTERM + `sleep 3` is insufficient — the port may not be released yet.

**Fix:** The script now polls for port release after kill:
1. `kill $PID` (SIGTERM)
2. Poll `ss -tlnp | grep 1933` for up to 10 seconds
3. If still in use: `kill -9 $PID` (SIGKILL) + wait
4. Verify port is free before starting new server

**Manual fix:**
```bash
pkill -9 -f vsbin-openviking-server
sleep 3
ss -tlnp | grep 1933  # should be empty
```

---

## Problem 5: DataDirectoryLocked on Startup

**Symptom:**
```
openviking.utils.process_lock.DataDirectoryLocked: Another OpenViking process (PID 4) is already using the data directory
```

**Cause:** Stale lock files (`.openviking.pid`, `LOCK`) remain after killing the server.

**Fix:** The script now cleans up lock files before starting the new server:
```bash
SANDBOX_DIR=$(curl -s http://127.0.0.1:8090/api/v1/envs/openviking \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['cwd'])")
rm -f "${SANDBOX_DIR}/data/.openviking.pid"
find "${SANDBOX_DIR}/data/vectordb" -name "LOCK" -delete
```

---

## Problem 6: Health Check Passes But Config Not Applied (False Positive)

**Symptom:** Script reports success, but server is still using old config.

**Cause:** Old server wasn't killed, new server failed to bind port. Health check hit the old server.

**Fix:** The script now verifies the server PID changed after restart:
```bash
OLD_PID=$(ps aux | grep vsbin-openviking-server | grep -v grep | awk '{print $2}' | head -1)
# ... restart ...
NEW_PID=$(ps aux | grep vsbin-openviking-server | grep -v grep | awk '{print $2}' | head -1)
if [ "$NEW_PID" = "$OLD_PID" ]; then
  echo "ERROR: PID unchanged — old server still running"
  # rollback
fi
```

---

## Problem 7: llama-server Embedding Endpoint Returns Error

**Symptom:** `curl` to embedding endpoint returns error or empty response.

**Fix:**
```bash
# Check llama env state
curl -s http://127.0.0.1:8090/api/v1/envs/llama | python3 -c "import sys,json; print(json.load(sys.stdin)['state'])"
# Start if needed
curl -s -X POST http://127.0.0.1:8090/api/v1/envs/llama/start
# Verify --embeddings flag
ps aux | grep llama-server | grep -v grep
```

---

## Problem 8: nsenter Fails with "Operation not permitted"

**Fix:** Do not use `nsenter`. Use the job-env-manager `exec` API instead.

---

## Problem 9: Server Log Shows Constant Retrying to /embeddings

**Symptom:** `INFO Retrying request to /embeddings in 0.48 seconds`

**Cause:** Configured `api_base` endpoint is unreachable (typically TokenHub).

**Fix:** This is the original problem that switching to local llama-server solves. Note: these "Retrying" messages are INFO level and do NOT match the script's error detection pattern, so they won't cause false positives.

---

## Problem 10: exec API Call Hangs Indefinitely

**Symptom:** `curl` to exec API blocks forever when starting server with `nohup`.

**Cause:** The exec session waits for all child processes to exit. `nohup ... &` backgrounds the server, but the exec session may still wait.

**Fix:** Always use `--max-time` on the curl call:
```bash
curl -s --max-time 15 -X POST .../exec \
  -d '{"cmd":["bash","-c","nohup ... & sleep 2 && echo started"]}'
```
The `sleep 2 && echo started` ensures the command returns after giving the server time to start.
references/verification-method.md
# Verification Method

Step-by-step verification for each workflow of the embedding model switch skill.

## Prerequisite Checks

| Check | Method |
|-------|--------|
| job-env-manager reachable | `curl -s http://127.0.0.1:8090/api/v1/envs/openviking` returns JSON |
| OpenViking env running | Response `state` equals `running` |
| llama-server reachable | `curl -s http://127.0.0.1:${LLAMA_PORT}/v1/embeddings -d '{"model":"${MODEL_NAME}","input":"test"}'` returns an embedding |
| Host tooling | `curl --version` and `python3 --version` succeed |

## Task 1: Detect Current Configuration

| Check | Method |
|-------|--------|
| Sandbox dir obtained | `cwd` from env response is non-empty |
| Current embedding read | `ov.conf` contains `embedding.dense` with provider/model/dimension fields |

## Task 2: Validate Target Endpoint

| Check | Method |
|-------|--------|
| Endpoint reachable | `/v1/embeddings` returns HTTP 200 |
| Dimension measured | `len(d['data'][0]['embedding'])` returns a positive integer |
| Dimension correction | Warning printed if user-supplied `TARGET_DIMENSION` differs from measured value |

## Task 3: Modify ov.conf

| Check | Method |
|-------|--------|
| Backup created | `ov.conf.bak` exists before modification |
| Fields updated | `provider`, `model`, `api_base`, `dimension` match the target in `embedding.dense` |

## Task 4: Delete Incompatible vectordb Index

| Check | Method |
|-------|--------|
| Conditional deletion | `vectordb/context` removed **only** when dimension changed |
| Unchanged dimension | `vectordb/context` still exists and Task 4 was skipped |

## Task 5: Restart Server

| Check | Method |
|-------|--------|
| Kill method | Old PID killed with SIGTERM (escalate to SIGKILL if port stays busy) |
| Port released | Port 1933 free within ~10s of kill |
| Stale locks cleaned | `.openviking.pid` and vectordb `LOCK` files removed |
| New process started | `exec` API returns success and a new PID appears for port 1933 |

## Task 6: Verify

| Check | Method |
|-------|--------|
| Health OK | `GET /health` returns `healthy=true` within 30s |
| PID changed | New PID differs from old PID (no port-conflict false positive) |
| Dimension OK | `collection_meta.json` `Dimension` equals measured target dimension |
| Log clean | Grep for `Traceback\|Application startup failed\|EmbeddingRebuildRequiredError\|DataDirectoryLocked` returns 0 |
| Rollback executed | On any failure, `ov.conf.bak` restored and process exits non-zero |

## End-to-End Acceptance

1. `switch-embedding-model.sh <model> <port> <dimension>` exits 0
2. `curl http://127.0.0.1:1933/health` shows `healthy=true`
3. `collection_meta.json` Dimension matches the target
4. Embedding search works with the new model (a `search` MCP call returns results)
5. Server restarts (kill + exec) still use the new model without stop/start
scripts/switch-embedding-model.sh
#!/usr/bin/env bash
set -euo pipefail

# ────────────────────────────────────────────────────────────
# switch-embedding-model.sh
# Switch OpenViking's embedding model to a local llama-server.
# Usage: bash switch-embedding-model.sh <model_name> <llama_port> <dimension>
# Example: bash switch-embedding-model.sh bge-small-zh-v1.5 18200 512
# ────────────────────────────────────────────────────────────

if [ $# -ne 3 ]; then
  echo "Usage: $0 <model_name> <llama_port> <dimension>"
  echo "Example: $0 bge-small-zh-v1.5 18200 512"
  exit 1
fi

MODEL_NAME="$1"
LLAMA_PORT="$2"
TARGET_DIMENSION="$3"
JEM_BASE="http://127.0.0.1:8090/api/v1"
SERVER_PORT=1933
HEALTH_TIMEOUT=30  # seconds to wait for server health

echo "=== OpenViking Embedding Model Switch ==="
echo "  Model:    $MODEL_NAME"
echo "  Port:     $LLAMA_PORT"
echo "  Dimension: $TARGET_DIMENSION"
echo ""

# ── Step 1: Detect sandbox directory ──
echo "[1/6] Detecting OpenViking sandbox..."
ENVS_RESP=$(curl -s "${JEM_BASE}/envs/openviking")
OV_STATE=$(echo "$ENVS_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['state'])")
SANDBOX_DIR=$(echo "$ENVS_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['cwd'])")

if [ "$OV_STATE" != "running" ]; then
  echo "ERROR: OpenViking environment state is '$OV_STATE', expected 'running'."
  echo "Start it first: curl -X POST ${JEM_BASE}/envs/openviking/start"
  exit 1
fi
echo "  Sandbox: $SANDBOX_DIR (state=$OV_STATE)"

# Show current config
echo "  Current embedding config:"
python3 -c "
import json
with open('${SANDBOX_DIR}/process_dir/ov.conf') as f:
    d = json.load(f)
e = d['embedding']['dense']
print(f\"    model={e['model']}, api_base={e['api_base']}, dim={e['dimension']}\")
"

# ── Step 2: Validate target endpoint ──
echo ""
echo "[2/6] Validating embedding endpoint at 127.0.0.1:${LLAMA_PORT}..."
EMBED_RESP=$(curl -s --max-time 10 "http://127.0.0.1:${LLAMA_PORT}/v1/embeddings" \
  -H "Content-Type: application/json" \
  -d "{\"model\":\"${MODEL_NAME}\",\"input\":\"test\"}" 2>&1) || true

ACTUAL_DIM=$(echo "$EMBED_RESP" | python3 -c "
import sys,json
try:
    d=json.load(sys.stdin)
    print(len(d['data'][0]['embedding']))
except:
    print('error')
" 2>/dev/null) || ACTUAL_DIM="error"

if [ "$ACTUAL_DIM" = "error" ]; then
  echo "ERROR: Cannot reach embedding endpoint or invalid response."
  echo "  Response: $EMBED_RESP"
  exit 1
fi
echo "  Endpoint OK, actual dimension: $ACTUAL_DIM"

if [ "$ACTUAL_DIM" != "$TARGET_DIMENSION" ]; then
  echo "WARNING: Specified dimension ($TARGET_DIMENSION) != actual ($ACTUAL_DIM)"
  echo "  Using actual dimension: $ACTUAL_DIM"
  TARGET_DIMENSION="$ACTUAL_DIM"
fi

# ── Step 3: Modify ov.conf ──
echo ""
echo "[3/6] Modifying ov.conf..."

# BUG-5 fix: backup original config for rollback
cp "${SANDBOX_DIR}/process_dir/ov.conf" "${SANDBOX_DIR}/process_dir/ov.conf.bak"

python3 -c "
import json
conf_path = '${SANDBOX_DIR}/process_dir/ov.conf'
with open(conf_path, encoding='utf-8') as f:
    data = json.load(f)
dense = data['embedding']['dense']
dense['provider'] = 'openai'
dense['model'] = '${MODEL_NAME}'
dense['api_key'] = 'not-needed'
dense['api_base'] = 'http://127.0.0.1:${LLAMA_PORT}/v1'
dense['dimension'] = ${TARGET_DIMENSION}
with open(conf_path, 'w', encoding='utf-8') as f:
    json.dump(data, f, indent=2, ensure_ascii=False)
print('  ov.conf updated (backup at ov.conf.bak)')
"

# ── Step 4: Delete incompatible vectordb index ──
echo ""
echo "[4/6] Checking vectordb index compatibility..."
COLLECTION_META="${SANDBOX_DIR}/data/vectordb/context/collection_meta.json"
if [ -f "$COLLECTION_META" ]; then
  CURRENT_DIM=$(python3 -c "import json; print(json.load(open('$COLLECTION_META'))['Dimension'])")
  if [ "$CURRENT_DIM" != "$TARGET_DIMENSION" ]; then
    echo "  Dimension mismatch: $CURRENT_DIM → $TARGET_DIMENSION"
    echo "  Deleting vectordb/context..."
    rm -rf "${SANDBOX_DIR}/data/vectordb/context"
    echo "  Deleted."
  else
    echo "  Dimensions match ($CURRENT_DIM), no deletion needed."
  fi
else
  echo "  No existing collection_meta.json, skipping."
fi

# ── Step 5: Restart openviking-server ──
echo ""
echo "[5/6] Restarting openviking-server..."

# 5a. Kill old process and wait for port release (BUG-1 fix)
OLD_PID=$(ps aux | grep vsbin-openviking-server | grep -v grep | awk '{print $2}' | head -1 || true)
if [ -n "$OLD_PID" ]; then
  echo "  Killing old server (PID $OLD_PID)..."
  kill "$OLD_PID" 2>/dev/null || true

  # Poll for port release (up to 10 seconds)
  for i in $(seq 1 10); do
    if ! ss -tlnp 2>/dev/null | grep -q ":${SERVER_PORT} "; then
      echo "  Port $SERVER_PORT released after ${i}s"
      break
    fi
    [ "$i" = "10" ] && {
      echo "  SIGTERM didn't release port, using SIGKILL..."
      kill -9 "$OLD_PID" 2>/dev/null || true
      sleep 2
    }
    sleep 1
  done
fi

# BUG-3 fix: clean up stale lock files
rm -f "${SANDBOX_DIR}/data/.openviking.pid" 2>/dev/null
find "${SANDBOX_DIR}/data/vectordb" -name "LOCK" -delete 2>/dev/null
echo "  Cleaned up lock files"

# 5b. Start new server via exec API
echo "  Starting new server via exec API..."
EXEC_RESP=$(curl -s --max-time 15 -X POST "${JEM_BASE}/envs/openviking/exec" \
  -H 'Content-Type: application/json' \
  -d '{"cmd":["bash","-c","nohup /root/runtime/openviking/venv/bin/openviking-server --config /workspace/process_dir/ov.conf > /workspace/process_dir/openviking-server.log 2>&1 & sleep 2 && echo started"]}' 2>&1) || true

# BUG-5 fix: check exec API response
if echo "$EXEC_RESP" | grep -qi "error\|fail\|not found" 2>/dev/null; then
  echo "  WARNING: exec API response unexpected: $EXEC_RESP"
fi

# ── Step 6: Verify ──
echo ""
echo "[6/6] Verifying..."

# BUG-2 fix: poll health endpoint with retry loop instead of fixed sleep
echo "  Waiting for server to become healthy (timeout ${HEALTH_TIMEOUT}s)..."
HEALTHY="False"
for i in $(seq 1 "$HEALTH_TIMEOUT"); do
  HEALTH=$(curl -s --max-time 3 "http://127.0.0.1:${SERVER_PORT}/health" 2>/dev/null || echo "")
  if [ -n "$HEALTH" ]; then
    HEALTHY=$(echo "$HEALTH" | python3 -c "import sys,json; print(json.load(sys.stdin).get('healthy',False))" 2>/dev/null || echo "False")
    if [ "$HEALTHY" = "True" ]; then
      echo "  ✅ Server healthy after ${i}s"
      break
    fi
  fi
  sleep 1
done

if [ "$HEALTHY" != "True" ]; then
  echo "  ❌ Server not healthy after ${HEALTH_TIMEOUT}s"
  echo "  Rolling back config..."
  cp "${SANDBOX_DIR}/process_dir/ov.conf.bak" "${SANDBOX_DIR}/process_dir/ov.conf"
  echo "  Check log: ${SANDBOX_DIR}/process_dir/openviking-server.log"
  exit 1
fi

# 6b. Verify the running server PID is NEW (BUG-1 fix: detect false positive)
NEW_PID=$(ps aux | grep vsbin-openviking-server | grep -v grep | awk '{print $2}' | head -1 || true)
if [ -n "$OLD_PID" ] && [ "$NEW_PID" = "$OLD_PID" ]; then
  echo "  ❌ Server PID unchanged ($OLD_PID) — old server still running, config not applied!"
  echo "  Rolling back config..."
  cp "${SANDBOX_DIR}/process_dir/ov.conf.bak" "${SANDBOX_DIR}/process_dir/ov.conf"
  exit 1
fi
echo "  ✅ New server PID: $NEW_PID (was: ${OLD_PID:-none})"

# 6c. Collection dimension
if [ -f "$COLLECTION_META" ]; then
  NEW_DIM=$(python3 -c "import json; print(json.load(open('$COLLECTION_META'))['Dimension'])" 2>/dev/null || echo "unknown")
  if [ "$NEW_DIM" = "$TARGET_DIMENSION" ]; then
    echo "  ✅ Collection dimension: $NEW_DIM"
  else
    echo "  ❌ Collection dimension: $NEW_DIM (expected $TARGET_DIMENSION)"
    exit 1
  fi
else
  echo "  ⚠️  collection_meta.json not found (may still be initializing)"
fi

# 6d. Log errors — precise pattern to avoid false positives (BUG-4 fix)
# Only match actual Python errors, not "Retrying" info messages
ERROR_COUNT=$(grep -ci "Traceback\|ERROR.*Application startup failed\|EmbeddingRebuildRequiredError\|DataDirectoryLocked" "${SANDBOX_DIR}/process_dir/openviking-server.log" 2>/dev/null || true)
if [ "$ERROR_COUNT" = "0" ] || [ -z "$ERROR_COUNT" ]; then
  echo "  ✅ No errors in log"
else
  echo "  ⚠️  $ERROR_COUNT error-related lines in log:"
  grep -i "Traceback\|ERROR.*Application startup failed\|EmbeddingRebuildRequiredError\|DataDirectoryLocked" "${SANDBOX_DIR}/process_dir/openviking-server.log" 2>/dev/null | head -5
fi

# Cleanup backup
rm -f "${SANDBOX_DIR}/process_dir/ov.conf.bak"

echo ""
echo "=== Switch Complete ==="
echo "  OpenViking is now using: $MODEL_NAME ($TARGET_DIMENSION-dim) at 127.0.0.1:${LLAMA_PORT}"
SKILL.md
---
name: huawei-cloud-openviking-embedding-switch
description: |
  Switch OpenViking's embedding model to a local llama-server (or any OpenAI-compatible embedding endpoint) running inside a bwrap sandbox managed by job-env-manager. Handles the full lifecycle: detect current config, validate the target embedding endpoint, modify ov.conf, delete incompatible vectordb index when dimension changes, restart the openviking-server process in the sandbox, and verify the new collection dimension.
  Use this skill when the user wants to: (1) switch the OpenViking embedding model, (2) change the embedding dimension, (3) fix EmbeddingRebuildRequiredError after a dimension mismatch, (4) rebuild the vectordb index after an embedding model change, (5) use a local llama-server for OpenViking embeddings.
  Trigger words: "切换OpenViking embedding", "OpenViking embedding模型", "OpenViking向量化模型", "openviking embedding switch", "change openviking embedding model", "配置openviking embedding", "openviking llama embedding", "bge embedding openviking", "切换向量化模型", "OpenViking模型切换".
tags:
  - openviking
  - embedding
  - llama
  - vectordb
  - job-env-manager
---

# OpenViking Embedding Model Switch

## 概述

Switch the embedding model used by OpenViking to a local llama-server or any OpenAI-compatible endpoint, with proper vectordb index rebuild and sandbox-safe restart.

> **⚠️ Single-purpose skill** — all operations go through the job-env-manager REST API (`http://127.0.0.1:8090`). Never run `openviking-server` directly on the host.

OpenViking is an AI context database that uses vector embeddings for semantic search. Its embedding model is configured in `ov.conf` under the `embedding.dense` section. When switching to a different embedding model (especially one with a different vector dimension), the existing vectordb index must be deleted and rebuilt — otherwise OpenViking raises `EmbeddingRebuildRequiredError` on startup.

## Architecture

```
OpenViking Embedding Model Switch
├── Detect current config     (Read ov.conf embedding.dense section)
├── Validate endpoint         (Check llama-server /v1/embeddings)
├── Modify ov.conf            (Update provider, model, api_base, dimension)
├── Delete vectordb index     (If dimension changed: rm -rf vectordb/context)
├── Restart server            (Kill + exec, NOT stop/start)
└── Verify                    (Health + PID + dimension + log check)
```

```
┌─────────────────────────────────────────────────────┐
│                    Host                              │
│                                                      │
│  ┌─────────────┐    REST API   ┌──────────────────┐ │
│  │  Agent       │─────────────▶│  job-env-manager  │ │
│  │  (this skill)│              │  :8090            │ │
│  └─────────────┘              └────────┬─────────┘ │
│                                        │            │
│         ┌──────────────────────────────┼──────┐    │
│         │  bwrap sandbox (openviking)   │      │    │
│         │                               ▼      │    │
│         │  ┌────────────────────────────────┐  │    │
│         │  │  openviking-server :1933       │  │    │
│         │  │  ├── ov.conf (embedding config)│  │    │
│         │  │  ├── vectordb/context/         │  │    │
│         │  │  └── viking/ (metadata)        │  │    │
│         │  └────────────────────────────────┘  │    │
│         └──────────────────────────────────────┘    │
│                                                      │
│         ┌──────────────────────────────────────┐    │
│         │  bwrap sandbox (llama)                │    │
│         │  ┌────────────────────────────────┐  │    │
│         │  │  llama-server :18200           │  │    │
│         │  │  --embeddings --model bge-...  │  │    │
│         │  └────────────────────────────────┘  │    │
│         └──────────────────────────────────────┘    │
│                                                      │
│  Both sandboxes use --share-net, so 127.0.0.1        │
│  endpoints are mutually reachable.                   │
└─────────────────────────────────────────────────────┘
```

## Prerequisites

> **Prerequisite check: job-env-manager running**
> ```bash
> curl -s http://127.0.0.1:8090/api/v1/envs/openviking | python3 -c "import sys,json; print(json.load(sys.stdin)['state'])"
> ```

- **job-env-manager** running on `http://127.0.0.1:8090`
- **OpenViking environment** deployed and running (state = `running`)
- **llama-server** running at `127.0.0.1:{port}` with `--embeddings` flag
- **curl** and **python3** available on the host
- No AK/SK or Huawei Cloud credentials required

## IAM Permission Policies

This skill operates on local bwrap sandboxes via the job-env-manager REST API and does not access Huawei Cloud services — no Huawei Cloud IAM policies required. Equivalent access controls are listed in [references/iam-policies.md](references/iam-policies.md).

## 核心命令 (Core Workflow)

### Task 1: Detect Current Configuration

```bash
SANDBOX_DIR=$(curl -s http://127.0.0.1:8090/api/v1/envs/openviking \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['cwd'])")
```

Read `ov.conf` under the sandbox directory to get the current `embedding.dense` section (provider, model, dimension).

### Task 2: Validate Target Embedding Endpoint

```bash
curl -s http://127.0.0.1:${LLAMA_PORT}/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{"model":"${MODEL_NAME}","input":"test"}' \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['data'][0]['embedding']))"
```

If unreachable, **STOP**. The script auto-corrects the dimension if the specified value doesn't match the actual endpoint output.

### Task 3: Modify ov.conf

Backs up `ov.conf` to `ov.conf.bak` before modifying. Updates the `embedding.dense` section:

| Field | Description |
|-------|-------------|
| `provider` | Embedding provider name |
| `model` | Model name (e.g., `bge-small-zh-v1.5`) |
| `api_key` | API key for the endpoint (empty for local) |
| `api_base` | Endpoint URL (e.g., `http://127.0.0.1:18200/v1`) |
| `dimension` | Vector dimension (auto-corrected from endpoint) |

### Task 4: Delete Incompatible vectordb Index

> **⚠️ Critical:** If dimensions differ, `rm -rf vectordb/context` is required. Otherwise `EmbeddingRebuildRequiredError` on startup.

If dimension is unchanged, skip this step.

### Task 5: Restart openviking-server Inside the Sandbox

> **⚠️ Pitfall:** `POST /envs/openviking/stop` + `start` re-runs `start.sh`, which overwrites `ov.conf` with TokenHub credentials. **Do not use stop/start.**

Instead:

1. **Kill old process** from host: `kill $PID`, then poll for port 1933 release (up to 10s). If SIGTERM doesn't release the port, escalate to `kill -9`.
2. **Clean up stale lock files**: `.openviking.pid` and vectordb `LOCK` files.
3. **Start new server** via `exec` API with `--max-time 15`:

```bash
curl -s --max-time 15 -X POST http://127.0.0.1:8090/api/v1/envs/openviking/exec \
  -H 'Content-Type: application/json' \
  -d '{"cmd":["bash","-c","nohup /root/runtime/openviking/venv/bin/openviking-server --config /workspace/process_dir/ov.conf > /workspace/process_dir/openviking-server.log 2>&1 & sleep 2 && echo started"]}'
```

### Task 6: Verify

1. **Health check with retry loop** (up to 30s): polls `GET /health` every second until `healthy=true` or timeout
2. **PID change check**: verifies the new server PID differs from the old one (detects port conflict false positives)
3. **Collection dimension check**: reads `collection_meta.json` and confirms `Dimension` matches target
4. **Log error check**: precise grep for `Traceback|ERROR.*Application startup failed|EmbeddingRebuildRequiredError|DataDirectoryLocked` (avoids false positives from "Retrying" info messages)
5. **Rollback on failure**: if health check fails or PID unchanged, restores `ov.conf.bak` and exits with error

## Parameter Confirmation

| Parameter | Required | Description | Example |
|-----------|----------|-------------|---------|
| `MODEL_NAME` | Yes | Embedding model name | `bge-small-zh-v1.5` |
| `LLAMA_PORT` | Yes | llama-server port | `18200` |
| `TARGET_DIMENSION` | Yes | Vector dimension (auto-corrected if wrong) | `512` |

```bash
# Usage
bash scripts/switch-embedding-model.sh <model_name> <llama_port> <dimension>
```

## Common Embedding Model Dimensions

| Model | Dimension | Typical Use |
|-------|-----------|-------------|
| `bge-small-zh-v1.5` | 512 | Lightweight Chinese embedding |
| `bge-large-zh-v1.5` | 1024 | High-quality Chinese embedding |
| `bge-small-en-v1.5` | 384 | Lightweight English embedding |
| `bge-base-en-v1.5` | 768 | General-purpose English embedding |
| `Qwen3-Embedding-0.6B` | 1024 | Qwen3 embedding (TokenHub default) |

## Verification

See [references/verification-method.md](references/verification-method.md) for step-by-step checks and end-to-end acceptance criteria.

**Quick verification:**
```bash
# 1. Server healthy
curl -s http://127.0.0.1:1933/health \
  | python3 -c "import sys,json; assert json.load(sys.stdin)['healthy']; print('OK')"

# 2. Collection dimension matches target
python3 -c "import json; d=json.load(open('${SANDBOX_DIR}/data/vectordb/context/collection_meta.json')); assert d['Dimension']==${TARGET_DIMENSION}; print('OK')"

# 3. No errors in log (precise pattern)
grep -ci "Traceback\|Application startup failed\|EmbeddingRebuildRequiredError\|DataDirectoryLocked" \
  "${SANDBOX_DIR}/process_dir/openviking-server.log"
# Expected: 0
```

## Guardrails

See [references/guardrails.md](references/guardrails.md) for the full rules. Key principles:

- **Always run through job-env-manager** — never execute `openviking-server` directly on the host
- **Never use stop/start restart** — `start.sh` overwrites `ov.conf` with TokenHub credentials
- **Validate before modify** — the target endpoint must respond before any config change
- **Rollback on failure** — `ov.conf.bak` is restored if verification fails

## References

| Document | Description |
|----------|-------------|
| [config-reference.md](references/config-reference.md) | ov.conf embedding section field reference |
| [guardrails.md](references/guardrails.md) | Safety rules: sandbox execution, restart sequence, rollback |
| [iam-policies.md](references/iam-policies.md) | Equivalent access controls (no Huawei Cloud IAM needed) |
| [verification-method.md](references/verification-method.md) | Step-by-step verification for each workflow |
| [related-commands.md](references/related-commands.md) | Common job-env-manager and curl commands |
| [acceptance-criteria.md](references/acceptance-criteria.md) | Acceptance criteria for a successful switch |
| [troubleshooting.md](references/troubleshooting.md) | Troubleshooting for common failure scenarios |
| [dataflow-diagram.md](references/dataflow-diagram.md) | Mermaid data flow diagram |
| [demo/example-input.json](demo/example-input.json) | Example input for the switch workflow |
huawei-cloud-openviking-embedding-switch · 人気上昇中の Agent Skills | Mengbi