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模型切换".
Installation
npx skills add https://github.com/huaweicloud/huaweicloud-skills --skill huawei-cloud-openviking-embedding-switch
Skill-Dateien
SKILL.md
Zuletzt synchronisiert · 29.08.2026
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/startscripts/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 |