@modusensus/dsh-mneme 0.7.1 → 0.7.2

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 (145) hide show
  1. package/README.md +415 -152
  2. package/{dsh-mneme/lib → lib}/client.js +55 -8
  3. package/{dsh-mneme/src → lib}/config.js +5 -0
  4. package/{dsh-mneme/lib → lib}/inject.js +31 -2
  5. package/package.json +40 -18
  6. package/{dsh-mneme/lib → src}/config.js +5 -0
  7. package/{dsh-mneme/src → src}/inject.js +31 -2
  8. package/{dsh-mneme/test → test}/client.test.js +37 -0
  9. package/{dsh-mneme/test → test}/config.test.js +7 -0
  10. package/{dsh-mneme/test → test}/inject.test.js +28 -0
  11. package/.github/workflows/test.yml +0 -32
  12. package/CHANGELOG.md +0 -89
  13. package/CONTRIBUTING.md +0 -245
  14. package/SECURITY.md +0 -544
  15. package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
  16. package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
  17. package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
  18. package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
  19. package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
  20. package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
  21. package/dsh-mneme/CHANGELOG.md +0 -295
  22. package/dsh-mneme/LICENSE +0 -21
  23. package/dsh-mneme/README.md +0 -484
  24. package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
  25. package/dsh-mneme/docs/ENTITIES.md +0 -245
  26. package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
  27. package/dsh-mneme/docs/MIGRATION.md +0 -127
  28. package/dsh-mneme/docs/SEMANTIC.md +0 -256
  29. package/dsh-mneme/docs/SLEEP.md +0 -163
  30. package/dsh-mneme/package-lock.json +0 -1936
  31. package/dsh-mneme/package.json +0 -80
  32. package/v0.7-plan-2026-08-21.md +0 -75
  33. package//346/250/252/345/271/205.png +0 -0
  34. /package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +0 -0
  35. /package/{dsh-mneme/lib → lib}/api.js +0 -0
  36. /package/{dsh-mneme/lib → lib}/commands.js +0 -0
  37. /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
  38. /package/{dsh-mneme/lib → lib}/dream/decisions.js +0 -0
  39. /package/{dsh-mneme/lib → lib}/dream/sleep.js +0 -0
  40. /package/{dsh-mneme/lib → lib}/dream/tag-extractor.js +0 -0
  41. /package/{dsh-mneme/lib → lib}/dream.js +0 -0
  42. /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
  43. /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
  44. /package/{dsh-mneme/lib → lib}/heat.js +0 -0
  45. /package/{dsh-mneme/lib → lib}/hot-memory.js +0 -0
  46. /package/{dsh-mneme/lib → lib}/index.js +0 -0
  47. /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
  48. /package/{dsh-mneme/lib → lib}/mirror.js +0 -0
  49. /package/{dsh-mneme/lib → lib}/parser/tag.js +0 -0
  50. /package/{dsh-mneme/lib → lib}/parser/wiki-link.js +0 -0
  51. /package/{dsh-mneme/lib → lib}/quality-filter.js +0 -0
  52. /package/{dsh-mneme/lib → lib}/reranker.js +0 -0
  53. /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
  54. /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
  55. /package/{dsh-mneme/lib → lib}/search/tag-boost.js +0 -0
  56. /package/{dsh-mneme/lib → lib}/service.js +0 -0
  57. /package/{dsh-mneme/lib → lib}/settings.js +0 -0
  58. /package/{dsh-mneme/lib → lib}/store.js +0 -0
  59. /package/{dsh-mneme/lib → lib}/summarize.js +0 -0
  60. /package/{dsh-mneme/lib → lib}/tools.js +0 -0
  61. /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
  62. /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
  63. /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
  64. /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
  65. /package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +0 -0
  66. /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
  67. /package/{dsh-mneme/scripts → scripts}/sync-lib.js +0 -0
  68. /package/{dsh-mneme/src → src}/api.js +0 -0
  69. /package/{dsh-mneme/src → src}/commands.js +0 -0
  70. /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
  71. /package/{dsh-mneme/src → src}/dream/decisions.js +0 -0
  72. /package/{dsh-mneme/src → src}/dream/sleep.js +0 -0
  73. /package/{dsh-mneme/src → src}/dream/tag-extractor.js +0 -0
  74. /package/{dsh-mneme/src → src}/dream.js +0 -0
  75. /package/{dsh-mneme/src → src}/embedding.js +0 -0
  76. /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
  77. /package/{dsh-mneme/src → src}/heat.js +0 -0
  78. /package/{dsh-mneme/src → src}/hot-memory.js +0 -0
  79. /package/{dsh-mneme/src → src}/index.js +0 -0
  80. /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
  81. /package/{dsh-mneme/src → src}/mirror.js +0 -0
  82. /package/{dsh-mneme/src → src}/parser/tag.js +0 -0
  83. /package/{dsh-mneme/src → src}/parser/wiki-link.js +0 -0
  84. /package/{dsh-mneme/src → src}/quality-filter.js +0 -0
  85. /package/{dsh-mneme/src → src}/reranker.js +0 -0
  86. /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
  87. /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
  88. /package/{dsh-mneme/src → src}/search/tag-boost.js +0 -0
  89. /package/{dsh-mneme/src → src}/service.js +0 -0
  90. /package/{dsh-mneme/src → src}/settings.js +0 -0
  91. /package/{dsh-mneme/src → src}/store.js +0 -0
  92. /package/{dsh-mneme/src → src}/summarize.js +0 -0
  93. /package/{dsh-mneme/src → src}/tools.js +0 -0
  94. /package/{dsh-mneme/src → src}/vector-index.js +0 -0
  95. /package/{dsh-mneme/test → test}/api.test.js +0 -0
  96. /package/{dsh-mneme/test → test}/audit.test.js +0 -0
  97. /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
  98. /package/{dsh-mneme/test → test}/boundary-v0625.test.js +0 -0
  99. /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
  100. /package/{dsh-mneme/test → test}/commands.test.js +0 -0
  101. /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
  102. /package/{dsh-mneme/test → test}/directory.test.js +0 -0
  103. /package/{dsh-mneme/test → test}/dream.test.js +0 -0
  104. /package/{dsh-mneme/test → test}/entities.test.js +0 -0
  105. /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
  106. /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
  107. /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
  108. /package/{dsh-mneme/test → test}/graph-api.test.js +0 -0
  109. /package/{dsh-mneme/test → test}/heat.test.js +0 -0
  110. /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
  111. /package/{dsh-mneme/test → test}/hot-memory.test.js +0 -0
  112. /package/{dsh-mneme/test → test}/llm-audit.test.js +0 -0
  113. /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
  114. /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
  115. /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
  116. /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
  117. /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
  118. /package/{dsh-mneme/test → test}/normalize-decisions.test.js +0 -0
  119. /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
  120. /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
  121. /package/{dsh-mneme/test → test}/provenance.test.js +0 -0
  122. /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
  123. /package/{dsh-mneme/test → test}/reasoning-effort.test.js +0 -0
  124. /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
  125. /package/{dsh-mneme/test → test}/recall-layer.test.js +0 -0
  126. /package/{dsh-mneme/test → test}/recall-runs.test.js +0 -0
  127. /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
  128. /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
  129. /package/{dsh-mneme/test → test}/reranker.test.js +0 -0
  130. /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
  131. /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
  132. /package/{dsh-mneme/test → test}/service-search.test.js +0 -0
  133. /package/{dsh-mneme/test → test}/service.test.js +0 -0
  134. /package/{dsh-mneme/test → test}/settings.test.js +0 -0
  135. /package/{dsh-mneme/test → test}/sleep-heat.test.js +0 -0
  136. /package/{dsh-mneme/test → test}/sleep.test.js +0 -0
  137. /package/{dsh-mneme/test → test}/store.test.js +0 -0
  138. /package/{dsh-mneme/test → test}/stress.test.js +0 -0
  139. /package/{dsh-mneme/test → test}/summarize.test.js +0 -0
  140. /package/{dsh-mneme/test → test}/tag-boost.test.js +0 -0
  141. /package/{dsh-mneme/test → test}/tag.test.js +0 -0
  142. /package/{dsh-mneme/test → test}/tools.test.js +0 -0
  143. /package/{dsh-mneme/test → test}/updated-at-semantics.test.js +0 -0
  144. /package/{dsh-mneme/test → test}/vector-index.test.js +0 -0
  145. /package/{dsh-mneme/test → test}/wiki-link.test.js +0 -0
