@modusensus/dsh-mneme 0.8.13 → 0.8.15

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.
Files changed (271) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +655 -626
  3. package/assets/icon.svg +3 -3
  4. package/bin/cli.mjs +689 -689
  5. package/bin/dsh-mneme-mcp.mjs +510 -510
  6. package/bin/dsh-mneme-serve.mjs +151 -0
  7. package/cordis.patch.yml +21 -21
  8. package/docs/CONFIGURATION.md +263 -0
  9. package/docs/CONTINUITY.md +124 -0
  10. package/docs/DAEMON.md +46 -0
  11. package/docs/ENTITIES.md +245 -0
  12. package/docs/LOCAL_MODEL.md +222 -0
  13. package/docs/SEMANTIC.md +256 -0
  14. package/docs/SLEEP.md +164 -0
  15. package/docs/STORAGE.md +89 -0
  16. package/lib/api-standalone.js +673 -572
  17. package/lib/api.js +1437 -1433
  18. package/lib/bootstrap.js +211 -211
  19. package/lib/client.js +5444 -5295
  20. package/lib/commands.js +95 -95
  21. package/lib/config.js +880 -836
  22. package/lib/content-hash.js +54 -54
  23. package/lib/continuity.js +220 -220
  24. package/lib/document-index.js +94 -94
  25. package/lib/document.js +303 -303
  26. package/lib/dream/clustering.js +118 -118
  27. package/lib/dream/decisions.js +674 -674
  28. package/lib/dream/narratives.js +44 -44
  29. package/lib/dream/sleep.js +819 -819
  30. package/lib/dream.js +1501 -1501
  31. package/lib/embedding.js +177 -177
  32. package/lib/entities/extractor.js +252 -252
  33. package/lib/graph/anchoring.js +179 -179
  34. package/lib/guide.js +74 -74
  35. package/lib/heat.js +144 -144
  36. package/lib/hot-memory.js +46 -46
  37. package/lib/index.js +535 -671
  38. package/lib/inject.js +464 -450
  39. package/lib/lang.js +248 -229
  40. package/lib/local-embedder.js +358 -358
  41. package/lib/maintenance.js +142 -142
  42. package/lib/mirror.js +330 -330
  43. package/lib/organize.js +450 -450
  44. package/lib/peak-hours.js +117 -117
  45. package/lib/placement.js +13 -0
  46. package/lib/pre-inject-gate.js +340 -0
  47. package/lib/quality-filter.js +126 -126
  48. package/lib/recall-stats.js +175 -175
  49. package/lib/reranker.js +269 -269
  50. package/lib/runtime/adopt.js +176 -176
  51. package/lib/runtime/closure.js +172 -172
  52. package/lib/runtime/download.js +247 -247
  53. package/lib/runtime/layout.js +265 -265
  54. package/lib/runtime/loader.js +252 -252
  55. package/lib/runtime/model-fetch.js +361 -361
  56. package/lib/runtime/provision.js +237 -237
  57. package/lib/runtime/tarball.js +130 -130
  58. package/lib/runtime/verify.js +223 -204
  59. package/lib/scope.js +146 -146
  60. package/lib/search/adaptive.js +60 -60
  61. package/lib/search/bm25.js +96 -96
  62. package/lib/semantic.js +229 -0
  63. package/lib/sensitive-scan.js +209 -209
  64. package/lib/serve.js +215 -0
  65. package/lib/service.js +2663 -2596
  66. package/lib/settings.js +517 -509
  67. package/lib/store.js +3007 -3007
  68. package/lib/summarize.js +891 -856
  69. package/lib/tools.js +743 -737
  70. package/lib/vector-index.js +110 -110
  71. package/lib/version-check.js +122 -122
  72. package/lib/write-admission.js +402 -402
  73. package/locale/en.json +6 -6
  74. package/locale/zh.json +6 -6
  75. package/package.json +8 -4
  76. package/runtime-manifest.json +526 -526
  77. package/scripts/benchmark-embed.js +201 -201
  78. package/scripts/benchmark-recall.js +249 -249
  79. package/scripts/benchmark-rerank.js +166 -166
  80. package/scripts/build-runtime-manifest.mjs +224 -224
  81. package/scripts/changelog-prep.mjs +36 -0
  82. package/scripts/check-sync.js +42 -42
  83. package/scripts/e2e-dsh.js +220 -220
  84. package/scripts/mneme-runtime.mjs +277 -277
  85. package/scripts/stress-dsh.js +255 -255
  86. package/scripts/sync-lib.js +47 -47
  87. package/scripts/sync-test-badge.mjs +69 -69
  88. package/scripts/test-count-sync.mjs +44 -44
  89. package/src/api-standalone.js +673 -572
  90. package/src/api.js +1437 -1433
  91. package/src/bootstrap.js +211 -211
  92. package/src/commands.js +95 -95
  93. package/src/config.js +880 -836
  94. package/src/content-hash.js +54 -54
  95. package/src/continuity.js +220 -220
  96. package/src/document-index.js +94 -94
  97. package/src/document.js +303 -303
  98. package/src/dream/clustering.js +118 -118
  99. package/src/dream/decisions.js +674 -674
  100. package/src/dream/narratives.js +44 -44
  101. package/src/dream/sleep.js +819 -819
  102. package/src/dream.js +1501 -1501
  103. package/src/embedding.js +177 -177
  104. package/src/entities/extractor.js +252 -252
  105. package/src/graph/anchoring.js +179 -179
  106. package/src/guide.js +74 -74
  107. package/src/heat.js +144 -144
  108. package/src/hot-memory.js +46 -46
  109. package/src/index.js +535 -671
  110. package/src/inject.js +464 -450
  111. package/src/lang.js +248 -229
  112. package/src/local-embedder.js +358 -358
  113. package/src/maintenance.js +142 -142
  114. package/src/mirror.js +330 -330
  115. package/src/organize.js +450 -450
  116. package/src/peak-hours.js +117 -117
  117. package/src/placement.js +13 -0
  118. package/src/pre-inject-gate.js +340 -0
  119. package/src/quality-filter.js +126 -126
  120. package/src/recall-stats.js +175 -175
  121. package/src/reranker.js +269 -269
  122. package/src/runtime/adopt.js +176 -176
  123. package/src/runtime/closure.js +172 -172
  124. package/src/runtime/download.js +247 -247
  125. package/src/runtime/layout.js +265 -265
  126. package/src/runtime/loader.js +252 -252
  127. package/src/runtime/model-fetch.js +361 -361
  128. package/src/runtime/provision.js +237 -237
  129. package/src/runtime/tarball.js +130 -130
  130. package/src/runtime/verify.js +223 -204
  131. package/src/scope.js +146 -146
  132. package/src/search/adaptive.js +60 -60
  133. package/src/search/bm25.js +96 -96
  134. package/src/semantic.js +229 -0
  135. package/src/sensitive-scan.js +209 -209
  136. package/src/serve.js +215 -0
  137. package/src/service.js +2663 -2596
  138. package/src/settings.js +517 -509
  139. package/src/store.js +3007 -3007
  140. package/src/summarize.js +891 -856
  141. package/src/tools.js +743 -737
  142. package/src/vector-index.js +110 -110
  143. package/src/version-check.js +122 -122
  144. package/src/write-admission.js +402 -402
  145. package/test/adaptive-inject-budget.test.js +85 -85
  146. package/test/api-routes-gaps.test.js +218 -218
  147. package/test/api.test.js +1702 -1688
  148. package/test/audit.test.js +448 -448
  149. package/test/baseline-persist.test.js +248 -248
  150. package/test/benchmark.test.js +94 -94
  151. package/test/bootstrap.test.js +220 -220
  152. package/test/changelog-prep.test.js +60 -0
  153. package/test/client.test.js +1230 -1169
  154. package/test/clustering.test.js +100 -100
  155. package/test/commands.test.js +106 -106
  156. package/test/config.test.js +66 -66
  157. package/test/conflict-freeze.test.js +300 -300
  158. package/test/content-hash.test.js +204 -204
  159. package/test/continuity.test.js +333 -333
  160. package/test/document-dir.test.js +233 -233
  161. package/test/document.test.js +671 -671
  162. package/test/dream-archive-guardrail.test.js +116 -116
  163. package/test/dream-candidate-hybrid.test.js +281 -281
  164. package/test/dream-coverage-degrade.test.js +86 -86
  165. package/test/dream-failure-backoff.test.js +173 -173
  166. package/test/dream-peak-hours.test.js +194 -194
  167. package/test/dream.test.js +1385 -1383
  168. package/test/embedding-ready.test.js +196 -196
  169. package/test/entities.test.js +627 -547
  170. package/test/entity-recall.test.js +179 -179
  171. package/test/epistemic.test.js +325 -325
  172. package/test/fnew-0112.test.js +311 -311
  173. package/test/fnew-03.test.js +422 -422
  174. package/test/graph-anchoring.test.js +184 -184
  175. package/test/graph-api.test.js +175 -175
  176. package/test/graph-inject-budget.test.js +103 -103
  177. package/test/graph-passive-confirm.test.js +91 -91
  178. package/test/graph-weight-evolution.test.js +104 -104
  179. package/test/heat.test.js +175 -175
  180. package/test/helpers/dream-mock.js +82 -82
  181. package/test/helpers/peer-worker.mjs +25 -25
  182. package/test/helpers/runtime-source.js +60 -60
  183. package/test/helpers/tar-builder.js +80 -80
  184. package/test/helpers/write-admission-samples.js +308 -308
  185. package/test/hot-memory.test.js +145 -145
  186. package/test/inject-content-cap.test.js +91 -91
  187. package/test/inject-first-round-bm25.test.js +89 -89
  188. package/test/inject-heat.test.js +77 -61
  189. package/test/inject-parent-gate.test.js +106 -106
  190. package/test/inject-pools.test.js +194 -194
  191. package/test/inject-recall.test.js +105 -105
  192. package/test/inject-rotation.test.js +161 -161
  193. package/test/inject.test.js +420 -420
  194. package/test/lang.test.js +87 -87
  195. package/test/lastrunat-persist.test.js +101 -101
  196. package/test/lib-smoke.test.js +96 -96
  197. package/test/llm-audit.test.js +598 -598
  198. package/test/llm-message-source.test.js +207 -207
  199. package/test/local-embedder.test.js +292 -292
  200. package/test/maintenance.test.js +391 -391
  201. package/test/mcp-stdio.test.js +385 -385
  202. package/test/mirror-dirty.test.js +424 -424
  203. package/test/mirror-edit-digest.test.js +187 -187
  204. package/test/mirror-generation.test.js +499 -499
  205. package/test/mirror-types.test.js +208 -208
  206. package/test/mirror.test.js +249 -249
  207. package/test/narrative-bars.test.js +162 -162
  208. package/test/organize.test.js +418 -418
  209. package/test/peer-blockers.test.js +193 -193
  210. package/test/placement.test.js +36 -0
  211. package/test/policy-epoch.test.js +259 -259
  212. package/test/pre-inject-gate.test.js +434 -0
  213. package/test/quality-filter.test.js +186 -186
  214. package/test/reasoning-effort.test.js +752 -752
  215. package/test/recall-anti-update.test.js +82 -82
  216. package/test/recall-evals.test.js +253 -253
  217. package/test/recall-layer.test.js +326 -326
  218. package/test/recall-stats.test.js +189 -189
  219. package/test/receipt-chain.test.js +451 -451
  220. package/test/reflection.test.js +226 -226
  221. package/test/reindex-backfill.test.js +62 -62
  222. package/test/reranker-single-logit.test.js +73 -73
  223. package/test/reranker.test.js +197 -197
  224. package/test/resident-status.test.js +83 -83
  225. package/test/runtime-adopt.test.js +206 -206
  226. package/test/runtime-cli.test.js +89 -89
  227. package/test/runtime-closure.test.js +193 -193
  228. package/test/runtime-download.test.js +302 -302
  229. package/test/runtime-layout.test.js +163 -163
  230. package/test/runtime-loader.test.js +255 -255
  231. package/test/runtime-manifest.test.js +159 -159
  232. package/test/runtime-model-fetch.test.js +348 -348
  233. package/test/runtime-provision.test.js +101 -101
  234. package/test/runtime-tarball.test.js +81 -81
  235. package/test/runtime-verify.test.js +319 -279
  236. package/test/scope-candidates.test.js +239 -239
  237. package/test/scope-explicit.test.js +452 -452
  238. package/test/scope-inject-soft.test.js +106 -106
  239. package/test/scope-retrieval.test.js +197 -197
  240. package/test/scope-storage.test.js +255 -255
  241. package/test/scope-strict.test.js +367 -343
  242. package/test/search-fusion.test.js +90 -90
  243. package/test/semantic.test.js +124 -124
  244. package/test/sensitive-scan.test.js +207 -207
  245. package/test/serve-bin.test.js +121 -0
  246. package/test/serve.test.js +196 -0
  247. package/test/service-search.test.js +174 -174
  248. package/test/service.test.js +405 -405
  249. package/test/settings.test.js +261 -261
  250. package/test/skip-invalid-restore.test.js +262 -262
  251. package/test/sleep-action-set.test.js +422 -422
  252. package/test/sleep-heat.test.js +126 -126
  253. package/test/sleep-scheduler-rearm.test.js +89 -89
  254. package/test/sleep.test.js +527 -527
  255. package/test/standalone-api.test.js +751 -544
  256. package/test/store-vector-cache.test.js +98 -98
  257. package/test/store.test.js +396 -396
  258. package/test/stress.test.js +209 -209
  259. package/test/summarize-occurred-at.test.js +127 -0
  260. package/test/summarize-reasoning-effort.test.js +189 -189
  261. package/test/summarize.test.js +1465 -1465
  262. package/test/supersede-state-tracking.test.js +394 -0
  263. package/test/test-count-sync.test.js +60 -60
  264. package/test/tools-dto-schema-matrix.test.js +325 -325
  265. package/test/tools-memory-get-schema.test.js +65 -65
  266. package/test/tools.test.js +428 -374
  267. package/test/updated-at-semantics.test.js +113 -113
  268. package/test/vector-index.test.js +221 -221
  269. package/test/version-check.test.js +152 -152
  270. package/test/webServer-optional.test.js +82 -82
  271. package/test/write-admission.test.js +668 -668
