Memory System (中文 (繁體))
選擇嵌入提供者(v3.8.16+)
Section titled “選擇嵌入提供者(v3.8.16+)”OmniRoute 的記憶引擎支援四種嵌入來源(src/lib/memory/embedding/)。每一種在延遲、成本、模型品質與設定複雜度方面各有取捨。
| 提供者 | 來源 | 延遲 | 成本 | 品質 | 設定 |
|---|---|---|---|---|---|
transformers |
本機 ONNX 模型(Xenova/all-MiniLM-L6-v2) | 約 50-150ms(CPU) | 免費 | 良好 | 僅需 npm install |
static |
預先計算的向量(已快取) | <1ms | 免費 | 不適用(取決於快取命中) | 無 |
remote |
OpenAI / Cohere / Voyage API | 約 100-300ms | $0.02-0.10/1M tokens | 極佳 | API 金鑰 |
auto |
在執行階段選擇最佳可用來源 | 與所選來源相同 | 免費 | 與所選來源相同 | 無 |
| (cache) | 位於任何來源之上的記憶體內 LRU 層 | <1ms(命中),完整延遲(未命中) | 免費 | 與底層來源相同 | 永遠啟用(不可選取的來源) |
您的部署環境為何? │ ┌───────────┼───────────┬──────────────┐ │ │ │ │ 開發/測試 小型正式環境 大型正式環境 邊緣/離線 │ │ │ │ ▼ ▼ ▼ ▼ transformers transformers remote (Qdrant) transformers (免費、無需 API) (最佳品質) (無需網際網路) │ │ │ │ └────────┬──┴───────────┴──────────────┘ │ ▼ 一律在最上層新增 `cache` 層 (LruCache 會包裝任何提供者)資料庫與 API 組態
Section titled “資料庫與 API 組態”記憶嵌入選項是透過設定 API/UI 進行配置,而非環境變數。設定中相關的資料庫鍵(src/lib/memory/settings.ts 內的 normalizeMemorySettings)如下:
memoryEmbeddingSource:"transformers"(本機)、"remote"(以 API 為基礎,例如 OpenAI)、"static"(外部儲存區)或"auto"memoryEmbeddingProviderModel:遠端/靜態來源的模型識別碼(例如"text-embedding-3-small")memoryTransformersEnabled:true|falsememoryStaticEnabled:true|falsememoryVectorStore:"sqlite-vec"、"qdrant"或"auto"
本機模型(transformers)
Section titled “本機模型(transformers)”在內部使用 transformers.js 執行本機模型:
# 程式碼中讀取的環境變數(src/lib/memory/embedding/index.ts):MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # HF 模型儲存庫MEMORY_STATIC_MODEL=minishlab/potion-base-8M # HF 靜態 potion 模型MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # 快取目錄LRU 嵌入快取
Section titled “LRU 嵌入快取”快取預設永遠啟用,並透過環境變數進行配置:
MEMORY_EMBEDDING_CACHE_MAX=1000 # 快取項目上限MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL(5 分鐘)在典型的 4 核心 x86 伺服器上進行基準測試(每段文字約 100 個 token):
| 提供者 | p50 | p95 | p99 | 每 100 萬個嵌入向量的成本 |
|---|---|---|---|---|
transformers (CPU) |
80ms | 180ms | 350ms | 免費 |
remote (OpenAI) |
120ms | 220ms | 400ms | 約 $0.02 (ada-002) / $0.13 (3-large) |
static (Qdrant) |
15ms | 30ms | 60ms | 視 Qdrant 託管服務而定 |
cache(命中) |
<1ms | <1ms | 2ms | 免費 |
事實擷取模式 (v3.8.16+)
Section titled “事實擷取模式 (v3.8.16+)”extraction.ts 模組 (src/lib/memory/extraction.ts) 使用正規表示式模式比對,從對話訊息中擷取結構化事實。瞭解這些模式有助於針對您的使用案例調整擷取品質。
預設模式類別
Section titled “預設模式類別”| 類別 | 模式範例 | 擷取內容 |
|---|---|---|
| PREFERENCE_PATTERNS | "我偏好 <X>"、"我喜歡 <X>"、"我討厭 <X>" |
使用者偏好 |
| DECISION_PATTERNS | "我會使用 <X>"、"我決定要 <X>"、"我選擇了 <X>" |
使用者決策(情節性) |
| PATTERN_PATTERNS | "我通常 <X>"、"我總是 <X>"、"我從不 <X>" |
持續性的行為模式 |
模式範例(簡化版)
Section titled “模式範例(簡化版)”// 來自 src/lib/memory/extraction.tsconst PREFERENCE_PATTERNS = [ /\bI\s+(?:really\s+)?prefer\s+([^.,\n]+)/gi, /\bI\s+(?:really\s+)?like\s+([^.,\n]+)/gi, /\bI\s+(?:hate|dislike|avoid)\s+([^.,\n]+)/gi,];const DECISION_PATTERNS = [ /\bI'?(?:ll|will)\s+use\s+([^.,\n]+)/gi, /\bI\s+(?:have\s+)?decided\s+(?:to\s+)?([^.,\n]+)/gi,];const PATTERN_PATTERNS = [/\bI\s+usually\s+([^.,\n]+)/gi, /\bI\s+always\s+([^.,\n]+)/gi];會擷取哪些內容
Section titled “會擷取哪些內容”當使用者說:
「我偏好 TypeScript。這個專案我會使用 Postgres。我總是在推送前提交。我不喜歡 Python。」 擷取會產生 4 筆記憶:
鍵值 類別 類型 內容 preference:typescriptpreference factual “TypeScript” decision:postgres_for_this_projectdecision episodic “Postgres for this project” pattern:commit_before_pushingpattern factual “commit before pushing” preference:pythonpreference factual “Python”
為避免無限制擷取,會套用以下限制:
| 最小內容長度 | 3 個字元 | | 最大內容長度 | 500 個字元 |
何時停用擷取
Section titled “何時停用擷取”只要啟用記憶功能,擷取就會自動執行;沒有獨立的
僅擷取切換選項。若要將其關閉,請完全停用記憶功能(透過 PUT /api/settings/memory
設定 enabled: false)。請在下列情況考慮這麼做:
- 訊息量很大,且擷取成本不可忽略
- 對話大多是暫時性的(聊天、偵錯),沒有長期價值
- 已透過自訂外掛程式擷取上下文
混合式 RRF 調校 (v3.8.16+)
Section titled “混合式 RRF 調校 (v3.8.16+)”倒數排名融合 (Reciprocal Rank Fusion, RRF) 演算法會合併 FTS5(關鍵字)與向量(語意)結果。k 參數控制給予排名較低結果的權重。
對於每個候選記憶,RRF 分數為:
RRF(d) = Σ 1 / (k + rank_i(d))其中:
k是常數(預設為 60)rank_i(d)是文件d在第 i 個擷取系統(FTS、向量)中的排名- 此總和涵蓋所有擷取系統
k 如何影響結果
Section titled “k 如何影響結果”k 值 |
效果 | 最適合的情況 |
|---|---|---|
k=0 |
純排名融合(無平滑處理) | 理論基準 |
k=10-30 |
大幅提高頂端結果的權重,低排名結果幾乎沒有貢獻 | 前 3 名結果通常正確時 |
k=60(預設值) |
平衡——前 10 名結果都有顯著貢獻 | 通用擷取 |
k=100+ |
更平坦——如果低排名結果出現在多個系統中,甚至也可能占主導地位 | 召回率比精確率更重要時 |
實務上調校 k
Section titled “實務上調校 k”# 預設值MEMORY_RRF_K=60
# 積極追求精確率(記憶體小、文件少)MEMORY_RRF_K=20
# 最大召回率(記憶體大、查詢多樣)MEMORY_RRF_K=120k=20 的範例:
- FTS 排名 1 → 貢獻
1/21 = 0.048 - FTS 排名 10 → 貢獻
1/30 = 0.033 - 向量排名 1 → 貢獻
0.048 - 合併後最大值:
0.096
k=60 的範例:
- FTS 排名 1 → 貢獻
1/61 = 0.016 - FTS 排名 10 → 貢獻
1/70 = 0.014 - 向量排名 1 → 貢獻
0.016 - 合併後最大值:
0.033
k 越高,排名第 1 與第 10 名之間的相對差異越小,因此演算法會更依賴擷取系統之間的共識,而不是最高排名的信賴度。
何時變更 k
Section titled “何時變更 k”| 症狀 | 建議嘗試 |
|---|---|
| 頂端結果總是勝出,但它是錯的 | 降低 k(例如 20)——最高排名的信賴度更重要 |
| 正確答案在前 5 名內,但不是第 1 名 | 提高 k(例如 100)——更平坦的評分方式會獎勵共識 |
| 召回率高,但精確率低 | 降低 k——讓排名區分更明顯 |
| 召回率低(遺漏相關文件) | 提高 k——讓排名較低的文件也有機會 |
RRF 權重
Section titled “RRF 權重”倒數排名融合會對語意向量排名與全文搜尋排名使用相同權重:
RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)沒有可用來調整個別權重的環境變數(MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT 不存在)。
摘要策略 (v3.8.16+)
Section titled “摘要策略 (v3.8.16+)”summarization.ts 模組 (src/lib/memory/summarization.ts) 會壓縮較舊的記憶,在保留回憶能力的同時縮小使用中的記憶集。
何時觸發摘要
Section titled “何時觸發摘要”| 觸發方式 | 閾值(預設) |
|---|---|
| 透過 API 手動觸發 | 不適用 |
summarization.ts 匯出兩個進入點:
summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000)— 將工作階段的 記憶濃縮成單一摘要文字,並限制在指定的 token 預算內。summarizeMemoriesOlderThan(apiKeyId, days, dryRun)— API 使用的依時間 壓縮功能:選取所有早於days的記憶,從中建立一筆濃縮摘要記憶,並在dryRun為false時刪除原始記憶。傳入dryRun: true可預覽候選集合與 token 總數,而不修改任何內容。
此處沒有標籤/鍵值分群步驟,也不會對每筆記憶進行「核心與可摘要」評分 — 選取完全依據時間截止點,而摘要文字則由每個候選項目各自形成一行經濃縮、 帶有類型前綴的內容。
摘要是手動/選擇性啟用的 — autoSummarize 設定預設為 false,
因此不會自動壓縮任何內容。請透過 API 觸發:
curl -X POST http://localhost:20128/api/memory/summarize \ -H "Authorization: Bearer $OMNIROUTE_KEY"若要保持停用,只需將 autoSummarize 維持在其預設值 (false)。
摘要品質建議
Section titled “摘要品質建議”- 先使用
dryRun預覽 —summarizeMemoriesOlderThan(..., true)會傳回 候選清單與 token 總數,讓您在刪除原始記憶前確認哪些內容將被合併。 - 如果您擁有大量記憶資料,請在低流量時段執行摘要 — LLM 呼叫是最耗時的部分
# Cron 形式:每天凌晨 3 點進行摘要0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \ -H "Authorization: Bearer $OMNIROUTE_KEY"MemoryBackend 提供者模式
Section titled “MemoryBackend 提供者模式”唯一真實來源:
src/lib/memory/backend.ts、src/lib/memory/genericBackend.ts、src/lib/memory/manager.ts測試:src/lib/memory/__tests__/generic-backend.test.ts
MemoryBackend 提供者模式在現有的記憶引擎之上導入一層可插拔的後端抽象層。記憶系統不再與單一儲存實作綁定,而是支援多種後端(SQLite、Obsidian、Notion、自訂 HTTP 後端),並可設定主要/備援路由。
┌──────────────────────────────────────────────────────────┐│ API 路由 ││ (src/app/api/memory/route.ts) │└──────────────────────┬───────────────────────────────────┘ │┌──────────────────────▼───────────────────────────────────┐│ MemoryManager ││ 單例協調器 (manager.ts) ││ ││ 主要 ────► 後端 A (例如 SQLite) ││ 備援 ────► 後端 B (例如 Obsidian) ││ 後端 C (例如透過 GenericBackend 的 Notion)│└──────────────────────┬───────────────────────────────────┘ │ ┌──────────────┼──────────────┐ ▼ ▼ ▼┌────────────┐ ┌────────────┐ ┌──────────────────┐│ SQLite │ │ Obsidian │ │ GenericMemory ││ 後端 │ │ 後端 │ │ 後端 (HTTP) │└────────────┘ └────────────┘ └──────────────────┘核心介面 (backend.ts)
Section titled “核心介面 (backend.ts)”每個後端都必須實作 MemoryBackend 介面:
interface MemoryBackend { readonly id: string; readonly displayName: string;
// 建立、讀取、更新、刪除 create(input: CreateMemoryInput): Promise<Memory>; get(id: string): Promise<Memory | null>; update(id: string, updates: Partial<...>): Promise<boolean>; delete(id: string): Promise<boolean>; list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// 搜尋 search(config: SearchConfig): Promise<Memory[]>;
// 健康狀態 health(): Promise<HealthCheckResult>;
// 生命週期(選用) initialize?(): Promise<void>; shutdown?(): Promise<void>;}MemoryManager (manager.ts)
Section titled “MemoryManager (manager.ts)”此單例協調器會:
- 透過
register(backend)註冊後端 — 啟動時由index.ts呼叫 - 透過
configure(primary, fallbacks)設定主要後端與備援後端 - 將 CRUD/搜尋路由至主要後端,失敗時使用備援鏈
- 定期對所有後端執行健康檢查
備援行為:
| 操作 | 主要後端 | 備援後端 |
|---|---|---|
create |
✅ 僅主要後端 | ❌ |
get |
✅ 先嘗試主要後端 | ✅ 若為 null 則使用備援 |
update |
✅ 僅主要後端 | ✅ 即發即棄同步 |
delete |
✅ 僅主要後端 | ✅ 即發即棄同步 |
list |
✅ 僅主要後端 | ❌ |
search |
✅ 先使用主要後端 | ✅ 發生錯誤時使用備援 |
GenericMemoryBackend (genericBackend.ts)
Section titled “GenericMemoryBackend (genericBackend.ts)”一種通用 HTTP 連接器,可將任何 REST API 轉接為 MemoryBackend。適用於:
- Notion — 透過 Notion API 連線
- Obsidian — 透過 Obsidian Local REST API 連線
- 自訂後端 — 任何公開 RESTful 記憶 API 的服務
設定:
interface GenericBackendConfig { baseUrl: string; // 後端 API 的基礎 URL apiKey?: string; // 用於驗證的 Bearer 權杖 headers?: Record<string, string>; // 自訂 HTTP 標頭 timeout?: number; // 請求逾時時間(預設:30000ms) backendType?: string; // 用於記錄日誌
// 端點覆寫(預設使用 REST 慣例) endpoints?: { search?: string; // 預設:"/memories/search" create?: string; // 預設:"/memories" list?: string; // 預設:"/memories" get?: string; // 預設:"/memories/{id}" update?: string; // 預設:"/memories/{id}" delete?: string; // 預設:"/memories/{id}" health?: string; // 預設:"/health" };
// 查詢參數名稱對應 queryParams?: { query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options? };
// 路徑參數名稱對應 pathParams?: { id?/memoryId? };}已知後端已預先設定於 KNOWN_BACKENDS:
createKnownBackend("obsidian"); // → 指向 localhost:27123 的 GenericMemoryBackendcreateKnownBackend("notion"); // → 指向 api.notion.com/v1 的 GenericMemoryBackendSQLiteBackend (sqliteBackend.ts)
Section titled “SQLiteBackend (sqliteBackend.ts)”預設的主要後端。使用 src/lib/memory/store.ts 封裝現有的 SQLite 記憶體儲存區。啟動時會自動註冊。
import { sqliteBackend } from "./sqliteBackend";memoryManager.register(sqliteBackend);ObsidianBackend (obsidianBackend.ts)
Section titled “ObsidianBackend (obsidianBackend.ts)”封裝現有的 Obsidian 整合功能(src/lib/memory/obsidianBackend.ts)。透過 Obsidian Local REST API 連線至 Obsidian vault。
記憶體後端設定儲存在應用程式設定資料表中,並透過 src/lib/memory/settings.ts 管理:
| 設定 | 環境/設定鍵 | 預設值 | 說明 |
|---|---|---|---|
| 主要後端 | memoryPrimaryBackend |
"sqlite" |
主要後端的 ID |
| 備援後端 | memoryFallbackBackends |
[] |
依序排列的備援後端 ID |
| 後端設定 | memoryBackendConfigs |
{} |
各後端的設定覆寫 |
設定會透過 normalizeMemorySettings() 正規化,並快取於 getMemorySettings()。
應用程式啟動 → index.ts 匯入(副作用):註冊 SQLiteBackend → 從應用程式生命週期呼叫 initMemoryBackends(): 1. 載入設定(getMemorySettings) 2. 設定主要後端與備援後端 3. 初始化所有後端(健康狀態檢查) 4. 準備接收請求- 在
src/lib/memory/<name>Backend.ts中實作MemoryBackend介面 - 從
src/lib/memory/index.ts匯出 - 啟動時使用
memoryManager.register(yourBackend)註冊 - 透過設定進行設定:將
memoryPrimaryBackend設為後端 ID - 以
src/lib/memory/__tests__/generic-backend.test.ts作為參考進行測試
範例:Brain 後端
Section titled “範例:Brain 後端”import { createGenericMemoryBackend } from "./genericBackend";
const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", { baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099", apiKey: process.env.BRAIN_API_KEY, endpoints: { search: "/api/memory/search", create: "/api/memory", health: "/api/health", },});
memoryManager.register(brainBackend);npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose預期輸出:35 項測試,全部通過,涵蓋:
- 建構函式(2)
- 健康狀態檢查(4)— 成功、失敗 500、網路錯誤、延遲
- 初始化(2)— 成功、失敗
- 建立(2)— 預設端點、自訂端點
- 取得(4)— 成功、404 → null、非 404 時擲出例外、自訂路徑參數
- 更新(2)— 成功、404 → false
- 刪除(2)— 成功、404 → false
- 列出(2)— 查詢參數、自訂參數名稱
- 搜尋(3)— 查詢參數、自訂端點、選項序列化
- 驗證標頭(2)— Bearer 權杖、自訂標頭
- 工廠函式(1)
npm run typecheck:core預期結果:0 個錯誤。
HagiCode
HagiCode 是智慧代理程式開發工作台,結合結構化工作流程、多代理程式執行與 Hero Dungeon 介面,將想法化為交付成果。
以更聰明、更快速且更有趣的智慧代理程式工作流程,打造實用的軟體。

- Smart結構化流程將意圖轉化為從構想到交付的可執行步驟。
- Efficient多代理程式工作流程讓研究、實作與審查並行進行。
- FunHero Dungeon 讓長時間的程式協作更直覺、更有參與感。