@@ -1,183 +0,0 @@
1
- > 📌 **归档说明**:本文由社区用户 **[@heptaspirit](https://github.com/heptaspirit)** 调研整理,经作者同意归档为 dsh-mneme **v0.7.0 规划参考输入**。
2
- > 原始出处:[Discussion #21 — 兴趣漂移方向调研与思路分享](https://github.com/modusensus/dsh-mneme/discussions/21)
3
- > 归档日期:2026-08-20
4
-
5
- # dsh-mneme 记忆功能增强:调研笔记
6
-
7
- > 日期:2026-08-20
8
- >
9
- > 在调研兴趣漂移的相关算法过程中陆续找了一些 agent memory 相关的文章,对照 mneme 的源码和本地数据整理了一下,觉得有些信息可能对 v0.7 的规划有点用,就写在这里。
10
- >
11
- > 这只是一次调研的整理,不是设计提案。里面提到的方向、建议、取舍,都以作者自己的判断为准——尤其是 mneme 本地化、低成本的定位,我在整理时一直拿它当标尺,凡是需要重型依赖的基本都划掉了。
12
-
13
- ---
14
-
15
- ## 一、调研来源
16
-
17
- 主要看了两篇综述性的材料,对这些资料里提到的架构设计进行了一些研究,外加对 mneme v0.6.0 源码和本地 memory.db 的核对:
18
-
19
- 1. 阿里云存储团队的《一口气读完 Agent Memory 的 21 篇论文》——agent memory 领域的全景梳理,从 MemGPT 到 Titans。
20
- 2. AWS 的《Agent 记忆系统的工程实践与演进》——生产环境下记忆系统的工程问题,五个方向都偏"跑久了才暴露"的类型。
21
- 3. 本地的 mneme 数据:79 条记忆,entities / entity_attrs / entity_relations 三张图谱表全是 0 条,recall_runs 也是 0 条。
22
-
23
- ## 二、两篇文章里跟 mneme 相关的部分
24
-
25
- ### 2.1 21 篇论文:挑几个有对照意义的系统
26
-
27
- 论文很多,跟 mneme 有实际对照价值的大概这些:
28
-
29
- | 系统 | 核心机制 | mneme 的对应 | 差距 |
30
- | -------------- | ----------------- | -------------------------------------------------- | ------------------------------------------ |
31
- | MemGPT | 虚拟内存分层 + LLM 自主换页 | hot-memory + 长期库 + 注入 | 分层结构有了,缺"LLM 主动决定提升哪条记忆" |
32
- | Zep / Graphiti | 时序知识图谱、边失效机制 | entities + entity_attrs(已有 valid_from/valid_until) | 属性层有时间戳,关系层没有(见建议 8) |
33
- | Mem0 | LLM 判官:增删改 | autoDream 的 LLM 合并/冲突 | 已覆盖;Mem0 v3 为省成本转向 ADD-only,mneme 的批处理方向一致 |
34
- | MemOS | 文本↔激活↔参数转换 + 冷热判断 | demoteToSummary / archived / restore | 降级链完整,冷热判定正是 v0.7 heat 要补的 |
35
- | HippoRAG | 海马索引 + PPR 图传播 | entities + relations | 图传播太重,1-hop 足够 |
36
- | Voyager | 技能验证成功才入库 | pattern 类型 | pattern 可以"被引用过才升温"(远期) |
37
- | CoALA | 四类记忆划分 | hot-memory / history / preference / pattern | 可作 TYPE_DECAY 的语义框架 |
38
-
39
- 有一个点比较意外:mneme 的 entity_attrs 已经带 valid_from/valid_until(事实有效窗口),这是很多记忆系统都没有的,Graphiti 双时间轴的核心也就这个程度。属性层已经做了,关系层还没跟上。
40
-
41
- ### 2.2 AWS 工程实践:五个工程考量
42
-
43
- AWS 那篇讲的都是"选型时看不出来、跑半年才显形"的问题,对照下来:
44
-
45
- | 工程考量 | AWS 方案 | mneme 现状 | 备注 |
46
- | --------------- | ----------------------------------- | ------------------------------------- | --------------------------------------------- |
47
- | 写入纪律与失效机制 | 双 LLM 判官 / 六维打分 / workload-feedback | autoDream 合并 + sleep 降级 | 三类失效场景(低频但重要 / 时间新但语义旧 / 并存而非冲突)可写进 heat 模型约束 |
48
- | Prompt Cache 冲突 | 冻结快照 | 注入块在 system prompt | DSH 无显式 cachePoint,暂不可做 |
49
- | 跨模型容量 | 字符级上限 | 已是字符级(MAX_BLOCK) | 方向一致,不用动 |
50
- | Embedding 迁移 | 双写→回填→切换→归档 | 换 embedder 需重建向量 | v0.8+ 工程优化 |
51
- | Skill 治理 | 写入与治理分离 + 可逆审计 | pattern + receipt_chain + archived 不删 | mneme 已符合"治理可逆"原则 |
52
-
53
- AWS 文里 OpenClaw 的六维打分公式可以留作远期参考,其中四维在 mneme 的数据源上就能落地:
54
-
55
- | 维度 | 权重 | mneme 数据源 |
56
- | ------------------------- | ---- | ----------------- |
57
- | Relevance(召回后真被用上) | 0.30 | 需"引用检测",远期 |
58
- | Frequency(短期引用次数) | 0.24 | recall_runs |
59
- | Query diversity(被多少种问题触发) | 0.15 | recall_runs |
60
- | Recency(最近用过) | 0.15 | last_accessed_at |
61
- | Consolidation(连续多天用) | 0.10 | recall_runs |
62
- | Conceptual richness(概念密度) | 0.06 | tags + entity 关联数 |
63
-
64
- 不过 OpenClaw 的权重是黑盒、默认参数没法直接用,所以就算做也多信号也建议从单信号起步、公式留接口。
65
-
66
- ## 三、本地数据核对发现的两个前提问题
67
-
68
- 对照配置和调用链,发现图谱表和 recall_runs 都是 0 条的原因:
69
-
70
- - `entityExtractionEnabled` 默认关——每次写入调一次 LLM 抽实体,关掉是合理的成本决策;
71
- - `recordRecall` 是 opt-in 参数,默认不落库。
72
-
73
- 也就是说存储层、API、前端 ego graph 面板都就绪了,但数据源默认不积累。这影响的不只是图谱,也影响 v0.7 要做的"recall_runs 隐式反馈"——**如果保持默认关,就永远没有数据可用**。
74
-
75
- 所以建议里最前置的一条是:**记录默认开,消费默认关**。记录(recall 落库)成本接近零,先把数据攒起来;消费(用数据影响排序)保持默认关,行为不变。
76
-
77
- ## 四、可以做的方向(8 条,按依赖排序)
78
-
79
- | # | 方向 | 成本 | 依赖 |
80
- | - | -------------------------------- | -- | -- |
81
- | 1 | recall_runs 记录默认开、消费默认关 | 极低 | 无 |
82
- | 2 | recall_runs 记 `injected` 标记 | 低 | 1 |
83
- | 3 | heat 衰减 + TYPE_DECAY + 信号接口预留 | 中 | 无 |
84
- | 4 | sleep 降级联合判定(importance/type 免疫) | 低 | 3 |
85
- | 5 | 实体提取移入 sleep 批量阶段 | 低 | 无 |
86
- | 6 | sleep 批量实体消歧 | 中 | 5 |
87
- | 7 | 1-hop 邻域回流检索 + heat 投影实体层 | 中 | 5 |
88
- | 8 | 关系时效(valid_until + superseded) | 低 | 5 |
89
-
90
- ### 4.1 数据前提(建议 1-2)
91
-
92
- 记录默认开、消费默认关,前面说过了。补充一条:recall_runs 的 candidates 上打个 `injected` 标记,"被召回"和"被注入进上下文"是两个强度不同的信号,打上标记以后要区分就有数据了。另外建议按 90 天滚动清理,防止无限膨胀。
93
-
94
- ### 4.2 heat 模型(建议 3-4)
95
-
96
- v0.7 的方向在 #21 里已经定了(简化幂律 + type 差异化半衰期),这里只补充两点:
97
-
98
- - 公式留个信号接口(新列或 metadata),以后想加多信号是增量不是重构。
99
- - sleep 降级现在是 30/90 天硬阈值,不看 importance 不看 type,高价值的 preference 也会被降级。改成 `heat < 阈值 && importance < 5 && type 非免疫`,加两道保护,主体还是时间驱动。
100
-
101
- 另外一个小点:合并/更新刷新 updated_at 不应该算访问。如果将来排序用 updated_at 当新鲜度,会被 autoDream 的合并动作干扰——语义旧但时间戳新的记忆会显得很活跃。
102
-
103
- ### 4.3 图谱增强(建议 5-8)
104
-
105
- 图谱的存储、API、前端都好了,缺的是数据。所以这组建议第一步是让数据能攒起来,再考虑怎么用:
106
-
107
- 1. **实体提取移入 sleep 批量阶段**:写入时实时抽实体,LLM 成本落在写入路径上;挪到 sleep 里批量跑,写入路径零成本,只有开 sleep 的用户积累图谱。代价是图谱数据滞后一个 sleep 周期,对"长期关系网"来说无感。
108
- 2. **批量实体消歧**:extractor 目前明确不合并同义词,同一实体的全称/简称会变成多个孤立节点。sleep 批量消歧(仿 autoDream 的 winner/loser + 审计),或规则兜底(小写/去空格)。
109
- 3. **1-hop 邻域回流检索**:ego graph 的 BFS 代码在 api.js 里已经有了,检索侧直接复用——`entity:X` 顺带召回相邻实体的记忆。这是图谱相对纯向量检索的差异化价值,也是把图从"展示"变成"召回信号"的一步。
110
- 4. **heat 投影实体层**:实体热 = 关联记忆 heat 聚合,前端节点大小/明暗随热变化,兴趣漂移在图上可见。成本接近零,因为 heat 本来就是 v0.7 的改动。
111
- 5. **关系时效**:属性层有 valid_from/until,关系层补上 valid_until + superseded 标记(不删只标记,保留历史回溯),项目换方案后旧关系不会一直挂着。
112
-
113
- ## 五、暂时不做的方向
114
-
115
- 多数不是"明确不做",而是"现在不划算",作者如果觉得哪条值得可以捡起来。
116
-
117
- **硬约束(基本排除)**:
118
-
119
- | 方向 | 原因 |
120
- | ----- | -------------------- |
121
- | 多模态记忆 | 需要大型在线多模态模型,与本地化定位冲突 |
122
-
123
- **暂缓(条件成熟可重估)**:
124
-
125
- | 方向 | 暂缓原因 | 重估条件 |
126
- | ---------------------------- | --------------------- | --------------------------------- |
127
- | 参数化记忆(MemoryLLM/WISE/Titans) | 模型层改动,与插件定位互斥,另一条技术路线 | 基本放弃,除非 DSH 出模型层插件 |
128
- | 图数据库 / Graphiti | SQLite 递归 CTE 实测够用 | 实体十万级,或真要复杂模式匹配 |
129
- | PPR 图传播 / 社区子图 | 重型图算法,1-hop 够用 | 消歧做完 + 数据量上来 + sleep 预计算得分表 |
130
- | MemOS 预加载 / KV-Cache | KV-Cache 要 LLM 层接口 | "高 heat 记忆注入缓存预热"部分可做,等 heat 数据积累 |
131
- | MIRIX 多 Agent 分工 | 新架构范式,subagent 场景没到 | subagent 常态化后,#17 的 scope 之上加路由即可 |
132
- | Prompt cache 冻结快照 | DSH 无 cachePoint | 实测缓存收益 > 会话内新鲜度损失;可先做"慢变部分冻结"折中 |
133
-
134
- 关于图数据库:本地验证过 SQLite 3.50.4 的递归 CTE(`WITH RECURSIVE`)做多跳查询完全可用,所以"图数据多了怎么办"的答案大概率不是上 Neo4j/Graphiti(那等于另起一套 Python + 图服务),而是继续用 SQLite 的 CTE——几千节点规模 BFS 也就几十毫秒。
135
-
136
- ## 六、如果参与 RFC,可以用这 9 条
137
-
138
- 1. recall_runs 记录默认开、消费默认关(最优先)
139
- 2. recall_runs candidates 记 `injected` 标记
140
- 3. heat 公式预留信号接口(不做闭式)
141
- 4. sleep 降级联合判定(importance/type 免疫)
142
- 5. 实体提取移入 sleep 批量阶段
143
- 6. sleep 批量实体消歧
144
- 7. 1-hop 邻域回流检索 + heat 投影实体层
145
- 8. 关系时效补全
146
- 9. 远期:多信号 heat、pattern 验证升温、workload-feedback、Embedding 迁移
147
-
148
- ## 七、这些建议对应的现有机制
149
-
150
- | 建议 | 复用的现有机制 |
151
- | --- | ----------------------------------------------- |
152
- | 1-2 | recall_runs 表、touchRecalled |
153
- | 3-4 | TYPE_DECAY(#21 已采纳)、sleep 批处理、archived |
154
- | 5-6 | sleep 的 LLM 阶段模式、receipt_chain、findEntityByName |
155
- | 7 | api.js 的 ego graph BFS、entities 表 |
156
- | 8 | entity_attrs 的 valid_from/until 模式 |
157
-
158
- 整理时的底线是每条建议都能在 mneme 现有的机制上找到承接点,无需引入新依赖、新概念。
159
-
160
- ---
161
-
162
- ## 参考来源
163
-
164
- **近两年研究与经典:**
165
-
166
- 1. FadeMem:arXiv:2601.18642 → <https://arxiv.org/abs/2601.18642>
167
- 2. SSGM:arXiv:2603.11768 → <https://arxiv.org/abs/2603.11768>
168
- 3. TiMem:arXiv:2601.02845 → <https://arxiv.org/abs/2601.02845>
169
- 4. Adaptive Budgeted Forgetting:arXiv:2604.02280 → <https://arxiv.org/abs/2604.02280>
170
- 5. MaRS / Forgetful but Faithful:arXiv:2512.12856 → <https://arxiv.org/abs/2512.12856>
171
- 6. Generative Agents(Park et al., 2023):arXiv:2304.03442 → <https://arxiv.org/abs/2304.03442>
172
-
173
- **综述与工程:**
174
- 7\. Agent Memory Consolidation 综述(Zylos, 2026-06):<https://zylos.ai/research/2026-06-08-agent-memory-consolidation-selective-retention-forgetting>
175
- 8\. 概念漂移与推荐系统(ACM, 2025):<https://dlnext.acm.org/doi/10.1145/3707693>
176
- 9\. 《一口气读完 Agent Memory 的 21 篇论文》(阿里云存储团队):<https://github.com/adongwanai/AgentGuide/blob/main/resources/agent/papers/agent_memory/一口气读完agent%20memory的21篇核心论文.md>
177
- 10\. AWS《Agent 记忆系统的工程实践与演进》:<https://aws.amazon.com/cn/blogs/china/agent-system-engineering-practice/>
178
-
179
- **本项目相关:**
180
- 11\. Discussion #21(兴趣漂移调研与采纳):<https://github.com/modusensus/dsh-mneme/discussions/21>
181
- 12\. Issue #17(记忆可见性边界,已纳入 v0.7 roadmap):<https://github.com/modusensus/dsh-mneme/issues/17>
182
-
183
- > 注:1–5 的 arXiv 编号来自检索结果引用,若编号有出入以 arXiv 检索为准。
@@ -1,245 +0,0 @@
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(抽取失败不阻塞写入)。
@@ -1,141 +0,0 @@
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
- > 依赖:`@huggingface/transformers`(transformers.js)+ `onnxruntime-node` 已声明在插件 `package.json` 的 `dependencies`,无需额外安装。
17
-
18
- ## 2. 模型安装 / 下载
19
-
20
- ### 2.1 自动下载(默认,推荐)
21
-
22
- 首次使用本地 Embedder 时会**自动从 Hugging Face Hub 下载**对应模型到本地缓存,无需手动操作:
23
-
24
- ```bash
25
- node scripts/benchmark-embed.js --provider local --model Xenova/bge-small-zh-v1.5
26
- # 首次运行会显示下载进度 → 加载模型 → 输出基准表格
27
- ```
28
-
29
- 默认缓存目录:`~/.dsh/mneme/models`(用户级,重装/升级依赖不丢;`embedModelCacheDir` 留空时启用)。transformers.js 的 hub 模型存于其下 `hub/models--<org>--<name>`:
30
-
31
- - Linux/macOS:`~/.dsh/mneme/models/`
32
- - Windows:`%USERPROFILE%\.dsh\mneme\models\`
33
-
34
- ### 2.2 手动指定缓存目录
35
-
36
- 内存受限/离线机器可把模型缓存放到指定位置(迁移、共享缓存):
37
-
38
- ```js
39
- // 配置示例(插件配置里设置 embedModelCacheDir)
40
- const embedder = createEmbedderByProvider("local", {
41
- cacheDir: "/data/hf-models", // 模型缓存目录
42
- model: "Xenova/bge-small-zh-v1.5"
43
- });
44
- ```
45
-
46
- ### 2.3 Ollama 路径
47
-
48
- 如果本机已有 Ollama:
49
-
50
- ```bash
51
- ollama pull nomic-embed-text # 或 bge-m3(多语言)
52
- ```
53
-
54
- Ollama Embedder 走 `/api/embeddings`,**不下载任何文件到插件缓存**,模型由 Ollama 管理。
55
-
56
- ### 2.4 断点续传
57
-
58
- 下载基于 Hugging Face Hub 缓存协议,**中断后重试自动续传**(按分片断点续传,不重新下载已完成部分)。若反复中断导致缓存损坏,删除对应模型的缓存目录后重跑即可:
59
-
60
- ```bash
61
- rm -rf ~/.dsh/mneme/models/hub/models--Xenova--bge-small-zh-v1.5
62
- ```
63
-
64
- ## 3. 配置
65
-
66
- ### 3.1 插件配置(`~/.dsh/profiles/web/cordis.patch.yml`)
67
-
68
- ```yaml
69
- - id: dsh-mneme
70
- name: '@modusensus/dsh-mneme'
71
- config:
72
- # ── Embedding ──────────────────────────────
73
- embedProvider: local # local | ollama | openai(默认 openai,保持 v0.1 行为)
74
- localEmbedModel: Xenova/bge-small-zh-v1.5
75
- embedDimension: 512 # 与模型匹配的向量维度
76
- localEmbedDevice: cpu # cpu | wasm | gpu(仅 local 生效)
77
- embedBatchSize: 8 # 分批嵌入条数,内存紧张调小
78
- embedModelCacheDir: "" # 模型缓存目录(local 生效,空=用户级 ~/.dsh/mneme/models)
79
- embedBaseUrl: "" # openai/ollama 生效(ollama 默认 http://localhost:11434)
80
- embedApiKey: "" # openai 生效
81
- # ── Rerank(Phase 2,可选)─────────────────
82
- rerankEnabled: false # 开启向量召回后的精排
83
- rerankModel: Xenova/bge-reranker-base
84
- ```
85
-
86
- ### 3.2 环境变量(可选)
87
-
88
- | 变量 | 作用 |
89
- |------|------|
90
- | `HF_ENDPOINT` | Hugging Face 镜像源,例如 `https://hf-mirror.com`(国内加速) |
91
- | `HF_HUB_CACHE` | 覆盖默认缓存目录 |
92
-
93
- ### 3.3 各后端配置对照
94
-
95
- | 配置 | local (ONNX) | ollama | openai |
96
- |------|--------------|--------|--------|
97
- | `localEmbedModel` | HF 模型 id(如 `Xenova/bge-small-zh-v1.5`) | Ollama 模型名(如 `nomic-embed-text`) | API 模型名(如 `text-embedding-3-small`) |
98
- | `embedBaseUrl` | — | `http://localhost:11434`(默认) | OpenAI 兼容端点 |
99
- | `embedApiKey` | — | — | 必填 |
100
- | `localEmbedDevice` | ✅ | — | — |
101
- | `embedBatchSize` | ✅ | — | ✅ |
102
- | `embedModelCacheDir` | ✅ | — | — |
103
-
104
- ## 4. 首次使用
105
-
106
- 1. 配置 `embedProvider`(或保持 `openai` 不动)
107
- 2. 保存配置并重启 DSH,或直接跑 `scripts/benchmark-embed.js` 验证
108
- 3. 首次触发本地模型时,transformers.js 自动下载 → `init()` 打印 `local embedder ready: <model> (dim=512, device=cpu)`
109
- 4. 之后所有 `memory_save` 写入自动补向量;已有记忆可用「重建索引」批量补建
110
-
111
- > 注意:切换 `localEmbedModel` 或 `embedDimension` 后,旧向量与新向量维度不同、无法混算,需**重建索引**(复用 v0.1 的 `reindexMissing` 机制)。
112
-
113
- ## 5. 常见问题
114
-
115
- ### Q1:模型下载失败 / 下载超慢
116
-
117
- - 国内网络:设置镜像源 `HF_ENDPOINT=https://hf-mirror.com`(或 `export HF_ENDPOINT=https://hf-mirror.com`)
118
- - 公司代理拦截:检查 `HTTPS_PROXY` / `HTTP_PROXY` 环境变量
119
- - 磁盘不足:清理缓存或用 `embedModelCacheDir` 换目录
120
-
121
- ### Q2:`Xenova/bge-small-zh-v1.5` 加载报错 / 显存不足(OOM)
122
-
123
- - 默认 `device: cpu`、`dtype: q8`,内存占用已最小化
124
- - 尝试 `embedBatchSize` 调小(如 4/2)降低峰值内存
125
- - 显存不足:`device: gpu` 时改回 `cpu`;或检查 onnxruntime-node 是否安装了 GPU 后端
126
-
127
- ### Q3:中英文混排 / CJK 文本处理
128
-
129
- - `bge-small-zh-v1.5` 对中文与中英混排均优化良好;纯英文场景可换 Ollama 的 `nomic-embed-text`
130
- - 单条文本上限 8000 字符(与 v0.1 一致),超长自动截断
131
- - 检索时 query 与记忆正文走同一 Embedder、同一归一化(mean pooling + L2),余弦分数才可比
132
-
133
- ### Q4:换模型后结果变差 / 向量混乱
134
-
135
- - 换模型(尤其维度不同)后旧向量不可复用,必须重建索引
136
- - 若在 Web 面板/API 配置过 v0.1 的 `vector-config`,v0.2 会优先采用插件配置,旧配置仅作降级路径
137
-
138
- ### Q5:本地模型加载很慢(首次/每次启动)
139
-
140
- - 首次含下载,属正常;已缓存后每次启动仅加载进内存(数百 ms 级)
141
- - 大模型(如 bge-m3)可考虑换用更小的 `Xenova/bge-small-zh-v1.5`(量化 q8 约 100MB)