package/docs/DAEMON.md ADDED
@@ -0,0 +1,46 @@
1
+ # dsh-mneme 独立服务(daemon)
2
+
3
+ `dsh-mneme-serve`:在 DSH 宿主之外把 mneme 跑成一个常驻数据面。#363(Mneme Bridge)确立的方向——第三方集成需要长期挂载,而 DSH 不必一直开着;这是官方推荐姿势,lib 直挂的 embedded 模式降级为无网兜底。
4
+
5
+ ## 1. 职责边界
6
+
7
+ daemon 是**数据面**,不是第二个宿主:
8
+
9
+ - **有**:存储(SQLite)、检索(关键词 + BM25 + 向量,`/search` 统一召回,与宿主共用 `src/semantic.js` 同一套装配)、注入候选(`/context`,issue #370,与宿主注入管线同一份 `injectCandidates`)、镜像同步与人改合并、`/maintenance/reclaim`、`/bootstrap`、recall_runs 检索/注入回执。
10
+ - **没有(第一期,无 LLM)**:巩固(autoDream)、蒸馏(autoSummarize)、实体抽取、sleep、注入/工具/面板路由。前两者是**结构性缺失**而非开关——daemon 装配里没有 LLM 句柄,巩固只属于 DSH 宿主进程。这就是 daemon 与宿主「单写者」的机械保证(AGENTS.md externalApi/autoDream 单侧纪律的 daemon 版),不依赖用户自觉。
11
+
12
+ 与宿主装配(`src/index.js` apply)的关系:`src/serve.js` 只搬数据面那一半,每步注释锚定 index.js 来源行号;刻意不抽公共装配函数(apply 其余环节与宿主 ctx 纠缠,防御段纪律「最后动或不动」)。装配漂移风险由 `test/serve-bin.test.js` 的多进程共存用例兜底(两进程真开同一个库互写互读)。
13
+
14
+ ## 2. 对外接口
15
+
16
+ 路由面 = `src/api-standalone.js` 全表(health/status/profile/rules/memories 读写/search/context/maintenance/bootstrap),鉴权同源(Bearer + timingSafeEqual,`GET /health` 免鉴权)。daemon 自身零新路由;#370 的 `/context` 落在 api-standalone 工厂里,宿主外部访问与 daemon 自动同享(参数、scope 语义与 recall_runs 注入分账见包 README 路由表)。daemon 侧唯一自有选项是 `strictPort`(见 §4)。
17
+
18
+ CLI:
19
+
20
+ ```bash
21
+ dsh-mneme-serve [--memory-dir <dir>] [--port <n>] [--host <addr>] [--embed <provider>]
22
+ ```
23
+
24
+ - `memoryDir`:CLI > env `DSH_MNEME_MEMORY_DIR` > `~/.dsh/memory`(与宿主 config.js 同默认,支持前导 `~`)。
25
+ - `--embed`:语义检索提供方。`local`(默认:自管 runtime 与嵌入模型缺失时**自动取件**,download 档,约 200MB;失败降级关键词并打可操作日志)| `ollama` | `openai`(读宿主面板存的 vector-config)| `off`(纯关键词 + BM25)。取件来源可用 env 换道:`DSH_MNEME_RUNTIME_DIR`(自管 runtime 目录)、`DSH_MNEME_RUNTIME_TARBALL_DIR`(离线 .tgz 目录,优先于联网)、`DSH_MNEME_RUNTIME_MIRROR`(registry 镜像前缀)。
26
+ - port/host 解析链与宿主「外部访问」一致:显式参数 > kv `external_api` 持久值 > 默认 8790 / 127.0.0.1。
27
+ - token 与 DSH 面板 / CLI **共用同一份**(kv `external_api`,首次启动自动生成并持久化)——三方零配置互通。
28
+ - 安全:daemon 使用明文 HTTP,不提供原生 TLS。指定非回环 `--host` 时,请勿直接把服务暴露给不可信网络;远程访问请走 TLS 终止代理或 SSH 隧道。
29
+ - stdout 只在就绪时打一行 `dsh-mneme-serve listening on http://host:port (pid N)`(机器可读,脚本/测试解析端口用);日志全走 stderr。
30
+ - SIGINT/SIGTERM 优雅收库后 exit 0;Windows 强杀由 WAL 回放兜底。
31
+
32
+ ## 3. 内部文件
33
+
34
+ - `src/serve.js` — `createServeRuntime({memoryDir, port, host, logger, strictPort, embed, embedder, reranker})`:装配链 createStore → createSettings → createMirror → createService(最小 config)→ recoverMirror → 人改镜像合并闭包 → vectorIndex + semantic → recall recorder → createMaintenance → createStandaloneApi(带 `embedder: embedder ?? semantic?.embedder ?? null`,供 `/context` 查询嵌入),每步锚定 index.js 行号。返回 `{api, store, service, settings, maintenance, semantic, tokenExisted, dispose}`;第三方可 import 它自行托管生命周期(bin 只是薄壳),`embedder/reranker` 参数供注入自管嵌入。
35
+ - `src/semantic.js` — embedder/reranker 装配 + boot 自动回填,**纯搬移自 index.js**(PR2),宿主与 daemon 共用同一份;`backfillMissingEmbeddings` 经 index.js barrel 再出口(测试照旧从 index.js import)。
36
+ - `bin/dsh-mneme-serve.mjs` — CLI 壳。独立成 bin 而非 cli.mjs 子命令:CONTRIBUTING 禁止给 cli.mjs 加 import;命名循 dsh-mneme-mcp 先例。
37
+ - `src/api-standalone.js` 的 `strictPort` 选项 — daemon 自己引入的唯一数据面改动,默认关闭(daemon 侧显式传 true)。#370 起该工厂另增 `/context` 路由与可选 `embedder` 注入,宿主外部访问同享。
38
+
39
+ ## 4. 已知坑
40
+
41
+ 1. **端口互斥,二选一**:daemon 与 DSH 的「外部访问」抢同一个默认端口。daemon 侧 strictPort 报错退出;反方向( daemon 先占 8790,DSH 后开外部访问)宿主侧会**静默顺延**到下一端口(宿主旁路的多实例恢复语义,api-standalone.js listenWithRetry)——面板显示的端口会变,别当 bug 报。
42
+ 2. **双进程写并发**:与 DSH 同时运行是设计内场景(WAL + busy_timeout 先序,store.js createStore)。但 `saveWithDedupe` 的 (type,title,scope) 去重是先查后写、库层无 UNIQUE 约束,两进程并发写同一三元组有极小概率产生重复条目——已知限制,勿当强保证宣传(要不要加 UNIQUE 索引属 schema 防御段,单独决策)。
43
+ 3. **镜像双写竞态**:daemon 与宿主都会渲镜像 .md;可再生物,失败由 recoverMirror 自愈,极端并发下单文件可能短暂脏,下次同步覆盖。
44
+ 4. **版本偏斜**:库迁移是幂等加法式(PRAGMA 检查 + ALTER),旧代码读新 schema 一般无碍,但该组合无人测过——daemon 与插件请同版本升级。
45
+ 5. **第一期不吃宿主配置**:daemon 不加载 config schema(schemastery 是宿主 peer 依赖),面板/feature_flags 对它不生效;它只有 CLI 参数 + 上述固定最小 config(`language: zh`、document 子系统关闭;语义键默认值逐键锚定 config.js,见 `src/serve.js` 的 `daemonSemanticCfg`)。`--embed local` 首次启动会自动取件 runtime + 模型(均有日志);取件/嵌入失败统一降级关键词 + BM25,不影响读写。
46
+ 6. **验收锚点**:`test/serve.test.js`(in-process 全链路 + token 复用 + recall_runs 回执 + 注入假 embedder 的向量轴锁)、`test/serve-bin.test.js`(真子进程 + 多进程互写互读)、`test/standalone-api.test.js` 的 strictPort 用例(busy → reject,默认路径仍顺延)、`test/reindex-backfill.test.js`(semantic 纯搬移后宿主行为不变)。
@@ -0,0 +1,245 @@
1
+ # 实体结构化记忆(Entity Gene,v0.3.0)
2
+
3
+ > dsh-mneme 的**记忆基因**层:从记忆文本里抽取**命名实体**、**带时间轴的属性**、**实体间关系**,并支持按实体/属性精确召回记忆。与 autoDream 联动——update 记录 supersedes、merge 迁移属性归属。
4
+
5
+ v0.3.0 四个阶段全部落地:
6
+
7
+ | 阶段 | 内容 |
8
+ |------|------|
9
+ | P1 | 三表 Schema(entities / entity_attrs / entity_relations)+ 索引 + store CRUD |
10
+ | P2 | LLM 抽取器(JSON 抽取 + resolveEntity 去重 + saveAttr 时间轴 + saveRelation + fail-safe) |
11
+ | P3 | 前缀搜索(`entity:` / `attr:`),attr 精确 = 1.0 > keyword 提及 = 0.7 |
12
+ | P4 | autoDream 联动(applyUpdate 写 supersedes 自引用;applyMerge 迁移 loser attrs) |
13
+
14
+ 存储层(建表 + CRUD)**始终可用**,不依赖任何 LLM;只有 `entityExtractionEnabled=true` 时流水线才会自动抽取。
15
+
16
+ ---
17
+
18
+ ## 一、三表结构
19
+
20
+ 所有建表语句在 `src/store.js` 的 `SCHEMA` 中,`CREATE TABLE IF NOT EXISTS` + `CREATE INDEX IF NOT EXISTS`,对旧库幂等。
21
+
22
+ ### `entities` — 命名实体
23
+
24
+ ```sql
25
+ CREATE TABLE IF NOT EXISTS entities (
26
+ id TEXT PRIMARY KEY,
27
+ name TEXT NOT NULL, -- 规范化名称(全名/主名)
28
+ type TEXT, -- person|project|concept|technology|organization
29
+ first_seen TEXT NOT NULL, -- 首次出现时间
30
+ last_seen TEXT NOT NULL, -- 最近出现时间
31
+ mention_count INTEGER DEFAULT 1, -- 提及次数(同名去重后递增)
32
+ canonical_memory_id TEXT -- 规范来源记忆(保留位)
33
+ );
34
+ CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
35
+ CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
36
+ ```
37
+
38
+ ### `entity_attrs` — 属性(快照式时间轴)
39
+
40
+ ```sql
41
+ CREATE TABLE IF NOT EXISTS entity_attrs (
42
+ id TEXT PRIMARY KEY,
43
+ entity_id TEXT NOT NULL,
44
+ attr_key TEXT NOT NULL,
45
+ attr_value TEXT NOT NULL,
46
+ memory_id TEXT, -- 属性出处记忆
47
+ valid_from TEXT NOT NULL,
48
+ valid_until TEXT, -- NULL = 当前有效
49
+ confidence REAL DEFAULT 1.0,
50
+ source TEXT
51
+ );
52
+ CREATE INDEX IF NOT EXISTS idx_attrs_entity ON entity_attrs(entity_id);
53
+ CREATE INDEX IF NOT EXISTS idx_attrs_key ON entity_attrs(attr_key);
54
+ CREATE INDEX IF NOT EXISTS idx_attrs_valid ON entity_attrs(valid_from, valid_until);
55
+ CREATE INDEX IF NOT EXISTS idx_attrs_memory ON entity_attrs(memory_id);
56
+ ```
57
+
58
+ ### `entity_relations` — 关系(追加式)
59
+
60
+ ```sql
61
+ CREATE TABLE IF NOT EXISTS entity_relations (
62
+ id TEXT PRIMARY KEY,
63
+ from_entity TEXT NOT NULL,
64
+ to_entity TEXT NOT NULL,
65
+ relation_type TEXT NOT NULL, -- uses|depends_on|part_of|related_to|supersedes
66
+ memory_id TEXT,
67
+ created_at TEXT NOT NULL,
68
+ metadata TEXT -- JSON 自由字段
69
+ );
70
+ CREATE INDEX IF NOT EXISTS idx_relations_from ON entity_relations(from_entity);
71
+ CREATE INDEX IF NOT EXISTS idx_relations_to ON entity_relations(to_entity);
72
+ CREATE INDEX IF NOT EXISTS idx_relations_type ON entity_relations(relation_type);
73
+ ```
74
+
75
+ ---
76
+
77
+ ## 二、valid_until 时间轴机制
78
+
79
+ 属性采用**快照式**写入:同一 `entity_id + attr_key` 只允许**一行当前有效**(`valid_until IS NULL`)。
80
+
81
+ ```
82
+ saveAttr(entity=X, key=role, value="前端工程师")
83
+ → 先把 X/role 的现有有效行 valid_until 置为 now(invalidateOldAttr)
84
+ → 再插入新行 valid_from=now, valid_until=NULL
85
+ ```
86
+
87
+ 读取语义:
88
+
89
+ - `getCurrentAttrs(entityId)` — 只返回有效行(`valid_until IS NULL`),即"当前值"
90
+ - `getAttrHistory(entityId)` — 按 valid_from 升序返回全部历史行(含已失效),用于追溯演变
91
+ - `findMemoriesByAttr(key, value)` — 返回携带该属性**当前有效**行的记忆(去重)
92
+ - **value 非空**:精确匹配 `attr_value`
93
+ - **value 为空 / undefined / null**:返回该 `attr_key` 的全部当前有效记忆("`attr:key` 无 `=value`"契约)
94
+
95
+ > ⚠️ 迁移时注意:同一 entity+key 连存两次会**失效前一行**(快照语义)。要保留多条同 key 的当前值,应把它们挂到**不同实体**上。
96
+
97
+ ---
98
+
99
+ ## 三、Store CRUD
100
+
101
+ `createStore` 暴露以下实体 API(`src/store.js`):
102
+
103
+ | 方法 | 说明 |
104
+ |------|------|
105
+ | `createEntity({ name, type })` | 建实体,`first_seen=last_seen=now`,`mention_count=1` |
106
+ | `findEntityByName(name)` | 按规范化名称查(去重锚点) |
107
+ | `findEntityById(id)` | 按 id 查 |
108
+ | `updateEntity(id, patch)` | 部分更新;默认 `mention_count+1`、刷新 `last_seen`;可用 `patch.mention_count` 显式覆盖 |
109
+ | `saveAttr({ entity_id, attr_key, attr_value, memory_id, confidence, source })` | 写属性(先失效旧行) |
110
+ | `invalidateOldAttr(entityId, attrKey, now)` | 手动失效;返回受影响行数 |
111
+ | `getCurrentAttrs(entityId)` | 当前有效属性列表 |
112
+ | `getAttrHistory(entityId)` | 全部属性历史(最旧在前) |
113
+ | `getAttrsByMemory(memoryId)` | 某记忆关联的全部属性(autoDream update 用) |
114
+ | `findMemoriesByAttr(key, value)` | 按属性召回记忆(value 空 = 该 key 全部当前有效) |
115
+ | `saveRelation({ from_entity, to_entity, relation_type, memory_id, metadata })` | 追加关系 |
116
+ | `getRelations(entityId)` | 某实体任一侧(from/to)的关系 |
117
+ | `migrateAttrsToMemory(fromId, toId, now)` | 把 from 记忆的属性 memory_id 改指 to(keeper 已有同 entity+key 当前行则失效);返回 `{ migrated, invalidated }` |
118
+
119
+ ---
120
+
121
+ ## 四、LLM 抽取器
122
+
123
+ `src/entities/extractor.js` → `extractEntities(memory, { store, config, callLLM })`。
124
+
125
+ ### 触发方式
126
+
127
+ `service.saveWithDedupe` 在 **created** 后 fire-and-forget 触发,仅当:
128
+
129
+ 1. `config.entityExtractionEnabled === true`
130
+ 2. service 已通过 `service.setEntityExtractor(...)` 注入抽取钩子(`index.js` 在插件启动时接好)
131
+
132
+ 抽取**异步执行**、失败绝不阻塞记忆写入(fail-safe)。
133
+
134
+ ### LLM JSON 契约
135
+
136
+ ```
137
+ {
138
+ "entities": [
139
+ {"name": "string", "type": "person|project|concept|technology|organization",
140
+ "attrs": [{"key": "string", "value": "string", "confidence": 0.9}]}
141
+ ],
142
+ "relations": [
143
+ {"from": "entityName", "to": "entityName", "type": "uses|depends_on|part_of|related_to"}
144
+ ]
145
+ }
146
+ ```
147
+
148
+ 抽取管线:
149
+
150
+ 1. `buildSystemPrompt` — 约束上限(`entityExtractionMaxEntities` / `entityExtractionMaxAttrs`)、禁编造、用规范化名
151
+ 2. `extractJsonFromText` — 先直接 `JSON.parse`,失败再抓首个 `{...}` 块
152
+ 3. `sanitizeExtractedData` — 非法 type → `concept`;非法 relation type → `related_to`;非法 confidence → 0.9;超限截断;relations 的 from/to 必须引用实体清单
153
+ 4. `resolveEntity` — **同名去重**:`findEntityByName` 命中 → `updateEntity(id, {})`(`mention_count+1`),未命中 → 建新实体
154
+ 5. 逐条 `saveAttr`(挂 `memory_id` + `source: "llm_extract"`)→ 逐条 `saveRelation`
155
+ 6. 单实体/属性/关系失败只 `skipCount++` 并告警,不中断整批
156
+
157
+ ### 返回 / fail-safe
158
+
159
+ ```js
160
+ { ok: true, entities: [{ name, type, attrs, entity_id }], attrs: [...], relations: [...], skipped: n }
161
+ { ok: false, error: "..." }
162
+ ```
163
+
164
+ - 缺失 content、LLM 返回空、JSON 解析失败、sanitize 抛错、`callLLM` reject → 一律 `{ ok: false }`,**绝不向上抛异常**
165
+ - `entityExtractionEnabled=false` 时钩子完全不触发,存储层照常可用
166
+
167
+ ---
168
+
169
+ ## 五、搜索:`entity:` / `attr:` 前缀
170
+
171
+ `service.searchMemories` 在 `config.entitySearchEnabled === true`(默认 true)时识别前缀路由:
172
+
173
+ ```
174
+ entity:阿尔托 → searchByEntity("阿尔托")
175
+ attr:国籍=芬兰 → searchByAttr("国籍", "芬兰") 精确匹配
176
+ attr:国籍 → searchByAttr("国籍", undefined) 该 key 全部当前有效
177
+ ```
178
+
179
+ ### searchByEntity 排序
180
+
181
+ **合并优先级:attr 精确关联 = 1.0 > 关键词提及 = 0.7**,attr 命中不覆盖、keyword 只补充召回,最终按 `_score` 降序取 topK:
182
+
183
+ | 来源 | 分数 |
184
+ |------|------|
185
+ | `entity_attrs.memory_id` 精确关联(当前有效) | `_score = 1.0`,`_source = "entity_attr"` |
186
+ | 标题/内容/标签含实体名 | `_score = 0.7`,`_source = "keyword"` |
187
+
188
+ `entitySearchEnabled=false` 时前缀不做路由,按普通关键词搜索(`entity:xxx` 字面串极少命中 → 通常返回空)。
189
+
190
+ ---
191
+
192
+ ## 六、autoDream 联动
193
+
194
+ `applyDecisions` 在 `config.entityExtractionEnabled === true` 时启用实体副作用,**仅记录、绝不阻断主流程**:
195
+
196
+ - **applyUpdate(supersedes)**:事务提交成功后,为该记忆关联的每条实体属性建立**自引用** supersedes 关系,表示"此属性版本已被替代":
197
+
198
+ ```js
199
+ saveRelation({ from_entity: attr.entity_id, to_entity: attr.entity_id,
200
+ relation_type: "supersedes", memory_id: id,
201
+ metadata: { attr_key, old_value } })
202
+ ```
203
+
204
+ - **applyMerge(属性迁移)**:将 loser 记忆关联的 `entity_attrs.memory_id` 迁移到 keeper;若 keeper 已有同 `entity+key` 的当前有效属性,则 loser 行被**失效**(keeper 值优先):
205
+
206
+ ```
207
+ { migrated, invalidated } = store.migrateAttrsToMemory(loserId, keeperId, now)
208
+ ```
209
+
210
+ - `entityExtractionEnabled=false` 时两条副作用**完全跳过**(update/merge 本身照常生效)。
211
+
212
+ ---
213
+
214
+ ## 七、配置项
215
+
216
+ | 键 | 默认值 | 说明 |
217
+ |----|--------|------|
218
+ | `entityExtractionEnabled` | `false` | 实体抽取总开关(默认关;存储层恒可用) |
219
+ | `entityExtractionModel` | `""` | 抽取专用模型(空 = 用 agent 默认模型) |
220
+ | `entityExtractionMaxEntities` | `10` | 每次抽取实体数上限 |
221
+ | `entityExtractionMaxAttrs` | `20` | 每实体属性数上限 |
222
+ | `entitySearchEnabled` | `true` | `entity:` / `attr:` 前缀搜索开关 |
223
+
224
+ ```yaml
225
+ # ~/.dsh/profiles/web/cordis.patch.yml
226
+ - id: dsh-mneme
227
+ config:
228
+ entityExtractionEnabled: true
229
+ entityExtractionModel: deepseek-chat # 可选,默认用 agent 默认模型
230
+ entitySearchEnabled: true
231
+ ```
232
+
233
+ ---
234
+
235
+ ## 八、迁移说明
236
+
237
+ - 实体三表在 `createStore` 的 `SCHEMA` 中 `CREATE TABLE IF NOT EXISTS` 创建,**旧库打开自动建表**,幂等(重复打开/重启无副作用,`test/entities.test.js` 有覆盖)。
238
+ - 无需人工迁移数据;现有 `memories` 表不变,实体表是**旁挂**结构。
239
+ - 抽取开启后,新写入的记忆自动补实体;存量记忆可通过触发抽取或后续 backfill 补全(v0.3.0 未内置存量回扫)。
240
+
241
+ ---
242
+
243
+ ## 测试
244
+
245
+ `test/entities.test.js`(31 条,node:test):Schema 三表 + 索引 + 旧库幂等迁移 / 实体 CRUD / 属性时间轴(saveAttr 失效旧行、getCurrentAttrs / getAttrHistory)/ `findMemoriesByAttr`(精确匹配 + **空 value 契约** + 过期行排除)/ 抽取器(mock LLM 写入、同名去重、垃圾 JSON / LLM 故障 fail-safe、开关触发)/ 前缀搜索(1.0 > 0.7 排序、attr 精确、attr 空值、开关关闭不路由)/ autoDream(supersedes、merge 迁移与失效、开关关闭跳过)/ fail-safe(抽取失败不阻塞写入)。
@@ -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)