diff --git a/docs/backlog.md b/docs/backlog.md index 88d5a03..2af5ffd 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -98,6 +98,9 @@ |---|---|---|---|---|---|---| | BL-036 | LanceDB ANN fast-path for large scopes | P2 | planned | TBD | TBD | 新增 `LANCEDB_OPENCODE_PRO_VECTOR_INDEX_THRESHOLD` (預設 1000);當 scope entries ≥ 閾值時自動建立 IVF_PQ 向量索引;`memory_stats` 揭露 `searchMode` 欄位;`pruneScope` 超過 `maxEntriesPerScope` 時發出警告日誌 [Surface: Plugin] | | BL-037 | Event table TTL / archival | P1 | planned | TBD | TBD | 為 `effectiveness_events` 建立保留期與歸檔機制,降低長期 local store 成本 [Surface: Plugin] | +| BL-048 | LanceDB 索引衝突修復與備份安全機制 | P1 | proposed | TBD | TBD | 修復 ensureIndexes() 重試邏輯 + 可選定期備份 config [Surface: Plugin + Docs] | +| BL-049 | Embedder 錯誤容忍與 graceful degradation | P1 | proposed | TBD | TBD | embedder 失敗時的重試/延遲 + 搜尋時 BM25 fallback [Surface: Plugin] | +| BL-050 | 內建 embedding 模型(transformers.js) | P1 | proposed | TBD | TBD | 新增 TransformersEmbedder,提供離線 embedding 能力 [Surface: Plugin] | ## Epic 10 — 架構可維護性與效能硬化 @@ -127,7 +130,7 @@ BL-003, BL-014, BL-015, BL-016, BL-017, BL-018, BL-019, BL-020 待處理:BL-021(Plugin;視 upstream 事件), BL-032(Test-infra), BL-033(Test-infra + docs), BL-040(Plugin) ### Release D(儲存引擎與規模韌性)— 📝 PLANNED -BL-036, BL-037 +BL-036, BL-037, BL-048, BL-049, BL-050 ### Release E(架構可維護性與效能硬化)— 📝 PLANNED BL-041, BL-043, BL-044, BL-045, BL-046, BL-047 diff --git a/docs/embedding-self-hosted.md b/docs/embedding-self-hosted.md new file mode 100644 index 0000000..83a2b2b --- /dev/null +++ b/docs/embedding-self-hosted.md @@ -0,0 +1,172 @@ +# Embedding 模型自託管研究 + +> 研究日期:2026-04-03 +> 原因:降低對 Ollama 服務的依賴,提升 plugin 穩定性 + +--- + +## 問題背景 + +目前 plugin 仰賴 Ollama 服務提供 `nomic-embed-text` 模型: +- 服務宕機 → plugin 完全無法運作 +- 需要維護額外服務(Ollama) +- npm 发行時使用者需自行安裝與運行 Ollama + +**目標**:在 plugin 內提供內建的 embedding 模型,實現真正的 **zero-setup**。 + +--- + +## 可行方案 + +### 方案 A:@huggingface/transformers.js + +| 項目 | 內容 | +|------|------| +| 模型 | `Xenova/all-MiniLM-L6-v2` | +| 維度 | 384 | +| 安裝 | `npm i @huggingface/transformers` | +| 運行 | WebGPU / WebAssembly / CPU | +| 下載 | 約 90MB(首次) | + +**優點**: +- 15K stars,最成熟穩定 +- 支援 WebGPU 加速 +- 自動下載模型權重 +- 跨平台(browser/Node.js) + +**缺點**: +- 模型維度 384 vs 目前 768(需 schema migration 或 dual-dim) +- 首次下載時間 + +### 方案 B:fastembed + +| 項目 | 內容 | +|------|------| +| 模型 | `Qdrant/fastembed-ontelt5-base` | +| 維度 | 384 | +| 安裝 | `npm i fastembed` | +|運行 | Node.js 原生 | + +**優點**: +- 專為 Node.js 設計 +- 較小的套件大小(~100KB) + +**缺點**: +- 新套件(2025 發布),穩定性待驗證 +- 模型選擇較少 + +### 方案 C:保持現狀 + fallback + +| 項目 | 內容 | +|------|------| +| 預設 | Ollama(nomic-embed-text) | +| fallback | transformers.js(離線模式) | +| 觸發條件 | Ollama 不可用 | + +**優點**: +- 不破壞現有功能 +- 提供離線能力 + +**缺點**: +- 實作複雜度較高 + +--- + +## 向量維度 Migration 策略 + +| 方案 | 現有資料處理 | +|------|-------------| +| **A: Dual-dim** | 兩個維度共存,用時降維或升維 | +| **B: Re-embed** | 離線工具重算所有向量 | +| **C: 新 table** | 新 table 存放新維度 | + +**推薦方案 B**:提供 migration tool,使用者自願升級。 + +--- + +## 實作評估 + +### 實作價值:⭐⭐⭐⭐☆(高) + +| 評估 | 分數 | +|------|------| +| 穩定性提升 | ⭐⭐⭐⭐⭐ | +| zero-setup | ⭐⭐⭐⭐⭐ | +| 向量相容 | ⭐⭐⭐ | +| 套件大小 | ⭐⭐⭐ | +| 實作複雜度 | ⭐⭐⭐⭐ | + +### BL-050(建議新增) + +| 項目 | 內容 | +|------|------| +| BL-ID | BL-050 | +| Title | 內建 embedding 模型(transformers.js) | +| Priority | P1 | +| Status | proposed | +| Surface | Plugin | +| 實作 | 1. 新增 TransformersEmbedder 2. config fallback 链 3. 向量維度 migration tool | + +--- + +## 更新的 backlog + +在 `Epic 9 — 儲存引擎與規模韌性` 新增: + +```markdown +| BL-050 | 內建 embedding 模型(transformers.js) | P1 | proposed | TBD | TBD | +新增 TransformersEmbedder,提供离线 embedding 能力 [Surface: Plugin] | +``` + +--- + +## 實作後的差異分析 + +### 精確度影響 + +| 模型 | 維度 | MTEB 得分 | 適用場景 | +|------|------|-----------|----------| +| nomic-embed-text | 768 | ~60+ | 高精確度需求 | +| all-MiniLM-L6-v2 | 384 | ~55-58 | 一般用途足夠 | + +**說明**: +- 維度降低不直接導致精確度下降 +- all-MiniLM-L6-v2 是成熟模型,在 Sentence Similarity 任務上表現穩定 +- 對於短文本檢索(memory 場景),差異不明顯 + +### Package 大小 + +| 項目 | 大小 | 說明 | +|------|------|------| +| lancedb-opencode-pro | ~237 KB | plugin 本體(不變) | +| @huggingface/transformers | ~2 MB | runtime | +| 模型權重 | ~90 MB | 首次調用時下載 | + +**關鍵**:plugin npm package 大小維持不變 (~237KB),額外依賴在 runtime 動態下載。 + +### 使用者體驗差異 + +| 項目 | Ollama (+ nomic-embed-text) | Transformers.js | +|------|-------------------------|-----------------| +| 首次設定 | 需安裝運行 Ollama | 自動下載 (~90MB) | +| 記憶體佔用 | Ollama 服務佔用 | 模型在 GPU/CPU | +| 離線能力 | Ollama 關閉即不可用 | 下載後可離線 | +| **npm package 大小** | ~237 KB | ~237 KB | + +### 運作模式比較 + +| 現狀 | Transformers.js 版本 | +|------|-------------------| +| 依賴 Ollama HTTP API | 依賴 HuggingFace CDN | +| 服務需手動啟動 | 自動下載模型 | +| 網路不穩定→失敗 | 網路不穩定→首次失敗 | + +**結論**:實作後體驗類似,只是換了一個下載來源。下載後可離線運行(比 Ollama 更穩定)。 + +--- + +## 參考資料 + +- [Transformers.js](https://github.com/huggingface/transformers.js/) +- [fastembed npm](https://www.npmjs.com/package/fastembed) +- [Xenova/all-MiniLM-L6-v2](https://huggingface.co/Xenova/all-MiniLM-L6-v2) \ No newline at end of file diff --git a/docs/memory-backup-safety.md b/docs/memory-backup-safety.md new file mode 100644 index 0000000..f99cac3 --- /dev/null +++ b/docs/memory-backup-safety.md @@ -0,0 +1,231 @@ +# Memory 儲存安全與備份機制研究 + +> 研究日期:2026-04-03 +> 原因:memory_stats 顯示 LanceDB 索引 conflict 錯誤,導致 vector/fts 索引失效 + +--- + +## 問題診斷 + +### 錯誤訊息 + +``` +lance error: Retryable commit conflict for version 6: +This CreateIndex transaction was preempted by concurrent transaction CreateIndex at version 6. +Please retry. +``` + +### 根本原因分析 + +在 `src/store.ts:1959-1983` 的 `ensureIndexes()` 函式中: + +```typescript +private async ensureIndexes(): Promise { + const table = this.requireTable(); + + try { + await table.createIndex("vector"); // ← 同時建立 vector 索引 + this.indexState.vector = true; + } catch { + this.indexState.vector = false; + } + + try { + // FTS 索引建立也可能衝突 + await table.createIndex("text", ...); + this.indexState.fts = true; + } catch (error) { + this.indexState.fts = false; + this.indexState.ftsError = error.message; + } +} +``` + +**問題**: +1. 每次 init() 都會嘗試建立索引(如果尚未建立) +2. 兩個 `createIndex` 呼叫沒有序列化保護 +3. LanceDB 的索引建立是 transaction,如果同時有多個連線或進程嘗試建立索引,會發生 conflict +4. 失敗後 `indexState` 狀態不會重試,導致永久失效 + +--- + +## LanceDB 原生安全機制 + +### 1. 版本控制 (Versioning) + +LanceDB 內建表格版本追蹤: + +| 操作 | API | 說明 | +|------|-----|------| +| 查看版本 | `table.version()` | 取得目前版本號 | +| 切換版本 | `table.checkout(n)` | 時間旅行到指定版本 | +| 回歸最新 | `table.checkoutLatest()` | 回到最新版本 | +| 復原 | `table.restore(n)` | 復原到指定版本 | +| 標籤 | `table.createTag("name")` | 為版本設標籤 | +| 清理 | `table.optimize({cleanupOlderThan})` | 清理舊版本 | + +```typescript +// 時間旅行範例 +const table = await db.openTable("memories"); +const version = await table.version(); +console.log(`Current version: ${version}`); + +// 復原到舊版本 +await table.restore(5); +``` + +### 2. 資料保護特性 + +- ✅ ACID transactions(事務支援) +- ✅ MVCC(多版本併發控制) +- ✅ 自動版本歷史(預設保留 7 天) +- ❌ **無自動備份排程**(需自行實作) +- ❌ **無 RDB 類似的定期快照**(需外部腳本) + +--- + +## 實作建議:定期備份機制 + +### 方案 A:使用 LanceDB 版本復原(無外部備份) + +**概念**:利用 LanceDB 內建的版本系統作為還原點 + +**優點**: +- 不需要額外儲存空間 +- 復原速度快 +- 內建功能 + +**缺點**: +- 需要先 checkout 到舊版本才能復原 +- 版本會被 `optimize()` 清理 +- 無法跨 DB 檔案備份 + +```typescript +// 腳本:建立備份復原點(概念) +const table = await db.openTable("memories"); +await table.createTag(`backup-${Date.now()}`); // 如:backup-1712140800000 +``` + +### 方案 B:手動匯出備份(JSON/Parquet) + +**概念**:定期匯出資料到外部檔案 + +**優點**: +- 完全獨立的備份檔 +- 可跨機器遷移 +- 可版本控制(如 commit 到 git) + +**缺點**: +- 匯出需要時間 +- 需要額外儲存空間 +- 復原時需要覆寫表格 + +```bash +# 概念:匯出腳本 +# 使用 lancedb CLI 或 script +lancedb export memories --format json --output ./backups/memories-$(date +%Y%m%d).json +``` + +### 方案 C:定時建立 clean DB 目錄(最安全) + +**概念**:定時複製整個 DB 目錄 + +```bash +# crontab 範例:每日 03:00 備份 +0 3 * * * rsync -av --delete ~/.opencode/memory/lancedb/ ~/.opencode/memory/lancedb-backup-$(date +\%Y\%m%d)/ +``` + +**優點**: +- 完整 DB 狀態(包括索引) +- 復原最簡單(置換目錄) +- 可保留多個備份點 + +**缺點**: +- 硬碟空間需求 +- 複製時間(取決於 DB 大小) + +--- + +## 實作價值評估 + +### 評估標準 + +| 標準 | 權重 | 方案 A | 方案 B | 方案 C | +|------|------|--------|--------|--------| +| 實作難度 | 高 | ★★★ | ★★ | ★ | +| 成本(空間) | 高 | ★ | ★★ | ★★★ | +| 復原速度 | 高 | ★★ | ★★ | ★★★ | +| 獨立性 | 高 | ★ | ★★★ | ★★★ | +| 可遷移性 | 中 | ★ | ★★★ | ★★★ | + +### 建議 + +**如果專案已有穩定索引建立**(不會常冲突): +- 方案 C(定時 rsync)是最簡單有效的安全網 +- 低實作成本,高保障 + +**如果索引問題持續发生**: +- 需要修復 `ensureIndexes()` 的重試邏輯 +- 加上方案 C 作為最終安全網 + +--- + +## 實作項目: BL-048(新增) + +| 項目 | 內容 | +|------|------| +| BL-ID | BL-048 | +| Title | LanceDB 定期備份與安全機制 | +| Priority | P1 | +| Status | proposed | +| Surface | Plugin + Docs | +| 實作 | 1. 修復 ensureIndexes() 重試邏輯 2. 建立 optional backup tool 或 config 3. 更新 operations.md | + +--- + +## 修復 ensureIndexes() 建議 + +修改 `src/store.ts` 的 `ensureIndexes()` 加入重試邏輯: + +```typescript +private async ensureIndexes(retries = 3): Promise { + const table = this.requireTable(); + + // Vector 索引建立,重試最多 3 次 + for (let attempt = 0; attempt < retries; attempt++) { + try { + await table.createIndex("vector"); + this.indexState.vector = true; + break; + } catch (error) { + if (attempt === retries - 1) { + console.error("[store] Failed to create vector index after 3 attempts:", error); + this.indexState.vector = false; + } else { + await new Promise(r => setTimeout(r, 500)); // 500ms backoff + } + } + } + + // FTS 索引建立,同樣重試 + // ... +} +``` + +--- + +## 更新建議:backlog.md + +在 `Epic 9 — 儲存引擎與規模韌性` 新增項目: + +```markdown +| BL-048 | LanceDB 定期備份與安全機制 | P1 | proposed | TBD | TBD | 1. 修復 ensureIndexes() 重試 2. 可選 backup config 3. 更新 operations.md | +``` + +--- + +## 參考資料 + +- [LanceDB Versioning](https://docs.lancedb.com/tables/versioning) +- [LanceDB TypeScript SDK](https://lancedb.github.io/lancedb/js/) +- [Context7: LanceDB Restore](https://context7.com/lancedb/lancedb/llms.txt) \ No newline at end of file diff --git a/docs/roadmap.md b/docs/roadmap.md index 693d5b5..9582aeb 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -413,6 +413,9 @@ OpenCode 要從「有長期記憶的工具」進化成「會累積團隊工作 13. Duplicate consolidation 擴充性重構(Surface: Plugin)→ BL-044 ✅ DONE 14. Scope cache 記憶體治理(Surface: Plugin)→ BL-045 ✅ DONE 15. DB row runtime schema validation(Surface: Plugin + Test-infra)→ BL-046 +16. LanceDB 索引衝突修復與備份安全機制(Surface: Plugin + Docs)→ BL-048 ⚠️ 研究完成,待實作 +17. Embedder 錯誤容忍與 graceful degradation(Surface: Plugin)→ BL-049 ⚠️ 研究完成,待實作 +18. 內建 embedding 模型(transformers.js)(Surface: Plugin)→ BL-050 ⚠️ 研究完成,待實作 ### P2 16. large-scope retrieval 的 ANN fast-path(Surface: Plugin)→ BL-036