@modusensus/dsh-mneme 0.8.14 → 0.8.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +660 -654
- package/assets/icon.svg +3 -3
- package/bin/cli.mjs +691 -689
- package/bin/dsh-mneme-mcp.mjs +510 -510
- package/bin/dsh-mneme-serve.mjs +151 -151
- package/cordis.patch.yml +21 -21
- package/docs/CONFIGURATION.md +275 -0
- package/docs/CONTINUITY.md +124 -0
- package/docs/DAEMON.md +46 -0
- package/docs/DISTILL.md +162 -0
- package/docs/ENTITIES.md +245 -0
- package/docs/LOCAL_MODEL.md +222 -0
- package/docs/SEMANTIC.md +256 -0
- package/docs/SLEEP.md +164 -0
- package/docs/STORAGE.md +98 -0
- package/lib/api-standalone.js +656 -673
- package/lib/api.js +1605 -1433
- package/lib/bootstrap.js +211 -211
- package/lib/client.js +6354 -5296
- package/lib/commands.js +95 -95
- package/lib/config.js +947 -843
- package/lib/content-hash.js +54 -54
- package/lib/continuity.js +220 -220
- package/lib/document-index.js +94 -94
- package/lib/document.js +303 -303
- package/lib/dream/clustering.js +118 -118
- package/lib/dream/decisions.js +694 -674
- package/lib/dream/narratives.js +44 -44
- package/lib/dream/sleep.js +819 -819
- package/lib/dream.js +1503 -1501
- package/lib/embedding.js +177 -177
- package/lib/entities/extractor.js +252 -252
- package/lib/graph/anchoring.js +179 -179
- package/lib/guide.js +74 -74
- package/lib/heat.js +144 -144
- package/lib/hot-memory.js +108 -46
- package/lib/index.js +552 -503
- package/lib/inject.js +428 -450
- package/lib/lang.js +248 -229
- package/lib/local-embedder.js +358 -358
- package/lib/maintenance.js +162 -142
- package/lib/mirror.js +330 -330
- package/lib/organize.js +450 -450
- package/lib/peak-hours.js +117 -117
- package/lib/placement.js +13 -0
- package/lib/pre-inject-gate.js +340 -0
- package/lib/quality-filter.js +126 -126
- package/lib/recall-stats.js +190 -175
- package/lib/reclaim-http.js +150 -0
- package/lib/reranker.js +269 -269
- package/lib/runtime/adopt.js +176 -176
- package/lib/runtime/closure.js +172 -172
- package/lib/runtime/download.js +247 -247
- package/lib/runtime/layout.js +265 -265
- package/lib/runtime/loader.js +252 -252
- package/lib/runtime/model-fetch.js +361 -361
- package/lib/runtime/provision.js +237 -237
- package/lib/runtime/tarball.js +130 -130
- package/lib/runtime/verify.js +223 -204
- package/lib/safe-slice.js +18 -0
- package/lib/scope.js +146 -146
- package/lib/search/adaptive.js +60 -60
- package/lib/search/bm25.js +96 -96
- package/lib/semantic.js +229 -229
- package/lib/sensitive-scan.js +209 -209
- package/lib/serve.js +215 -215
- package/lib/service.js +2688 -2605
- package/lib/settings.js +528 -511
- package/lib/store.js +3196 -3007
- package/lib/summarize.js +1039 -856
- package/lib/tools.js +743 -737
- package/lib/vector-index.js +110 -110
- package/lib/version-check.js +122 -122
- package/lib/write-admission.js +402 -402
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +118 -118
- package/runtime-manifest.json +526 -526
- package/scripts/benchmark-embed.js +201 -201
- package/scripts/benchmark-recall.js +249 -249
- package/scripts/benchmark-rerank.js +166 -166
- package/scripts/build-runtime-manifest.mjs +224 -224
- package/scripts/changelog-prep.mjs +36 -36
- package/scripts/check-sync.js +42 -42
- package/scripts/e2e-dsh.js +220 -220
- package/scripts/mneme-runtime.mjs +277 -277
- package/scripts/stress-dsh.js +255 -255
- package/scripts/sync-lib.js +47 -47
- package/scripts/sync-test-badge.mjs +69 -69
- package/scripts/test-count-sync.mjs +44 -44
- package/src/api-standalone.js +656 -673
- package/src/api.js +1605 -1433
- package/src/bootstrap.js +211 -211
- package/src/commands.js +95 -95
- package/src/config.js +947 -843
- package/src/content-hash.js +54 -54
- package/src/continuity.js +220 -220
- package/src/document-index.js +94 -94
- package/src/document.js +303 -303
- package/src/dream/clustering.js +118 -118
- package/src/dream/decisions.js +694 -674
- package/src/dream/narratives.js +44 -44
- package/src/dream/sleep.js +819 -819
- package/src/dream.js +1503 -1501
- package/src/embedding.js +177 -177
- package/src/entities/extractor.js +252 -252
- package/src/graph/anchoring.js +179 -179
- package/src/guide.js +74 -74
- package/src/heat.js +144 -144
- package/src/hot-memory.js +108 -46
- package/src/index.js +552 -503
- package/src/inject.js +428 -450
- package/src/lang.js +248 -229
- package/src/local-embedder.js +358 -358
- package/src/maintenance.js +162 -142
- package/src/mirror.js +330 -330
- package/src/organize.js +450 -450
- package/src/peak-hours.js +117 -117
- package/src/placement.js +13 -0
- package/src/pre-inject-gate.js +340 -0
- package/src/quality-filter.js +126 -126
- package/src/recall-stats.js +190 -175
- package/src/reclaim-http.js +150 -0
- package/src/reranker.js +269 -269
- package/src/runtime/adopt.js +176 -176
- package/src/runtime/closure.js +172 -172
- package/src/runtime/download.js +247 -247
- package/src/runtime/layout.js +265 -265
- package/src/runtime/loader.js +252 -252
- package/src/runtime/model-fetch.js +361 -361
- package/src/runtime/provision.js +237 -237
- package/src/runtime/tarball.js +130 -130
- package/src/runtime/verify.js +223 -204
- package/src/safe-slice.js +18 -0
- package/src/scope.js +146 -146
- package/src/search/adaptive.js +60 -60
- package/src/search/bm25.js +96 -96
- package/src/semantic.js +229 -229
- package/src/sensitive-scan.js +209 -209
- package/src/serve.js +215 -215
- package/src/service.js +2688 -2605
- package/src/settings.js +528 -511
- package/src/store.js +3196 -3007
- package/src/summarize.js +1039 -856
- package/src/tools.js +743 -737
- package/src/vector-index.js +110 -110
- package/src/version-check.js +122 -122
- package/src/write-admission.js +402 -402
- package/test/adaptive-inject-budget.test.js +85 -85
- package/test/api-routes-gaps.test.js +899 -218
- package/test/api.test.js +1725 -1690
- package/test/audit.test.js +448 -448
- package/test/baseline-persist.test.js +248 -248
- package/test/benchmark.test.js +94 -94
- package/test/bootstrap.test.js +220 -220
- package/test/changelog-prep.test.js +60 -60
- package/test/client.test.js +1582 -1169
- package/test/clustering.test.js +100 -100
- package/test/commands.test.js +106 -106
- package/test/config.test.js +66 -66
- package/test/conflict-freeze.test.js +300 -300
- package/test/content-hash.test.js +204 -204
- package/test/continuity.test.js +333 -333
- package/test/contrast.test.js +314 -0
- package/test/document-dir.test.js +233 -233
- package/test/document.test.js +671 -671
- package/test/dream-archive-guardrail.test.js +116 -116
- package/test/dream-candidate-hybrid.test.js +281 -281
- package/test/dream-coverage-degrade.test.js +86 -86
- package/test/dream-failure-backoff.test.js +173 -173
- package/test/dream-merge-guardrail.test.js +219 -0
- package/test/dream-peak-hours.test.js +194 -194
- package/test/dream.test.js +1385 -1383
- package/test/embedding-ready.test.js +196 -196
- package/test/entities.test.js +627 -547
- package/test/entity-recall.test.js +179 -179
- package/test/epistemic.test.js +325 -325
- package/test/fnew-0112.test.js +311 -311
- package/test/fnew-03.test.js +422 -422
- package/test/graph-anchoring.test.js +184 -184
- package/test/graph-api.test.js +175 -175
- package/test/graph-inject-budget.test.js +103 -103
- package/test/graph-passive-confirm.test.js +91 -91
- package/test/graph-weight-evolution.test.js +104 -104
- package/test/heat.test.js +175 -175
- package/test/helpers/dream-mock.js +82 -82
- package/test/helpers/peer-worker.mjs +25 -25
- package/test/helpers/runtime-source.js +60 -60
- package/test/helpers/tar-builder.js +80 -80
- package/test/helpers/write-admission-samples.js +308 -308
- package/test/hot-memory.test.js +211 -145
- package/test/inject-content-cap.test.js +91 -91
- package/test/inject-first-round-bm25.test.js +89 -89
- package/test/inject-heat.test.js +77 -77
- package/test/inject-parent-gate.test.js +106 -106
- package/test/inject-pools.test.js +194 -194
- package/test/inject-recall.test.js +144 -105
- package/test/inject-rotation.test.js +161 -161
- package/test/inject.test.js +443 -420
- package/test/lang.test.js +87 -87
- package/test/lastrunat-persist.test.js +101 -101
- package/test/lib-smoke.test.js +96 -96
- package/test/llm-audit.test.js +696 -598
- package/test/llm-message-source.test.js +207 -207
- package/test/local-embedder.test.js +292 -292
- package/test/maintenance.test.js +501 -391
- package/test/mcp-stdio.test.js +385 -385
- package/test/mirror-dirty.test.js +424 -424
- package/test/mirror-edit-digest.test.js +187 -187
- package/test/mirror-generation.test.js +499 -499
- package/test/mirror-types.test.js +208 -208
- package/test/mirror.test.js +249 -249
- package/test/narrative-bars.test.js +162 -162
- package/test/organize.test.js +418 -418
- package/test/peer-blockers.test.js +193 -193
- package/test/placement.test.js +36 -0
- package/test/policy-epoch.test.js +259 -259
- package/test/pre-inject-gate.test.js +444 -0
- package/test/quality-filter.test.js +186 -186
- package/test/reasoning-effort.test.js +752 -752
- package/test/recall-anti-update.test.js +82 -82
- package/test/recall-evals.test.js +253 -253
- package/test/recall-layer.test.js +326 -326
- package/test/recall-stats.test.js +274 -189
- package/test/receipt-chain.test.js +451 -451
- package/test/reclaim-http.test.js +209 -0
- package/test/reflection.test.js +226 -226
- package/test/reindex-backfill.test.js +62 -62
- package/test/reranker-single-logit.test.js +73 -73
- package/test/reranker.test.js +197 -197
- package/test/resident-status.test.js +83 -83
- package/test/runtime-adopt.test.js +206 -206
- package/test/runtime-cli.test.js +89 -89
- package/test/runtime-closure.test.js +193 -193
- package/test/runtime-download.test.js +302 -302
- package/test/runtime-layout.test.js +163 -163
- package/test/runtime-loader.test.js +255 -255
- package/test/runtime-manifest.test.js +159 -159
- package/test/runtime-model-fetch.test.js +348 -348
- package/test/runtime-provision.test.js +101 -101
- package/test/runtime-tarball.test.js +81 -81
- package/test/runtime-verify.test.js +319 -279
- package/test/scope-candidates.test.js +239 -239
- package/test/scope-explicit.test.js +452 -452
- package/test/scope-inject-soft.test.js +106 -106
- package/test/scope-retrieval.test.js +197 -197
- package/test/scope-storage.test.js +255 -255
- package/test/scope-strict.test.js +367 -367
- package/test/search-fusion.test.js +90 -90
- package/test/semantic.test.js +124 -124
- package/test/sensitive-scan.test.js +207 -207
- package/test/serve-bin.test.js +121 -121
- package/test/serve.test.js +196 -196
- package/test/service-search.test.js +174 -174
- package/test/service.test.js +410 -405
- package/test/settings.test.js +269 -261
- package/test/skip-invalid-restore.test.js +262 -262
- package/test/sleep-action-set.test.js +422 -422
- package/test/sleep-heat.test.js +126 -126
- package/test/sleep-scheduler-rearm.test.js +89 -89
- package/test/sleep.test.js +527 -527
- package/test/standalone-api.test.js +751 -751
- package/test/store-vector-cache.test.js +98 -98
- package/test/store.test.js +504 -396
- package/test/stress.test.js +209 -209
- package/test/summarize-occurred-at.test.js +127 -0
- package/test/summarize-reasoning-effort.test.js +189 -189
- package/test/summarize.test.js +1639 -1465
- package/test/supersede-state-tracking.test.js +394 -0
- package/test/test-count-sync.test.js +60 -60
- package/test/tools-dto-schema-matrix.test.js +325 -325
- package/test/tools-memory-get-schema.test.js +65 -65
- package/test/tools.test.js +428 -374
- package/test/updated-at-semantics.test.js +113 -113
- package/test/vector-index.test.js +221 -221
- package/test/version-check.test.js +152 -152
- package/test/webServer-optional.test.js +82 -82
- package/test/write-admission.test.js +668 -668
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# dsh-mneme 本地模型部署指南
|
|
2
|
+
|
|
3
|
+
- **日期**:2026-08-15
|
|
4
|
+
- **范围**:为 dsh-mneme 配置完全离线的本地语义模型(Embedding + Rerank),覆盖 ONNX(transformers.js)与 Ollama 两种路径
|
|
5
|
+
|
|
6
|
+
## 1. 前置要求
|
|
7
|
+
|
|
8
|
+
| 项目 | 要求 | 说明 |
|
|
9
|
+
|------|------|------|
|
|
10
|
+
| Node.js | ≥ 22.5(建议 24+) | 插件依赖 `node:sqlite`(22.5 起可用,24 稳定) |
|
|
11
|
+
| 磁盘空间 | ≥ 300MB(推荐 1GB+) | bge-small-zh-v1.5 量化约 100MB,bge-reranker-base 约 250MB;Ollama 模型另计 |
|
|
12
|
+
| 内存 | ≥ 1GB 可用 | 纯 CPU 推理亦可运行 |
|
|
13
|
+
| GPU(可选) | 显存 ≥ 2GB | 开启 `localEmbedDevice: "gpu"`(onnxruntime 需要额外安装 cuda 后端) |
|
|
14
|
+
| 网络 | 仅首次下载模型需要 | 已下载后可完全离线(默认走 Hugging Face,可换镜像源) |
|
|
15
|
+
|
|
16
|
+
> 依赖解析分三层:① **插件自管运行时目录**(`~/.dsh/mneme/runtime/`,见 §2.5,优先);② 宿主 profile 里已装的 `@huggingface/transformers`;③ 都没有则本地推理不可用,检索自动降级为关键词/BM25(读写在任何情况下都不受影响)。
|
|
17
|
+
>
|
|
18
|
+
> 从本版起 `@huggingface/transformers` 已从 `dependencies` 降为**可选 peer**(`peerDependenciesMeta.optional`),安装插件时不会再带上它和它那套原生闭包;第 ② 层因此只对「自己装过这份依赖」或「从旧版本升级上来、尚未被 prune」的环境有效。**正在使用本地嵌入的用户请在升级前按 §2.5 收编一份**,否则升级后本地嵌入不可用(读写信道不受影响)。
|
|
19
|
+
>
|
|
20
|
+
> `onnxruntime-node` 作为可选原生后端列在 `allowScripts`(首次安装需确认脚本),无需手动额外安装。
|
|
21
|
+
|
|
22
|
+
## 2. 模型安装 / 下载
|
|
23
|
+
|
|
24
|
+
### 2.1 自动下载(默认,推荐)
|
|
25
|
+
|
|
26
|
+
首次使用本地 Embedder 时会**自动从 Hugging Face Hub 下载**对应模型到本地缓存,无需手动操作:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node scripts/benchmark-embed.js --provider local --model Xenova/bge-small-zh-v1.5
|
|
30
|
+
# 首次运行会显示下载进度 → 加载模型 → 输出基准表格
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
默认缓存目录:`~/.dsh/mneme/models`(用户级,重装/升级依赖不丢;`embedModelCacheDir` 留空时启用)。transformers.js 把模型存成 `<cacheDir>/<org>/<name>/`(实测 4.2.0,例如 `~/.dsh/mneme/models/Xenova/bge-small-zh-v1.5/`):
|
|
34
|
+
|
|
35
|
+
- Linux/macOS:`~/.dsh/mneme/models/`
|
|
36
|
+
- Windows:`%USERPROFILE%\.dsh\mneme\models\`
|
|
37
|
+
|
|
38
|
+
> 实测 transformers.js 4.2.0 在离线加载(`allowRemoteModels=false`)时按 `<cacheDir>/<org>/<model>/` 解析模型文件,例如 `<cacheDir>/Xenova/bge-small-zh-v1.5/config.json`。与本段上面描述的 `hub/models--…` 布局不一致时,以实际报错里给出的路径为准;缓存不在默认位置时按 §2.5 用 `--cache-dir` 指过去。
|
|
39
|
+
|
|
40
|
+
### 2.2 手动指定缓存目录
|
|
41
|
+
|
|
42
|
+
内存受限/离线机器可把模型缓存放到指定位置(迁移、共享缓存):
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
// 配置示例(插件配置里设置 embedModelCacheDir)
|
|
46
|
+
const embedder = createEmbedderByProvider("local", {
|
|
47
|
+
cacheDir: "/data/hf-models", // 模型缓存目录
|
|
48
|
+
model: "Xenova/bge-small-zh-v1.5"
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2.3 Ollama 路径
|
|
53
|
+
|
|
54
|
+
如果本机已有 Ollama:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
ollama pull nomic-embed-text # 或 bge-m3(多语言)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Ollama Embedder 走 `/api/embeddings`,**不下载任何文件到插件缓存**,模型由 Ollama 管理。
|
|
61
|
+
|
|
62
|
+
### 2.4 断点续传
|
|
63
|
+
|
|
64
|
+
下载基于 Hugging Face Hub 缓存协议,**中断后重试自动续传**(按分片断点续传,不重新下载已完成部分)。若反复中断导致缓存损坏,删除对应模型的缓存目录后重跑即可:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
rm -rf ~/.dsh/mneme/models/hub/models--Xenova--bge-small-zh-v1.5
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2.5 自管运行时(收编 / 校验 / 排查)
|
|
71
|
+
|
|
72
|
+
本地推理真正要用的是一整套依赖闭包:`@huggingface/transformers` + `onnxruntime-node` + `sharp`(本机实测从宿主 node_modules 收编时是 **49 个包、3203 个文件、约 393MB**)。插件支持把它收编到自管目录 `~/.dsh/mneme/runtime/`,与宿主 profile 的依赖图解耦;收编**优先使用硬链接**,同盘时几乎不额外占盘。
|
|
73
|
+
|
|
74
|
+
**为什么要独立出来**:这份闭包此前留在插件 `dependencies` 里,而 profile 是所有插件共用的依赖图,于是「装任何插件」都要替它重走一遍整条链,弱网下极慢。摘掉该声明后,运行时由自管目录承接——所以要**先收编、再升级**,否则升级后本地嵌入不可用。
|
|
75
|
+
|
|
76
|
+
**三条取件来源,按可用性依次尝试**(面板按钮、`memory_runtime` 工具、CLI 三者共用同一套编排):
|
|
77
|
+
|
|
78
|
+
| 顺序 | 来源 | 特点 |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| ① | 收编本机 profile 的 `node_modules` | **零网络**;同盘硬链接,几乎不占额外空间 |
|
|
81
|
+
| ② | `runtimeTarballDir` 指向的本地 `.tgz` 目录 | 某个包网络下不到时用(文件名按 npm 约定 `<basename>-<version>.tgz`) |
|
|
82
|
+
| ③ | npm registry(`runtimeMirror` 可换镜像) | 按随包发布的 `runtime-manifest.json` 逐个取,**每个 tarball 先校验 sha512 再落盘** |
|
|
83
|
+
|
|
84
|
+
走 ③ 时体积是 **win32-x64 实测约 33 个包、2142 个文件、数百 MB**(各平台不同,故不写死数值)——比①少,因为清单剔除了 `onnxruntime-web`(127.7MB,占全量三分之一):transformers 的 Node 构建从不 import 它(把一个空的 `onnxruntime-web` 打桩进去,嵌入结果逐字节相同),风险由 `verify` 的真实推理兜底。清单本身**平台无关**(数据源是 `package-lock.json` 里所有平台的条目),下载时按各包的 `os`/`cpu` 过滤,所以 win32 / darwin / linux × x64 / arm64 都被覆盖 —— 不需要谁在某个系统上先跑一次生成脚本。
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# 在插件目录下运行(例如 node_modules/@modusensus/dsh-mneme)
|
|
88
|
+
|
|
89
|
+
# 1) 看状态:在哪、来源、结构完不完整(不加载模型,秒回)
|
|
90
|
+
node scripts/mneme-runtime.mjs status
|
|
91
|
+
|
|
92
|
+
# 2) 收编宿主里已有的那份(只读源、只写自管目录,不安装任何东西)
|
|
93
|
+
node scripts/mneme-runtime.mjs adopt --from ~/.dsh/profiles/<profile>/node_modules
|
|
94
|
+
|
|
95
|
+
# 3) 真加载运行时并跑一次推理,确认可用
|
|
96
|
+
node scripts/mneme-runtime.mjs verify --cache-dir ~/.dsh/mneme/models
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- 退出码:`0` 健康 / `1` 不健康或失败 / `2` 用法错误(便于脚本直接 gate,区分「没装」和「装坏了」看输出里的 `status`)。加 `--json` 得到机器可读结果。
|
|
100
|
+
- `verify` 的功能验证**不触网**(`allowRemoteModels=false`):模型必须已在缓存目录里,否则会明确失败——这是有意的,验证不该偷偷触网。缓存不在默认位置时用 `--cache-dir` 指到与 `embedModelCacheDir` 一致的位置。
|
|
101
|
+
- 三条入口(CLI / 面板 / `memory_runtime`)的判据是同一份:缓存目录取 `embedModelCacheDir`(**留空即用户级默认目录**,与真实嵌入同一处解析),探针模型取 `localEmbedModel`(留空即默认小模型)。也就是说验证用的就是真实推理用的那份模型——CLI 不需要额外 `--cache-dir`,面板也不会跑到包内去找(#387 修的就是这个不一致)。
|
|
102
|
+
- 收编来的目录没有可比对的原始产物,完整性如实标 `unverified`,不假装验过;结构检查通过 ≠ 功能可用,两者分开报。
|
|
103
|
+
- `status` 只做结构检查,所以 `functional` 恒为 `unknown`——要看真实推理结果就跑 `verify`。
|
|
104
|
+
- 运行时不可用时插件不会崩:检索降级为关键词/BM25,读写在任何情况下都不受影响;错误信息会带上 `adopt` 的具体命令。
|
|
105
|
+
- 同一份状态也能从 `GET /api/dsh-mneme/semantic` 的 `localRuntime` 字段读到(面板与 CLI 同源)。
|
|
106
|
+
|
|
107
|
+
#### 不用命令行也可以
|
|
108
|
+
|
|
109
|
+
- **面板**:记忆面板的「向量索引」卡片在本地 provider 且运行时不就绪时,会显示**代价说明**(要额外一份运行时、解包后数百 MB)、先试收编、没有再下载,并按实际走的那一档分开报结果。卡片下面还有一个「取回本地运行时」按钮,点一下即完成。
|
|
110
|
+
- **让 agent 代做**:`memory_runtime` 工具提供三个动作——`status`(只读,**返回里带代价说明**,便于 agent 先告知你再动手)、`provision`(走上面三档来源)、`verify`(真跑一次推理)。所以即便你不看面板,也可以直接让 agent 处理。
|
|
111
|
+
- **这条路由与其它写路由走同一道门**:`POST /api/dsh-mneme/runtime/provision` 是唯一会联网拉数百 MB 并写盘的写面,鉴权规则与其他写路由完全一致——配了 `apiToken` 就必须带 token,**没配就等于不校验**(这是插件既有的鉴权模型,不是这条路由特有的)。本机默认部署下它开箱可用;但若这个端口能被别的页面/机器够到,请照第 3.1 节配上 `apiToken`。面板按钮与 `memory_runtime` 都走这道门,不需要额外配置。
|
|
112
|
+
- **平台过滤的取向是「宽松优先」**:清单是平台无关的一份(含所有平台的包),下载时按本机 `os`/`cpu` 过滤;Linux 上如果**确认**了本机是 glibc,还会剔掉另一套 libc 的变体(`sharp` 与 `libvips` 的 musl 包,十几 MB)。拿不到 libc 证据时一律不过滤——多下十几兆,好过赌错方向装出一份跑不起来的运行时。
|
|
113
|
+
- **宿主那份 transformers 会被版本门禁**:第 ② 层(宿主裸 specifier)只接受 `>=4.2.0` 且 `<5` 的版本;宿主装到 v5 时不会被静默使用,而是明确报出「版本越界」并退到第 ③ 层的可操作提示。版本读不到(某些打包形态)时不拦,以免这层对老用户整体失效。
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
# 两个可选配置键(默认留空 = 不改变任何行为)
|
|
117
|
+
runtimeTarballDir: "" # 本地 .tgz 目录:有它就优先于联网
|
|
118
|
+
runtimeMirror: "" # registry 镜像前缀,例如 https://npmmirror.com/mirrors/npm/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### 升级前必做(否则本地嵌入会断)
|
|
122
|
+
|
|
123
|
+
如果你正在使用本地嵌入(`embedProvider: local`),**在升级到已摘掉 `dependencies` 声明的版本之前**先执行一次 `adopt` + `verify`。顺序不能反:升级时 pnpm 会 prune 掉宿主里那份依赖,那之后就没有可收编的源了,只剩联网下载一条路。
|
|
124
|
+
|
|
125
|
+
#### 自己取一份来收编(相对上面的手动路径)
|
|
126
|
+
|
|
127
|
+
上面的面板按钮 / `provision` 已经能在没有可收编副本时自行下载。但如果你希望**完全掌控取件过程**(例如用自己的镜像、或先在一处统一缓存),仍然可以手工取一份再收编——不必逐个下载包:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Windows(PowerShell / cmd)
|
|
131
|
+
mkdir %TEMP%\mneme-fetch && cd /d %TEMP%\mneme-fetch
|
|
132
|
+
npm i @huggingface/transformers@^4.2.0
|
|
133
|
+
node "<插件目录>\scripts\mneme-runtime.mjs" adopt --from "%TEMP%\mneme-fetch\node_modules"
|
|
134
|
+
|
|
135
|
+
# Linux / macOS
|
|
136
|
+
mkdir -p /tmp/mneme-fetch && cd /tmp/mneme-fetch
|
|
137
|
+
npm i @huggingface/transformers@^4.2.0
|
|
138
|
+
node "<插件目录>/scripts/mneme-runtime.mjs" adopt --from /tmp/mneme-fetch/node_modules
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **安装脚本必须能执行**:npm 默认会执行生命周期脚本,而 `onnxruntime-node` / `sharp` 正是靠 postinstall 取原生二进制;加 `--ignore-scripts` 会得到一个装不起来的闭包。pnpm 10+ 默认拦截构建脚本,所以这里用 npm 更省事。
|
|
142
|
+
- 收编完那个临时目录可以直接删掉:闭包已经在 `~/.dsh/mneme/runtime/` 里了(硬链接时几乎不额外占盘)。
|
|
143
|
+
- 收编后**仍然建议跑一次 `verify`**——结构完整不等于能推理。
|
|
144
|
+
|
|
145
|
+
## 3. 配置
|
|
146
|
+
|
|
147
|
+
### 3.1 插件配置(`~/.dsh/profiles/web/cordis.patch.yml`)
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
- id: dsh-mneme
|
|
151
|
+
name: '@modusensus/dsh-mneme'
|
|
152
|
+
config:
|
|
153
|
+
# ── Embedding ──────────────────────────────
|
|
154
|
+
embedProvider: local # local | ollama | openai(默认 openai,保持 v0.1 行为)
|
|
155
|
+
localEmbedModel: Xenova/bge-small-zh-v1.5
|
|
156
|
+
localEmbedDimension: 512 # 与模型匹配的向量维度
|
|
157
|
+
localEmbedDevice: cpu # cpu | gpu(仅 local 生效)
|
|
158
|
+
localEmbedBatchSize: 8 # 分批嵌入条数,内存紧张调小
|
|
159
|
+
embedModelCacheDir: "" # 模型缓存目录(空=用户级 ~/.dsh/mneme/models;注意:不做 ~ 展开,要指别处必须写绝对路径)
|
|
160
|
+
ollamaBaseUrl: "http://localhost:11434" # 仅 ollama 生效
|
|
161
|
+
ollamaModel: "nomic-embed-text" # 仅 ollama 生效(openai 端点走面板 vector-config)
|
|
162
|
+
# ── Rerank(Phase 2,可选)─────────────────
|
|
163
|
+
rerankEnabled: false # 开启向量召回后的精排
|
|
164
|
+
rerankModel: Xenova/bge-reranker-base
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 3.2 环境变量(可选)
|
|
168
|
+
|
|
169
|
+
| 变量 | 作用 |
|
|
170
|
+
|------|------|
|
|
171
|
+
| `HF_ENDPOINT` | Hugging Face 镜像源,例如 `https://hf-mirror.com`(国内加速) |
|
|
172
|
+
| `HF_HUB_CACHE` | 覆盖默认缓存目录 |
|
|
173
|
+
|
|
174
|
+
### 3.3 各后端配置对照
|
|
175
|
+
|
|
176
|
+
| 配置 | local (ONNX) | ollama | openai |
|
|
177
|
+
|------|--------------|--------|--------|
|
|
178
|
+
| `localEmbedModel` | HF 模型 id(如 `Xenova/bge-small-zh-v1.5`) | —(用 `ollamaModel`) | —(用面板 vector-config) |
|
|
179
|
+
| `ollamaBaseUrl` | — | `http://localhost:11434`(默认) | — |
|
|
180
|
+
| `ollamaModel` | — | Ollama 模型名(如 `nomic-embed-text`) | — |
|
|
181
|
+
| `localEmbedDevice` | ✅ | — | — |
|
|
182
|
+
| `localEmbedBatchSize` | ✅ | — | — |
|
|
183
|
+
| `embedModelCacheDir` | ✅ | — | — |
|
|
184
|
+
|
|
185
|
+
## 4. 首次使用
|
|
186
|
+
|
|
187
|
+
1. 配置 `embedProvider`(或保持 `openai` 不动)
|
|
188
|
+
2. 保存配置并重启 DSH,或直接跑 `scripts/benchmark-embed.js` 验证
|
|
189
|
+
3. 首次触发本地模型时,transformers.js 自动下载 → `init()` 打印 `local embedder ready: <model> (dim=512, device=cpu)`
|
|
190
|
+
4. 之后所有 `memory_save` 写入自动补向量;已有记忆可用「重建索引」批量补建
|
|
191
|
+
|
|
192
|
+
> 注意:切换 `localEmbedModel` 或 `embedDimension` 后,旧向量与新向量维度不同、无法混算,需**重建索引**(复用 v0.1 的 `reindexMissing` 机制)。
|
|
193
|
+
|
|
194
|
+
## 5. 常见问题
|
|
195
|
+
|
|
196
|
+
### Q1:模型下载失败 / 下载超慢
|
|
197
|
+
|
|
198
|
+
- 国内网络:设置镜像源 `HF_ENDPOINT=https://hf-mirror.com`(或 `export HF_ENDPOINT=https://hf-mirror.com`)
|
|
199
|
+
- 公司代理拦截:检查 `HTTPS_PROXY` / `HTTP_PROXY` 环境变量
|
|
200
|
+
- 磁盘不足:清理缓存或用 `embedModelCacheDir` 换目录
|
|
201
|
+
|
|
202
|
+
### Q2:`Xenova/bge-small-zh-v1.5` 加载报错 / 显存不足(OOM)
|
|
203
|
+
|
|
204
|
+
- 默认 `device: cpu`、`dtype: q8`,内存占用已最小化
|
|
205
|
+
- 尝试 `embedBatchSize` 调小(如 4/2)降低峰值内存
|
|
206
|
+
- 显存不足:`device: gpu` 时改回 `cpu`;或检查 onnxruntime-node 是否安装了 GPU 后端
|
|
207
|
+
|
|
208
|
+
### Q3:中英文混排 / CJK 文本处理
|
|
209
|
+
|
|
210
|
+
- `bge-small-zh-v1.5` 对中文与中英混排均优化良好;纯英文场景可换 Ollama 的 `nomic-embed-text`
|
|
211
|
+
- 单条文本上限 8000 字符(与 v0.1 一致),超长自动截断
|
|
212
|
+
- 检索时 query 与记忆正文走同一 Embedder、同一归一化(mean pooling + L2),余弦分数才可比
|
|
213
|
+
|
|
214
|
+
### Q4:换模型后结果变差 / 向量混乱
|
|
215
|
+
|
|
216
|
+
- 换模型(尤其维度不同)后旧向量不可复用,必须重建索引
|
|
217
|
+
- 若在 Web 面板/API 配置过 v0.1 的 `vector-config`,v0.2 会优先采用插件配置,旧配置仅作降级路径
|
|
218
|
+
|
|
219
|
+
### Q5:本地模型加载很慢(首次/每次启动)
|
|
220
|
+
|
|
221
|
+
- 首次含下载,属正常;已缓存后每次启动仅加载进内存(数百 ms 级)
|
|
222
|
+
- 大模型(如 bge-m3)可考虑换用更小的 `Xenova/bge-small-zh-v1.5`(量化 q8 约 100MB)
|
package/docs/SEMANTIC.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# dsh-mneme 语义增强架构 — 设计文档
|
|
2
|
+
|
|
3
|
+
- **日期**:2026-08-15
|
|
4
|
+
- **状态**:✅ 已实现(本地 Embedding / Rerank / 聚类随 v0.2+ 落地;本文档转为历史设计存档,当前能力以 [README](../README.md) 正文为准)
|
|
5
|
+
- **范围**:为 dsh-mneme 增加"完全离线的语义记忆引擎",覆盖本地 Embedding、Rerank 精排、autoDream 向量聚类三个阶段
|
|
6
|
+
- **相关文档**:[本地模型部署指南](LOCAL_MODEL.md) · [从 v0.1 升级说明](MIGRATION.md)
|
|
7
|
+
|
|
8
|
+
## 1. 背景与目标
|
|
9
|
+
|
|
10
|
+
dsh-mneme v0.1 已具备**可选的向量搜索**:通过 `/api/dsh-mneme/vector-config` 配置 OpenAI 兼容 `/embeddings` 端点,命中"字面不同但语义相近"的记忆;未配置或失败时降级为 LIKE 子串搜索。
|
|
11
|
+
|
|
12
|
+
v0.2 的目标是把这条**可选、依赖外部 API** 的向量链路升级为**默认可用、完全离线**的语义记忆引擎:
|
|
13
|
+
|
|
14
|
+
1. **本地 Embedding**:ONNX(`Xenova/bge-small-zh-v1.5`)或 Ollama,记忆向量不再依赖外网 API
|
|
15
|
+
2. **Rerank 精排**:向量初筛后交叉编码精排,把 Top-K 准确率再抬一档
|
|
16
|
+
3. **autoDream 语义增强**:对记忆向量做聚类,让自动巩固(去重/合并/冲突裁决)从"主题相近"升级为"语义相近"
|
|
17
|
+
|
|
18
|
+
## 2. 总体架构
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ 语义引擎(v0.2,完全离线可选) │
|
|
23
|
+
├──────────────────────────────────────────────────────────────┤
|
|
24
|
+
│ ① Embedder 层(三选一,统一接口,可自动降级) │
|
|
25
|
+
│ LocalEmbedder(ONNX) │ OllamaEmbedder │ OpenAIEmbedder │
|
|
26
|
+
│ init() / embed(texts) / embedSingle() / dimension / hash │
|
|
27
|
+
├──────────────────────────────────────────────────────────────┤
|
|
28
|
+
│ ② VectorIndex 层 │
|
|
29
|
+
│ SQLite memories.embedding 列(JSON 向量) │
|
|
30
|
+
│ + 余弦相似度检索(score 0..1)+ 缺失向量增量补建 reindex │
|
|
31
|
+
├──────────────────────────────────────────────────────────────┤
|
|
32
|
+
│ ③ Rerank 层(可选) │
|
|
33
|
+
│ LocalReranker(Xenova/bge-reranker-base,交叉编码) │
|
|
34
|
+
│ 召回候选 → 精排 → Top-K │
|
|
35
|
+
├──────────────────────────────────────────────────────────────┤
|
|
36
|
+
│ ④ autoDream 聚类(可选) │
|
|
37
|
+
│ clusterMemories(向量 K-Means) / findPotentialConflicts │
|
|
38
|
+
│ 语义分组 + 疑似矛盾检测 → 巩固决策更精准 │
|
|
39
|
+
└──────────────────────────────────────────────────────────────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **① Embedder**:`src/local-embedder.js`,三后端统一接口,`createEmbedderByProvider(provider, opts)` 按名创建
|
|
43
|
+
- **② VectorIndex**:复用 v0.1 的 `memories.embedding` 列与 `store.js` 的余弦检索,无新存储层
|
|
44
|
+
- **③ Rerank**:`src/reranker.js`(Phase 2),可选开启
|
|
45
|
+
- **④ 聚类**:`src/dream/clustering.js`(Phase 3),autoDream 调度器集成
|
|
46
|
+
|
|
47
|
+
### 目录结构(新增/变化部分)
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/
|
|
51
|
+
├── local-embedder.js # [新增] Local/Ollama/OpenAI 三 Embedder + createEmbedderByProvider
|
|
52
|
+
├── reranker.js # [新增·Phase2] LocalReranker 交叉编码精排
|
|
53
|
+
├── dream/
|
|
54
|
+
│ └── clustering.js # [新增·Phase3] clusterMemories / findPotentialConflicts / kMeans
|
|
55
|
+
└── embedding.js # [保留] v0.1 OpenAI 兼容客户端(作为降级路径之一复用)
|
|
56
|
+
scripts/
|
|
57
|
+
├── benchmark-embed.js # [新增] Embedding 吞吐量基准
|
|
58
|
+
└── benchmark-rerank.js # [新增] Rerank 延迟基准
|
|
59
|
+
docs/
|
|
60
|
+
├── SEMANTIC.md # 本文档
|
|
61
|
+
├── LOCAL_MODEL.md # 本地模型部署指南
|
|
62
|
+
└── MIGRATION.md # 从 v0.1 升级说明
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 3. 搜索流水线
|
|
66
|
+
|
|
67
|
+
`memory_search` 在 v0.2 的完整链路(对应 `src/tools.js` 的 `mode: auto | keyword | vector | hybrid`):
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
query
|
|
71
|
+
│
|
|
72
|
+
┌───────────┴───────────┐
|
|
73
|
+
▼ ▼
|
|
74
|
+
keyword 召回 vector 召回
|
|
75
|
+
(LIKE 子串 title/ (Embedder.embed(query)
|
|
76
|
+
content/tags) → 余弦相似度 top-N
|
|
77
|
+
→ 缺失向量自动补建)
|
|
78
|
+
│ │
|
|
79
|
+
└───── 混合融合 ─────────┘
|
|
80
|
+
(auto: 关键词命中优先 + 向量补足;分数归一化合并)
|
|
81
|
+
│
|
|
82
|
+
▼
|
|
83
|
+
[Rerank 开启?]
|
|
84
|
+
├─ 是 → LocalReranker.rerank(query, candidates, topK)
|
|
85
|
+
│ 交叉编码精排,取 topK
|
|
86
|
+
└─ 否 → 按 importance/score 直接截取 topK
|
|
87
|
+
│
|
|
88
|
+
▼
|
|
89
|
+
memory_search 结果
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- **召回阶段(候选)**:混合召回,宁多勿漏,候选集通常取向量 top-N(默认远超 Top-K,供精排筛选)
|
|
93
|
+
- **精排阶段(Rerank)**:对候选逐条打分(query 与候选的交互编码),取 Top-K
|
|
94
|
+
- **结果阶段**:返回条目 + `score`(召回分/精排分)+ `source` 时间戳,与 v0.1 返回结构保持一致
|
|
95
|
+
|
|
96
|
+
## 4. 模块接口
|
|
97
|
+
|
|
98
|
+
### 4.1 Embedder 统一接口(`src/local-embedder.js`)
|
|
99
|
+
|
|
100
|
+
三个后端共享同一接口,方法失败**抛错**,由上层编排降级链(与 v0.1 `embedding.js` 的"失败返回 null"不同)。
|
|
101
|
+
|
|
102
|
+
| 成员 | 说明 |
|
|
103
|
+
|------|------|
|
|
104
|
+
| `init()` | 加载模型 / 探测服务,失败抛错 |
|
|
105
|
+
| `embed(texts: string[])` | 批量嵌入,返回 `number[][]`(本地模型 mean pooling + L2 归一化) |
|
|
106
|
+
| `embedSingle(text)` | 单条嵌入 → `number[]` |
|
|
107
|
+
| `dimension` | 向量维度(getter) |
|
|
108
|
+
| `modelHash` | 模型指纹(`<model>#<hash>`),用于向量索引一致性校验,模型换名/换维度时提示重建索引 |
|
|
109
|
+
| `dispose()` | 释放模型资源 |
|
|
110
|
+
|
|
111
|
+
**LocalEmbedder(ONNX)**
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
new LocalEmbedder({
|
|
115
|
+
model: "Xenova/bge-small-zh-v1.5", // 默认;中文优化,全离线
|
|
116
|
+
dimension: 512, // 默认与模型匹配
|
|
117
|
+
device: "cpu", // cpu | wasm | gpu(onnxruntime 后端)
|
|
118
|
+
batchSize: 8, // 分批嵌入,控制峰值内存
|
|
119
|
+
cacheDir: "", // 自定义模型缓存目录,空则用 HF 默认缓存
|
|
120
|
+
useDtype: "q8", // 量化精度 q8/fp32/fp16
|
|
121
|
+
logger: null
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**OllamaEmbedder**
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
new OllamaEmbedder({
|
|
129
|
+
baseUrl: "http://localhost:11434", // 默认
|
|
130
|
+
model: "nomic-embed-text", // 默认;维度从首次响应自动推断
|
|
131
|
+
logger: null
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**OpenAIEmbedder(兼容 v0.1 行为)**
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
new OpenAIEmbedder({
|
|
139
|
+
baseUrl: "", // 例如 https://api.openai.com/v1,也支持 SiliconFlow/智谱/Ollama 代理
|
|
140
|
+
apiKey: "",
|
|
141
|
+
model: "",
|
|
142
|
+
timeoutMs: 15000
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**工厂**
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
import { createEmbedderByProvider } from "../src/local-embedder.js";
|
|
150
|
+
const embedder = createEmbedderByProvider("local", { device: "cpu" }); // local | ollama | openai
|
|
151
|
+
await embedder.init();
|
|
152
|
+
const vecs = await embedder.embed(["你好", "记忆库"]);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 4.2 Reranker(`src/reranker.js`,Phase 2)
|
|
156
|
+
|
|
157
|
+
| 成员 | 说明 |
|
|
158
|
+
|------|------|
|
|
159
|
+
| `constructor({ model, device, batchSize, cacheDir, logger })` | `model` 默认 `Xenova/bge-reranker-base` |
|
|
160
|
+
| `init()` | 加载交叉编码模型 |
|
|
161
|
+
| `rerank(query, candidates, topK?)` | 对候选逐条打分,返回按相关性降序的候选(附 `score`),`topK` 缺省返回全量排序 |
|
|
162
|
+
|
|
163
|
+
示例:
|
|
164
|
+
|
|
165
|
+
```js
|
|
166
|
+
import { LocalReranker } from "../src/reranker.js";
|
|
167
|
+
const reranker = new LocalReranker({ model: "Xenova/bge-reranker-base" });
|
|
168
|
+
await reranker.init();
|
|
169
|
+
const top = await reranker.rerank("怎么部署本地模型", candidates, 5);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 4.3 autoDream 聚类(`src/dream/clustering.js`,Phase 3,已实现)
|
|
173
|
+
|
|
174
|
+
| 成员 | 说明 |
|
|
175
|
+
|------|------|
|
|
176
|
+
| `cosineSimilarity(a, b)` | 余弦相似度,零向量/长度不齐返回 0,数值稳定 |
|
|
177
|
+
| `kMeans(vectors, k, opts)` | K-Means(k-means++ 播种 + 空簇修复),返回每簇索引数组 |
|
|
178
|
+
| `clusterMemories(memories, vectors, k)` | 按向量把记忆分组,返回簇(保留原对象引用) |
|
|
179
|
+
| `findPotentialConflicts(memories, vectors, threshold = 0.85)` | 同类型、高相似(可能矛盾)的记忆对 |
|
|
180
|
+
|
|
181
|
+
autoDream 调度器在触发巩固时:对候选记忆做 `clusterMemories` 分组 → 每组送 LLM 决策(merge/archive/conflict),并用 `findPotentialConflicts` 预先标出疑似矛盾对,减少漏判。
|
|
182
|
+
|
|
183
|
+
## 5. 降级策略
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
首选 回退 1 回退 2 兜底
|
|
187
|
+
LocalEmbedder(ONNX) ──失败──► OllamaEmbedder ──失败──► OpenAIEmbedder ──失败/未配置──► 关键词 LIKE
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
- Embedder 方法**抛错**,由上层 `createEmbedderByProvider` 编排的降级链逐级捕获切换;全链路失败则回退到 v0.1 的 LIKE 子串关键词搜索(本库无 FTS5,关键词召回即 LIKE 子串扫描)
|
|
191
|
+
- **原则**:语义链路任何一环失败都**静默降级、不阻断记忆读写**——写入照常落库,只是当次不补向量
|
|
192
|
+
- **降级是每请求动态的**:同一会话内 ONNX 偶发失败,下一次请求自动重试首选路径,不固化到降级档
|
|
193
|
+
- **索引一致性**:Embedder 的 `modelHash` 变化(换模型/换维度)时,旧向量与新向量不可混算余弦,需触发「重建索引」(复用 v0.1 的 `reindexMissing` 补建缺失向量)
|
|
194
|
+
|
|
195
|
+
## 6. 模型清单
|
|
196
|
+
|
|
197
|
+
| 用途 | 模型 | 后端 | 维度 | 说明 |
|
|
198
|
+
|------|------|------|------|------|
|
|
199
|
+
| Embedding | `Xenova/bge-small-zh-v1.5` | ONNX(默认) | 512 | 中文优化,全离线,体积小 |
|
|
200
|
+
| Embedding | `bge-m3` | Ollama | 1024 | 多语言(中/英/日等),质量高 |
|
|
201
|
+
| Embedding | `nomic-embed-text` | Ollama | 768 | 英文为主,通用 |
|
|
202
|
+
| Embedding | `text-embedding-3-small` | OpenAI 兼容 | 1536 | v0.1 云端默认,升级后仍可用 |
|
|
203
|
+
| Rerank | `Xenova/bge-reranker-base` | ONNX(Phase 2) | — | 交叉编码,首候选精排 |
|
|
204
|
+
| Rerank | `bge-reranker-v2-m3` | 云/本地 | — | 更强精排,多语言 |
|
|
205
|
+
|
|
206
|
+
> 维度随模型而定:换用不同维度的模型后,已存向量与模型指纹不匹配,需重建索引。
|
|
207
|
+
|
|
208
|
+
## 7. 配置新增项
|
|
209
|
+
|
|
210
|
+
见 [MIGRATION.md](MIGRATION.md) 完整表格。核心:`embedProvider`(`local` / `ollama` / `openai`,默认 `openai` 保持 v0.1 行为)、`localEmbedModel`、`localEmbedDimension`、`localEmbedDevice`、`localEmbedBatchSize`、`ollamaBaseUrl`、`ollamaModel`、`embedModelCacheDir`、`rerankEnabled`、`rerankModel`。
|
|
211
|
+
|
|
212
|
+
## 8. 测试与基准
|
|
213
|
+
|
|
214
|
+
- 单元:`src/local-embedder.js` 三后端接口一致性、`clustering.js` 聚类正确性(node:test)
|
|
215
|
+
- 降级链:mock 各后端抛错,断言逐级回退到 LIKE
|
|
216
|
+
- 基准:`scripts/benchmark-embed.js`(嵌入延迟/吞吐)、`scripts/benchmark-rerank.js`(Rerank 延迟 vs 候选规模)
|
|
217
|
+
|
|
218
|
+
## 9. 反思更新(v0.2.1 `update` 决策 + 失败追踪)
|
|
219
|
+
|
|
220
|
+
### 9.1 `update` 决策类型
|
|
221
|
+
|
|
222
|
+
autoDream 整理时,LLM 可输出第 5 种决策 `update`,直接修正单条记忆的过时/错误内容:
|
|
223
|
+
|
|
224
|
+
- **约束**:`ids` 只能含一个 id;必须产生实际字段变化;不能更新 `summary`;新建 < 24h 的记忆不可 update
|
|
225
|
+
- **频率限制**:每次 autoDream 最多 `reflectionUpdateMaxPerRun`(默认 2)个 update,超限整单拒绝
|
|
226
|
+
- **幂等**:字段已与目标一致时跳过(可重放无副作用)
|
|
227
|
+
- **审计**:`dream_runs` 记录 update 的 `_before` 快照,可回溯变更前内容
|
|
228
|
+
- **向量同步**:update 后删除旧向量、按新内容重嵌入,保持索引一致
|
|
229
|
+
|
|
230
|
+
### 9.2 失败追踪(failure_memories)
|
|
231
|
+
|
|
232
|
+
`failure_memories` 表记录记忆纠正/失败事件,为后续反思进化积累数据:
|
|
233
|
+
|
|
234
|
+
```sql
|
|
235
|
+
CREATE TABLE failure_memories (
|
|
236
|
+
id TEXT PRIMARY KEY,
|
|
237
|
+
query TEXT, -- 用户原始查询/意图(可选)
|
|
238
|
+
expected TEXT, -- 期望结果
|
|
239
|
+
actual TEXT, -- 实际结果
|
|
240
|
+
failure_type TEXT, -- outdated / miss / wrong / user_correction
|
|
241
|
+
memory_id TEXT, -- 关联的记忆 id
|
|
242
|
+
created_at TEXT NOT NULL
|
|
243
|
+
);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- **触发**:用户调用 `memory_update` 且内容变化时,若 `reflectionFailureTracking` 开启则记录 `user_correction` 行(actual=旧值、expected=新值)
|
|
247
|
+
- **查询**:`store.listFailures`(过滤/分页)、`store.getFailureStats`(按类型计数)
|
|
248
|
+
|
|
249
|
+
### 9.3 配置项
|
|
250
|
+
|
|
251
|
+
```javascript
|
|
252
|
+
reflectionUpdateEnabled: true, // update 决策总开关
|
|
253
|
+
reflectionFailureTracking: true, // 失败追踪总开关
|
|
254
|
+
reflectionUpdateMaxPerRun: 2, // 每次整理最多 update 数
|
|
255
|
+
reflectionUpdateMinAgeHours: 24 // 新建记忆保护期(小时)
|
|
256
|
+
```
|
package/docs/SLEEP.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# dsh-mneme Sleep Mode 系统级睡眠设计文档
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
从被动整理到主动维护:在系统空闲时段自动执行深度记忆压缩、冲突消解、模式发现与关系补全,实现知识库的长效健康治理。
|
|
5
|
+
|
|
6
|
+
## 动机与设计原则
|
|
7
|
+
- **非侵入性**:仅在用户空闲(Idle)时触发,不干扰正常读写与实时推理。
|
|
8
|
+
- **可中断**:用户一旦产生活动,立即中止当前睡眠周期,保障实时体验。
|
|
9
|
+
- **分层压缩**:按访问热度与时间衰减进行阶梯式降级,保留核心语义,释放存储压力。
|
|
10
|
+
- **模式发现**:利用 LLM 挖掘长期记忆中的隐性规律,生成结构化 Pattern 实体。
|
|
11
|
+
- **审计延续**:与 `autoDream` 共享 `dream_runs` 审计表,通过 `run_type` 区分,保障全链路可追溯。
|
|
12
|
+
|
|
13
|
+
## 架构图
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart TD
|
|
16
|
+
User[用户写入/读取] -->|noteWrite| Scheduler[SleepScheduler]
|
|
17
|
+
Scheduler -->|clearTimeout + 重置计时器| Timer[Idle Timer]
|
|
18
|
+
Timer -->|>= sleepIdleMinutes| Check[maybeSchedule]
|
|
19
|
+
|
|
20
|
+
Check -->|条件满足| Queue[service.enqueue]
|
|
21
|
+
Check -->|条件不满足| Wait[等待下一次 noteWrite]
|
|
22
|
+
|
|
23
|
+
subgraph Sleep Run [串行执行]
|
|
24
|
+
Q[Queue] --> P1[Phase 1: conflict_resolution]
|
|
25
|
+
P1 --> P2[Phase 2: archival_demotion]
|
|
26
|
+
P2 --> P3[Phase 3: pattern_discovery]
|
|
27
|
+
P3 --> P4[Phase 4: relation_completion]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
P4 -->|独立 try/catch 隔离| Receipt[buildReceipt]
|
|
31
|
+
Receipt -->|saveDreamRun run_type='sleep'| Audit[(dream_runs)]
|
|
32
|
+
|
|
33
|
+
User -->|活动触发| Abort[AbortController.abort]
|
|
34
|
+
Abort -->|中断当前 Run| Scheduler
|
|
35
|
+
|
|
36
|
+
AutoDream[autoDream 实时轻量触发] -.共享队列不重叠.-> Queue
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 配置项说明
|
|
40
|
+
| 配置项 | 默认值 | 说明 |
|
|
41
|
+
|:---|:---|:---|
|
|
42
|
+
| `sleepModeEnabled` | `false` | 全局开关(Opt-in),开启后激活调度器 |
|
|
43
|
+
| `sleepIdleMinutes` | `5` | 连续无操作触发睡眠的阈值(分钟) |
|
|
44
|
+
| `sleepMinIntervalHours` | `8` | 两次 Sleep Run 的最小时间间隔(小时) |
|
|
45
|
+
| `sleepConflictStrictness` | `'normal'` | 冲突消解严格度:`'gentle'`(0.92) / `'normal'`(0.85) / `'aggressive'`(0.75) |
|
|
46
|
+
| `sleepArchiveDays` | `30` | 进入冷存储/摘要降级的天数阈值 |
|
|
47
|
+
| `sleepCompressDays` | `90` | 进入完全归档(Archive)的天数阈值 |
|
|
48
|
+
| `sleepPatternMinMemories` | `100` | 模式发现阶段扫描的最近记忆条数下限 |
|
|
49
|
+
| `sleepPatternLookbackDays` | `30` | 模式发现回溯的时间窗口(天) |
|
|
50
|
+
| `sleepMaxPatternPerRun` | `3` | 单次运行最多创建的模式数量上限 |
|
|
51
|
+
| `sleepProvider` | `''` | 睡眠模式专用 LLM Provider(留空则复用默认) |
|
|
52
|
+
| `sleepModel` | `''` | 睡眠模式专用 LLM Model(留空则复用默认) |
|
|
53
|
+
| `sleepReasoningEffort` | (未配置) | 睡眠 LLM 推理强度透传:`off` / `low` / `medium` / `high` / `none`。未配置 = 自动取模型支持的最低档(#135:避免思考模型用自带默认档烧光预算返回空体);显式 `none` = 不传该字段,使用模型默认(与 `dreamReasoningEffort` 语义一致) |
|
|
54
|
+
| `sleepMaxTokens` | `8192` | 冲突消解与模式发现阶段的 LLM 输出预算上限(token,#257:原硬编码 2048——`sleepActionSet: full` 实测 24 对需约 7000,截断即整轮裁决失败)。流式计费按实际用量,调大不增加成本 |
|
|
55
|
+
|
|
56
|
+
## 四阶段详解
|
|
57
|
+
每个阶段独立包裹 `try/catch`,任一阶段失败仅跳过该阶段,不阻塞后续流程。
|
|
58
|
+
|
|
59
|
+
### 1. `conflict_resolution`(冲突消解)
|
|
60
|
+
- **目标**:清理向量空间中的重复、矛盾或过时记忆。
|
|
61
|
+
- **输入**:全量/增量记忆向量索引、`sleepConflictStrictness` 阈值。
|
|
62
|
+
- **算法**:
|
|
63
|
+
1. 向量检索 `findPotentialConflicts` 获取高相似度对。
|
|
64
|
+
2. LLM 仲裁:基于严格度阈值生成 `keep/merge/discard` 决策。
|
|
65
|
+
3. `validateDecisions`:严格校验决策合法性,缺省策略为 `keep`。
|
|
66
|
+
4. `applyDecisions`:批量执行状态变更。
|
|
67
|
+
- **输出**:冲突消解报告(决策明细)。
|
|
68
|
+
- **验收**:无合法记忆被误删;仲裁结果符合配置的严格度阈值;决策可回滚。
|
|
69
|
+
|
|
70
|
+
### 2. `archival_demotion`(归档降级)
|
|
71
|
+
- **目标**:按时间衰减对记忆进行分层压缩,释放热存储压力。
|
|
72
|
+
- **输入**:`last_accessed_at` 排序的记忆列表、`sleepArchiveDays`、`sleepCompressDays`。
|
|
73
|
+
- **算法**(纯规则、无 LLM,确定性执行):
|
|
74
|
+
- `>= sleepCompressDays`:调用 `setArchived` 完全归档。
|
|
75
|
+
- `>= sleepArchiveDays && < sleepCompressDays`:调用 `demoteToSummary(m.id, 摘要, {minRefTimeMs: archiveCut})` 转为摘要态——原内容存入 `_full_content`,摘要由前 120 字截断生成(LLM 未配置时的 fallback)。
|
|
76
|
+
- `< sleepArchiveDays`:保持原状不动。
|
|
77
|
+
- **minRefTimeMs 关键点**:传入快照时刻 `archiveCut`,`demoteToSummary` 在事务内复查 `last_accessed_at`——快照后被召回 touch 的记忆视为"重新活跃",跳过不降级。
|
|
78
|
+
- **输出**:降级操作清单与状态快照。
|
|
79
|
+
- **验收**:严格遵循分层阈值;`minRefTimeMs` 机制生效,防止快照后 `touch` 导致的误降级;摘要保留 `_full_content` 可无损恢复。
|
|
80
|
+
|
|
81
|
+
### 3. `pattern_discovery`(模式发现)
|
|
82
|
+
- **目标**:从近期记忆中提炼可复用的隐性规律或知识模式。
|
|
83
|
+
- **输入**:最近 `sleepPatternLookbackDays` 内的 `sleepPatternMinMemories` 条记忆。
|
|
84
|
+
- **算法**:
|
|
85
|
+
1. LLM 扫描分析,提取候选模式。
|
|
86
|
+
2. **证据校验**:强制 `evidence` 列表与数据库真实 ID 求交集(`intersect`),过滤伪造/幻觉引用。
|
|
87
|
+
3. 输出 `type=pattern` 创建指令,受 `sleepMaxPatternPerRun` 限制。
|
|
88
|
+
- **输出**:新创建的 Pattern 实体及关联证据链。
|
|
89
|
+
- **验收**:无孤立证据;Pattern 创建数不超上限;证据 100% 可溯源。
|
|
90
|
+
|
|
91
|
+
### 4. `relation_completion`(关系补全)
|
|
92
|
+
- **目标**:修复知识图谱中的断链与孤立节点。
|
|
93
|
+
- **输入**:`listEntities` 获取的实体列表、关系图谱。
|
|
94
|
+
- **算法**:
|
|
95
|
+
1. 筛选 `getRelations` 返回为空的孤立实体。
|
|
96
|
+
2. 基于上下文推断关系:
|
|
97
|
+
- 语义/实体共现 → `related_to`
|
|
98
|
+
- 同属项目/模块 → `part_of`
|
|
99
|
+
- 技术栈/流程依赖 → `depends_on`
|
|
100
|
+
3. 批量写入关系边。
|
|
101
|
+
- **输出**:新增关系边列表。
|
|
102
|
+
- **验收**:关系推断符合领域常识;不引入循环依赖;图谱连通性提升。
|
|
103
|
+
|
|
104
|
+
## 分层压缩策略表
|
|
105
|
+
| 记忆层级 | 判定条件 (`last_accessed_at`) | 处理策略 | 存储形态 |
|
|
106
|
+
|:---|:---|:---|:---|
|
|
107
|
+
| **活跃 (Active)** | `< sleepArchiveDays` | 保持原状,正常搜索可见 | 全文索引 + 向量 |
|
|
108
|
+
| **冷 (Cold)** | `>= sleepArchiveDays` 且 `< sleepCompressDays` | `demoteToSummary` 压成摘要,原文进 `_full_content` | 摘要 + `_full_content` 分离 |
|
|
109
|
+
| **睡眠 (Archived)** | `>= sleepCompressDays` | `setArchived` 移出热查询域 | 仅保留元数据与归档标记 |
|
|
110
|
+
|
|
111
|
+
## 调度器与可中断机制
|
|
112
|
+
- **`noteWrite()` 心跳重置**:每次用户写入/读取记忆时触发。必须先 `clearTimeout` 清除旧计时器,再重新设置 Idle Timer,彻底杜绝 Stale Timer 导致的误触发。
|
|
113
|
+
- **`maybeSchedule()` 准入控制**:串行检查 `sleepModeEnabled` → 空闲时长 `>= sleepIdleMinutes` → 距上次运行 `>= sleepMinIntervalHours` → 状态非 `running/disposed`。任一不满足则放弃调度。
|
|
114
|
+
- **`service.enqueue` 串行队列**:所有 Sleep Run 必须进入全局串行队列执行,与 `autoDream` 严格互斥,避免并发写入冲突与资源争抢。
|
|
115
|
+
- **`AbortController` 可中断**:调度器持有 AbortController 实例。一旦检测到用户活动(`noteWrite` 或显式交互),立即调用 `abort()` 中断当前 `runSleep` 的执行上下文,保证实时性优先。
|
|
116
|
+
|
|
117
|
+
## 数据迁移与存储层变更
|
|
118
|
+
- **幂等迁移**:
|
|
119
|
+
- `memories` 表新增列:`last_accessed_at` (DATETIME)、`_full_content` (TEXT/BLOB)。
|
|
120
|
+
- `dream_runs` 表新增列:`run_type` (VARCHAR, 默认 'auto',用于区分 'sleep')。
|
|
121
|
+
- 迁移脚本需保证幂等(`IF NOT EXISTS` / 检查列是否存在)。
|
|
122
|
+
- **Store 新增方法**:
|
|
123
|
+
- `demoteToSummary(id, summary, opts)`:执行降级并记录 `minRefTimeMs`。
|
|
124
|
+
- `restoreContent(id)`:按需从归档/摘要恢复原文。
|
|
125
|
+
- `touchLastAccess(id)`:更新 `last_accessed_at` 至当前时间。
|
|
126
|
+
- `getUnrecalledSince(days)`:按时间窗口拉取未召回记忆。
|
|
127
|
+
- `listEntities()`:获取实体列表用于关系分析。
|
|
128
|
+
- **Service 扩展**:
|
|
129
|
+
- `enqueue(task)`:串行任务队列入口。
|
|
130
|
+
- `setSleepHook(fn)`:注入 `noteWrite` 钩子。
|
|
131
|
+
- `touchRecalled(id)`:在召回路径中自动更新访问时间。
|
|
132
|
+
|
|
133
|
+
## 与 autoDream 的协作关系
|
|
134
|
+
| 维度 | autoDream (实时) | Sleep Mode (系统级) |
|
|
135
|
+
|:---|:---|:---|
|
|
136
|
+
| **触发时机** | 写入阈值/实时事件 | 系统空闲 + 定时周期 |
|
|
137
|
+
| **执行深度** | 轻量级、局部关联补全 | 深度扫描、全局压缩与模式提炼 |
|
|
138
|
+
| **资源占用** | 低延迟、短时 | 允许较高延迟、长时运行 |
|
|
139
|
+
| **队列关系** | 共享 `service.enqueue` 串行队列 | 共享 `service.enqueue` 串行队列(严格互斥不重叠) |
|
|
140
|
+
| **审计记录** | 共用 `dream_runs`,`run_type='auto'` | 共用 `dream_runs`,`run_type='sleep'` |
|
|
141
|
+
|
|
142
|
+
## 实现要点与避坑(v0.4.1 教训)
|
|
143
|
+
1. **Stale Timer 防护**:`noteWrite()` 中必须严格遵循 `clearTimeout(oldTimer) -> setNewTimer()` 顺序,否则快速连续写入会累积多个 Timer 导致频繁误触发。
|
|
144
|
+
2. **`minRefTimeMs` 防误降级**:调用 `demoteToSummary` 时必须传入快照时刻 `{minRefTimeMs: archiveCut}`。否则在 Sleep Run 执行期间被用户 `touch` 的记忆会因时间窗口漂移被错误降级。
|
|
145
|
+
3. **证据强过滤**:Pattern Discovery 阶段 LLM 返回的 `evidence` 必须与 DB 真实 ID 列表求交集。未通过 `intersect` 校验的证据一律丢弃,严防幻觉伪造关联。
|
|
146
|
+
4. **严格串行化**:所有 Sleep Run **必须**走 `service.enqueue`。禁止直接调用异步函数启动,确保与 `autoDream` 绝对不重叠,避免写入锁冲突。
|
|
147
|
+
5. **Fail-Safe 阶段隔离**:四阶段必须各自独立 `try/catch`。LLM 服务抖动或网络异常时,仅跳过当前阶段(如跳过 Pattern 发现),后续阶段(如 Demotion、Relation)必须继续执行并记录 Receipt。
|
|
148
|
+
6. **无 LLM 降级路由**:当 `resolveSleepRoute` 返回 `undefined`(如 Provider 未配置或模型不可用)时,系统应自动跳过所有依赖 LLM 的阶段,但 `archival_demotion`(纯规则)仍需照常执行,保障基础压缩能力不丢失。
|
|
149
|
+
|
|
150
|
+
## 测试清单
|
|
151
|
+
- [ ] **调度器**:验证 `sleepIdleMinutes` 触发准确性;验证 `clearTimeout` 防 Stale;验证 `sleepMinIntervalHours` 冷却生效。
|
|
152
|
+
- [ ] **中断机制**:运行中触发 `noteWrite`,验证 `abort()` 立即终止 Run 且状态回滚至安全点。
|
|
153
|
+
- [ ] **阶段隔离**:Mock LLM 500 错误,验证仅对应阶段跳过,Receipt 仍生成且 `run_type='sleep'`。
|
|
154
|
+
- [ ] **降级策略**:构造不同 `last_accessed_at` 的记忆,验证 `minRefTimeMs` 防误降级逻辑及分层归档正确性。
|
|
155
|
+
- [ ] **模式发现**:注入含虚假 ID 的 LLM 响应,验证 `intersect` 过滤生效且创建数 `<= sleepMaxPatternPerRun`。
|
|
156
|
+
- [ ] **迁移幂等**:重复执行迁移脚本,验证无报错、无重复列/数据。
|
|
157
|
+
- [ ] **队列互斥**:并发触发 `autoDream` 与 Sleep,验证 `service.enqueue` 串行执行无重叠。
|
|
158
|
+
|
|
159
|
+
## 启用指南(Opt-in)
|
|
160
|
+
1. **配置开启**:在 `config.js` 或环境变量中设置 `sleepModeEnabled: true`。
|
|
161
|
+
2. **资源评估**:建议生产环境配置独立的 `sleepProvider` 与 `sleepModel`,避免与实时推理争抢额度/并发。
|
|
162
|
+
3. **初始运行**:首次启用后,系统将在首次空闲 `>= sleepIdleMinutes` 时触发全量扫描。可通过查看 `dream_runs` 表中 `run_type='sleep'` 的记录监控执行结果。
|
|
163
|
+
4. **调优建议**:初期可设置 `sleepConflictStrictness: 'gentle'`,观察 `conflict_resolution` 决策准确率后再逐步收紧至 `'normal'` 或 `'aggressive'`。
|
|
164
|
+
5. **关闭恢复**:设置 `sleepModeEnabled: false` 后,调度器将在 `dispose()` 阶段清理 Timer 并释放 Hook,已归档数据可通过 `restoreContent` 按需恢复。
|