dsh-memory-tiers 0.8.1 → 0.8.3
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/ARCHITECTURE.md +420 -2460
- package/README.md +140 -268
- package/client.mjs +701 -608
- package/compaction.mjs +184 -184
- package/index.mjs +939 -826
- package/{memory5.mjs → memory.mjs} +5280 -3403
- package/package.json +61 -63
- package/settings.mjs +335 -327
- package/silicon.mjs +24 -22
- package/memory6.mjs +0 -1553
- package/reindex.mjs +0 -162
package/README.md
CHANGED
|
@@ -1,268 +1,140 @@
|
|
|
1
|
-
# dsh
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
##
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
| `preset` | `siliconflow` | `siliconflow` 用上面两个免费模型;`custom` 走 `models` 里自己填的 |
|
|
142
|
-
| `models.chat` | `{ source: "session" }` | 判关系用哪个模型。`session` = 跟当前会话 |
|
|
143
|
-
| `models.summary` | `{ source: "session" }` | 摘要用哪个模型 |
|
|
144
|
-
| `noThink.chat` | `true` | 判关系关思考(只要一个词,思考是浪费) |
|
|
145
|
-
| `noThink.summary` | `true` | 摘要关思考。**打开思考会更慢(实测慢 3 倍),但摘要可能更细** |
|
|
146
|
-
|
|
147
|
-
想换成别的 provider(DeepSeek 官方、OpenAI 兼容接口都行):
|
|
148
|
-
|
|
149
|
-
```json
|
|
150
|
-
{
|
|
151
|
-
"preset": "custom",
|
|
152
|
-
"models": {
|
|
153
|
-
"embed": { "base": "https://api.siliconflow.cn/v1", "apiKey": "sk-xxx", "model": "BAAI/bge-m3" },
|
|
154
|
-
"summary": { "base": "https://api.deepseek.com/v1", "apiKey": "sk-xxx", "model": "deepseek-chat" }
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
> 嵌入模型一旦换了,旧向量不可比较,插件会自动隔离旧向量并重建。**两个程序共用记忆时,嵌入模型必须一致。**
|
|
160
|
-
|
|
161
|
-
### 整理节奏
|
|
162
|
-
|
|
163
|
-
| 参数 | 默认 | 说明 |
|
|
164
|
-
|---|---|---|
|
|
165
|
-
| `quietMs` | 600000(10 分钟) | 一段对话安静这么久才允许整理,避免聊到一半被腰斩 |
|
|
166
|
-
| `organizeEveryMs` | 300000(5 分钟) | 定时补一轮整理(拿不到钥匙会自己跳过) |
|
|
167
|
-
| `refreshMinMs` | 1800000(30 分钟) | L3 摘要段的重算间隔。它在上下文最前面,变一次后面缓存全失效 |
|
|
168
|
-
|
|
169
|
-
### 两端共享一份记忆
|
|
170
|
-
|
|
171
|
-
两个程序指向同一个目录就是共享:
|
|
172
|
-
|
|
173
|
-
```json
|
|
174
|
-
// dsh 的 config.json
|
|
175
|
-
{ "layout": "split", "writer": "dsh" }
|
|
176
|
-
|
|
177
|
-
// astrbot 侧
|
|
178
|
-
{ "layout": "split", "writer": "astrbot" }
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
规则:
|
|
182
|
-
|
|
183
|
-
- 各自只写自己的 `L1.tmp/<writer>.jsonl`,注入时合并
|
|
184
|
-
- 谁先结束谁拿「钥匙」整理,同一时刻只有一个在整理
|
|
185
|
-
- 深层(L2/L3/DL1~3)只有一个写者,不会互相覆盖
|
|
186
|
-
|
|
187
|
-
**要求**:两端都用本插件、`writer` 不同、嵌入模型一致。旧数据不迁移,`layout: "legacy"` 可回退到老的单文件布局。
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## 怎么确认它在工作
|
|
192
|
-
|
|
193
|
-
1. dsh 启动日志里出现:`[memory-tiers] 布局 split · writer=dsh · 嵌入 api:BAAI/bge-m3`
|
|
194
|
-
2. 数据目录里出现 `L1.json`、`L1.tmp/`、`L2/L3.json`、`DL1/DL2/DL3.json`
|
|
195
|
-
3. 聊两轮后 `L1.tmp/dsh.jsonl` 里有内容,`organize.state.json` 有统计
|
|
196
|
-
4. 设置 → 记忆,能看到每层的条数和字数
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## 常见问题
|
|
201
|
-
|
|
202
|
-
**Q:token 没降下来?**
|
|
203
|
-
|
|
204
|
-
先确认三件事:`autoCompact` 是 `true`;`compactKeepTurns` 是 `0`;插件真的在跑(看启动日志)。
|
|
205
|
-
另外缓存是否命中也要看——上下文块**追加在末尾**,前缀不变才省得下来。
|
|
206
|
-
|
|
207
|
-
**Q:它好像没记住我说的话?**
|
|
208
|
-
|
|
209
|
-
- L1 只注入最新的若干条(按配额),更早的在 L2/DL 里
|
|
210
|
-
- 想让它"永远记得",说的时候带上明确的意愿("以后都…"),这类内容更容易被判为重要
|
|
211
|
-
- 检索是按需的:模型得先想到去搜,才会搜
|
|
212
|
-
|
|
213
|
-
**Q:搜索搜不到?**
|
|
214
|
-
|
|
215
|
-
`search` 有 `depth` 参数:`summary`(只搜摘要)/ `detail`(摘要 + 原文)/ `full`(+ 流水)。
|
|
216
|
-
默认走 `summary`,找不到时换 `full` 再试一次。
|
|
217
|
-
|
|
218
|
-
**Q:整理很慢,几十秒?**
|
|
219
|
-
|
|
220
|
-
正常。一次整理要调模型生成摘要,2000 条原文要约 40~140 秒(取决于输出多少)。
|
|
221
|
-
整理在后台跑,不阻塞对话;另一端的整理会等它放钥匙。
|
|
222
|
-
|
|
223
|
-
**Q:能删掉记忆吗?**
|
|
224
|
-
|
|
225
|
-
`mem.forget(id)` 会写台账并删除;也可以直接改/删数据目录里的 JSON。删之前先备份。
|
|
226
|
-
|
|
227
|
-
**Q:换机器怎么办?**
|
|
228
|
-
|
|
229
|
-
把整个数据目录拷过去,设好 `MEMORY_TIERS_DIR` 指过去即可(配置、记忆、向量都在里面)。
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## 数据与备份
|
|
234
|
-
|
|
235
|
-
```
|
|
236
|
-
<数据目录>/
|
|
237
|
-
L1.json L2.json L3.json DL1.json DL2.json DL3.json ← 六层
|
|
238
|
-
L1.tmp/<writer>.jsonl ← 各端待合并的当前轮
|
|
239
|
-
signals/<writer>.jsonl ← attend/pin/forget 请求
|
|
240
|
-
vectors/deep-part-0000.json ← 向量
|
|
241
|
-
graveyard/forgotten.jsonl ← 遗忘台账
|
|
242
|
-
config.json ← 配置
|
|
243
|
-
organize.lock ← 整理时的锁(正常情况下不存在)
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
备份就拷整个目录。想彻底重来,删掉目录、重启,会重新生成。
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## 开发者
|
|
251
|
-
|
|
252
|
-
> 下面这些脚本只在仓库里,npm 包里不含(`files` 字段只带运行时文件)。
|
|
253
|
-
|
|
254
|
-
```bash
|
|
255
|
-
node test-memory6.mjs # 六文件布局,66 项
|
|
256
|
-
node test-m5.mjs # legacy 回归,41 项
|
|
257
|
-
node test-batch.mjs # 批量写入,20 项
|
|
258
|
-
node test-share.mjs # 多写者分片,25 项
|
|
259
|
-
node sim-real.mjs # 真实模型仿真(九组机制,--dry 假模型不花钱)
|
|
260
|
-
node bench-scale.mjs sum # 摘要规模基准(会花真钱)
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
设计、实测数据、已知问题、待办:全部在 [`ARCHITECTURE.md`](./ARCHITECTURE.md)。
|
|
264
|
-
改代码前先读那一份,本文为了好读省略了大量约束。
|
|
265
|
-
|
|
266
|
-
## License
|
|
267
|
-
|
|
268
|
-
MIT
|
|
1
|
+
# 记忆分层 · dsh 端
|
|
2
|
+
|
|
3
|
+
让 dsh 记住你说过的话,忘掉无关的,聊再久也不多花钱。
|
|
4
|
+
|
|
5
|
+
和 astrbot 端的 `astrbot_plugin_dsh_memory` 是同一套协议的两端实现:**分层、整理、路由、注入块的语义完全一致**,
|
|
6
|
+
只有接入方式不同(这边走 dsh 的 `systemPrompt` 和工具,那边走 astrbot 的钩子和命令)。
|
|
7
|
+
两端指向同一个数据目录,就是**一份共享记忆**。
|
|
8
|
+
|
|
9
|
+
- 只有 dsh:也能用,自己抓取、自己整理、自己注入。
|
|
10
|
+
- 想两端共用:看下面「共用一份记忆」。
|
|
11
|
+
|
|
12
|
+
架构细节看 [`ARCHITECTURE.md`](ARCHITECTURE.md),改动记录看 [`CHANGELOG.md`](CHANGELOG.md)。
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
1. 用 dsh 的插件安装方式把本仓库装进去(当前是 link 方式:目录链到 dsh 的插件目录)。
|
|
17
|
+
加载器从**包根**读 `package.json` 与 `cordis.patch.yml`,所以根目录那几个文件必须在。
|
|
18
|
+
根目录那份是 `core/` 的副本,别手改。
|
|
19
|
+
2. 重启 dsh,看日志里有没有 `[memory-tiers] 已加载,注册 N 个工具`。
|
|
20
|
+
3. 打开「设置 → 记忆分层」,按需改配置,保存后重启生效。
|
|
21
|
+
|
|
22
|
+
数据目录默认 `~/.dsh/storages/memory-v5`(可用 `DSH_HOME` 改前缀)。
|
|
23
|
+
|
|
24
|
+
## 设置页
|
|
25
|
+
|
|
26
|
+
分组和默认值跟 astrbot 配置页一一对应。时间类配置一律是**分钟**(旧版的 `quietMs` /
|
|
27
|
+
`organizeEveryMs` / `refreshMinMs` / `semanticTtlMs` 会在读取时自动换算,不用手动改)。
|
|
28
|
+
|
|
29
|
+
### 基础(数据目录与写者)
|
|
30
|
+
|
|
31
|
+
| 项 | 默认 | 说明 |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| 数据目录 `dir` | `~/.dsh/storages/memory-v5` | 存在固定位置的指针文件 `~/.dsh/memory-tiers.json` 里 |
|
|
34
|
+
| 写者标识 `writer` | `default` | 决定 `L1.tmp/<writer>.jsonl`。共用目录时**每个程序必须不同** |
|
|
35
|
+
|
|
36
|
+
### 注入(每轮拼进上下文的量)
|
|
37
|
+
|
|
38
|
+
| 项 | 默认 | 说明 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| 注入总量上限 `bootMaxChars` | 0 | 0 = 不限,由下面三层容量决定 |
|
|
41
|
+
| 注入记忆 `inject` | 开 | 关掉 = 只写不读 |
|
|
42
|
+
| L1 即时层 `memoryCfg.hotMaxChars` | 3000 | 按时间保存的原文 |
|
|
43
|
+
| 自己那一端的配额 `memoryCfg.hotQuotaOwn` | 0.7 | L1 里先紧着自己写的占七成 |
|
|
44
|
+
| L2 巩固层 `memoryCfg.consolidateMaxChars` | 2500 | 较重要的原话 |
|
|
45
|
+
| L3 长期层 `memoryCfg.longMaxChars` | 3000 | 摘要视图,直接决定注入块大小 |
|
|
46
|
+
| L3 视图刷新间隔 `refreshMin` | 30 分钟 | 只有 L3 节流:它一变后面缓存全断 |
|
|
47
|
+
| 相关旧事有效期 `semanticTtlMin` | 3 分钟 | 超时重新检索,避免带入隔夜话题 |
|
|
48
|
+
|
|
49
|
+
### 抓取
|
|
50
|
+
|
|
51
|
+
| 项 | 默认 | 说明 |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| 单条用户消息上限 `userCaptureChars` | 2000 | |
|
|
54
|
+
| 工具输出上限 `toolCaptureChars` | 500 | bash 一次能吐几万字,L1 才 3000 字 |
|
|
55
|
+
| 回复截断 `botCaptureChars` | 400 | 一轮回复拼起来再截 |
|
|
56
|
+
|
|
57
|
+
### 整理(模型与摘要)
|
|
58
|
+
|
|
59
|
+
| 项 | 默认 | 说明 |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| 安静多久才整理 `quietMin` | 10 分钟 | 避免聊到一半被摘要腰斩 |
|
|
62
|
+
| 整理间隔 `organizeEveryMin` | 5 分钟 | 定时补漏 |
|
|
63
|
+
| 遗忘阈值 `memoryCfg.forgetThreshold` | 0.25 | DL1 保持率低于此值就忘,忘前写台账 |
|
|
64
|
+
| 每轮最多沉淀几条 `memoryCfg.l2KeepMax` | 10 | 其余落流水账 |
|
|
65
|
+
| 摘要分片 `memoryCfg.summaryChunk` | 60 条 | |
|
|
66
|
+
| 摘要时关思考 `noThink.summary` | 关 | 摘要要想清楚再压 |
|
|
67
|
+
| 各阶段模型 `models.*` | 跟会话 / 硅基流动 | 嵌入、重排、融合、摘要可分别指定 |
|
|
68
|
+
|
|
69
|
+
### 向量 / 语义检索
|
|
70
|
+
|
|
71
|
+
| 项 | 默认 | 说明 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| API Key `apiKey` | 空 | 空 = 没有语义检索,只走关键词(融合摘要不受影响) |
|
|
74
|
+
| API 地址 `apiBase` | `https://api.siliconflow.cn/v1` | |
|
|
75
|
+
| 嵌入模型 | `BAAI/bge-m3` | 必须和 astrbot 端一致,否则向量签名对不上(只读不写) |
|
|
76
|
+
| 重排模型 | `BAAI/bge-reranker-v2-m3` | 检索精排 |
|
|
77
|
+
| 相关旧事 `semanticInject` | 关 | 每轮多一次 embedding 调用 |
|
|
78
|
+
| 检索条数 `semanticTopK` | 5 | |
|
|
79
|
+
| 相似度下限 `semanticMinSim` | 0.35 | |
|
|
80
|
+
|
|
81
|
+
### 自身历史压缩
|
|
82
|
+
|
|
83
|
+
| 项 | 默认 | 说明 |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| 每轮压缩 dsh 历史 `autoCompact` | 开 | 压成一条占位(零 LLM),压掉的内容先归档进记忆 |
|
|
86
|
+
| 压缩后保留几轮 `compactKeepTurns` | 0 | 0 = 一条不留,全交给记忆 |
|
|
87
|
+
| 每轮归档字数 `compactArchiveChars` | 1200 | |
|
|
88
|
+
|
|
89
|
+
## 工具(模型自己调用)
|
|
90
|
+
|
|
91
|
+
dsh 这端没有聊天命令,记忆是给模型用的工具:
|
|
92
|
+
|
|
93
|
+
| 工具 | 作用 |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `memory_write` | 手动记一条 |
|
|
96
|
+
| `memory_search` | 跨五层检索(结果带 id) |
|
|
97
|
+
| `memory_attend` | 关注:热度 +1,DL1 的会回 L2 |
|
|
98
|
+
| `memory_forget` | 忘掉:写台账后从库和向量里删 |
|
|
99
|
+
| `memory_stats` | 各层条数 / 字数 / 向量库 / 整理情况 |
|
|
100
|
+
|
|
101
|
+
想「看一眼当前注入了什么」,看 `<数据目录>/status.json` 和注入块本身;
|
|
102
|
+
astrbot 那边的 `/memory ctx` 提供同样的视角。
|
|
103
|
+
|
|
104
|
+
## 共用一份记忆
|
|
105
|
+
|
|
106
|
+
1. dsh 的数据目录就是共享目录(默认 `~/.dsh/storages/memory-v5`),把它填进 astrbot 的「数据目录」。
|
|
107
|
+
2. 两端 `writer` 必须不同(`dsh` / `astrbot`)。
|
|
108
|
+
3. 两端进程要能看见同一个路径;docker 要把宿主目录挂进容器。
|
|
109
|
+
4. 嵌入模型必须一致,签名对不上时本端只读不写。
|
|
110
|
+
|
|
111
|
+
## 常见问题
|
|
112
|
+
|
|
113
|
+
**装完没反应?**
|
|
114
|
+
看日志有没有 `[memory-tiers] 已加载`。没有 = 包没被加载(检查包根的 `package.json` 和
|
|
115
|
+
`cordis.patch.yml` 是否在);有但注入块是空的 = 记忆库还没内容。
|
|
116
|
+
|
|
117
|
+
**注入块一直是空的?**
|
|
118
|
+
`inject=false`?`bootMaxChars` 设成了很小的值?`<数据目录>/status.json` 里的
|
|
119
|
+
`promptInjected` / `inject` 字段能直接回答。
|
|
120
|
+
|
|
121
|
+
**改了配置没生效?**
|
|
122
|
+
设置页保存后要重启 dsh(配置是 apply 时读的)。
|
|
123
|
+
|
|
124
|
+
**旧配置会不会坏?**
|
|
125
|
+
不会。旧的毫秒键读取时自动换算成分钟,只在内存里换算,不会改写你的 config.json。
|
|
126
|
+
|
|
127
|
+
**两份代码怎么改?**
|
|
128
|
+
`core/` 是真源,根目录是副本。改完跑 `node sync_core.mjs`;`node sync_core.mjs --check` 只比对。
|
|
129
|
+
|
|
130
|
+
## 目录
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
dsh/
|
|
134
|
+
├── core/ 源码真源:memory / silicon / index / settings / client / compaction / dshllm
|
|
135
|
+
├── *.mjs 等 core/ 的副本(加载器从包根读 package.json 与 cordis.patch.yml)
|
|
136
|
+
├── sync_core.mjs core → 根目录 同步
|
|
137
|
+
├── ARCHITECTURE.md 架构(前半段与 astrbot 逐字一致,后半段是本端适配)
|
|
138
|
+
├── CHANGELOG.md 改动笔记
|
|
139
|
+
└── test/ 测试(node test/test_xxx.mjs 直接跑)
|
|
140
|
+
```
|