dsh-engramory-recall 0.6.1 → 0.7.0
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/CHANGELOG.md +37 -0
- package/README.md +57 -38
- package/bm25.js +54 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,43 @@
|
|
|
3
3
|
本项目的所有重要变更都记在这里。格式参照 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
|
|
4
4
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
5
5
|
|
|
6
|
+
## [0.7.0] — 2026-10-15
|
|
7
|
+
|
|
8
|
+
### 变更
|
|
9
|
+
|
|
10
|
+
- **长度归一常数 B 从教科书的 0.75 调到 0.4**(`bm25.js`)。三把尺子一致说 0.75 偏大:
|
|
11
|
+
LongMemEval-S 500 题 @1 87.8% → **88.4%**、@5 97.4% → **98.2%**、覆盖 91.5% → **92.5%**、
|
|
12
|
+
MRR 0.9155 → **0.9202**;自建合成卷子 hit@1 49.9% → **50.4%**、hit@4 66.6% → **67.5%**;
|
|
13
|
+
真库探针的中立对照 C top1 141/181 → **150/181**、recall@4 168/181 → **177/181**。
|
|
14
|
+
代价是偏袒短名的对照 A 掉了 2 分(45→43)。机制:长会话/长笔记里同一个词出现得更多,
|
|
15
|
+
`len/avgLen` 的惩罚就变成「谁长谁吃亏」,与相关性无关。扫过 0.2~0.9,0.3~0.5 是平台,取 0.4。
|
|
16
|
+
- README 三张表与「长度归一」小节据此重算;`bench/expectations.json` 与
|
|
17
|
+
`bench/longmemeval-results.json` 一起重设(都写了原因)。
|
|
18
|
+
|
|
19
|
+
### 新增(记在「被实测证伪的路」里)
|
|
20
|
+
|
|
21
|
+
- **拿「结果要多样」当选桶护栏(MMR / 覆盖优先)**:在 LongMemEval-S 500 题上,MMR 各档
|
|
22
|
+
(λ 0.15 / 0.3 / 0.5)与基线逐位相同;覆盖优先 β=1.0 让 @1 掉 1.4pt。
|
|
23
|
+
顺带把「差一点」量化出来:948 个 gold 里 87.9% 已进 top-5,没进的里 40.0% 排在第 6~10 名、
|
|
24
|
+
39.1% 在第 11~25 名、16.5% 与问题零词重叠 —— 瓶颈是语义,不是重排。
|
|
25
|
+
- **df 下限**(丢掉出现在超过一半文档里的词):LongMemEval-S 上三档全不涨,@1 持平或更差。
|
|
26
|
+
|
|
27
|
+
## [0.6.2] — 2026-10-15
|
|
28
|
+
|
|
29
|
+
### 变更
|
|
30
|
+
|
|
31
|
+
- **查询侧丢掉英文虚词**(`bm25.js` 的 `EN_STOPWORDS` + `queryTerms()`):`what / is / the / of /
|
|
32
|
+
did / how…` 这类语法词不再参与打分。只过滤查询、**索引侧一个词不动** —— 索引侧删词会顺带
|
|
33
|
+
改掉每篇的 `len`,那等于动了长度归一,不值当。
|
|
34
|
+
LongMemEval-S 500 题:RecallAny@1 **86.2% → 87.8%**、Recall@5 90.7% → 91.5%、
|
|
35
|
+
MRR@5 0.9056 → 0.9155。自建合成基准与真库探针不变(那两把尺子的查询是中文的)。
|
|
36
|
+
虚词表只收语法词,`time / name / like` 这类可能带语义的词一个不收;只剩虚词的查询退回原样,
|
|
37
|
+
不清空(清空等于一条都召不回)。
|
|
38
|
+
- README 的 LongMemEval 章节据此重算:本插件现在比教科书 BM25 高 1.6pt @1,并写明**这 1.6pt
|
|
39
|
+
全部来自这一处通用改动**,不是本插件独门机制的功劳。
|
|
40
|
+
- 「被实测证伪的路」加入第六条:**伪相关反馈查询扩展**(拿 top-1 的高频词再搜一遍)。
|
|
41
|
+
实测 RecallAny@1 89.2% → 48.3%、Recall@5 只涨 0.4pt —— 只看 top-5 的话会被误判成有效。
|
|
42
|
+
|
|
6
43
|
## [0.6.1] — 2026-10-15
|
|
7
44
|
|
|
8
45
|
### 新增
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
| **三年后还能跑** | 不需要编译原生模块、不需要常驻服务 | 带 `better-sqlite3` 编译、rust 工具链、独立 daemon 的方案,会在某次系统升级后让你回来看这篇 README |
|
|
33
33
|
|
|
34
34
|
四问都过了,才轮到比功能。功能这一层,本插件的取舍是**把不确定性变成可测量的东西**:
|
|
35
|
-
|
|
35
|
+
新机制必须在库上跑出数字,跑不出来就不上(文末八条被证伪的路就是这么留下的)。
|
|
36
36
|
|
|
37
37
|
## 跟同类插件对照
|
|
38
38
|
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
|
|
72
72
|
### 短板
|
|
73
73
|
|
|
74
|
-
- **公开基准只跑了一半**:LongMemEval-S 500 题跑出 RecallAny@5
|
|
74
|
+
- **公开基准只跑了一半**:LongMemEval-S 500 题跑出 RecallAny@5 98.2%(见下),但那只测英文词面
|
|
75
75
|
检索;`thoth-mem` 公布的 470 题版本(RecallAny@5 432/470、Recall@5 84.8%、NDCG@10 0.8165)
|
|
76
76
|
用的是别的 harness,两边数字不可相减。真正的「跨插件排名」仍然不存在。
|
|
77
77
|
- **无语义检索**:没有 embedding、没有向量库。同义改写、隐含意图要靠手工同义表覆盖,
|
|
@@ -95,7 +95,7 @@ CI 里再跑一遍 `npm run bench:check` 跟 `bench/expectations.json` 对账。
|
|
|
95
95
|
|
|
96
96
|
| 引擎 | 总 hit@1 | 总 hit@4 | verbatim | colloquial | keyword |
|
|
97
97
|
|---|---|---|---|---|---|
|
|
98
|
-
| **本插件** | **
|
|
98
|
+
| **本插件** | **50.4%** | 67.5% | 98.7% | 34.8% | 15.6% |
|
|
99
99
|
| 教科书 BM25(同分词口径) | 43.2% | 46.9% | 100% | 17.9% | 17.9% |
|
|
100
100
|
| 教科书 BM25(中文逐字切分) | 47.2% | 53.4% | 100% | 20.1% | 28.1% |
|
|
101
101
|
| 瞎猜(地板线) | 0.6% | 2.6% | 0.4% | 0.9% | 0.4% |
|
|
@@ -106,15 +106,15 @@ CI 里再跑一遍 `npm run bench:check` 跟 `bench/expectations.json` 对账。
|
|
|
106
106
|
|
|
107
107
|
| 改动 | 总 hit@1 | 相对本插件 |
|
|
108
108
|
|---|---|---|
|
|
109
|
-
| 本插件 |
|
|
110
|
-
| 去掉同义表 | 46.
|
|
111
|
-
| 去掉笔记别名(不再声明 `aliases`) | 56.
|
|
112
|
-
| 去掉字段加权(`TITLE_EXTRA` 24 → 0) | 49.
|
|
113
|
-
| 同义键碎片不降权(回到改动前) | 49.
|
|
114
|
-
| 桥权重与同义表一致(都算 0.5) |
|
|
109
|
+
| 本插件 | 50.4% | — |
|
|
110
|
+
| 去掉同义表 | 46.4% | -4.0pt |
|
|
111
|
+
| 去掉笔记别名(不再声明 `aliases`) | 56.1% | **+5.7pt** |
|
|
112
|
+
| 去掉字段加权(`TITLE_EXTRA` 24 → 0) | 49.1% | -1.3pt |
|
|
113
|
+
| 同义键碎片不降权(回到改动前) | 49.0% | -1.4pt |
|
|
114
|
+
| 桥权重与同义表一致(都算 0.5) | 51.4% | +1.0pt |
|
|
115
115
|
|
|
116
116
|
「去掉笔记别名」是 **+6.7pt**,在总分上反而更好看 —— 这个语料里别名是把双刃剑:
|
|
117
|
-
它帮自己(写了别名的 37 篇 hit@1
|
|
117
|
+
它帮自己(写了别名的 37 篇 hit@1 94.6%,没写的 187 篇 23.0%),
|
|
118
118
|
也把同域别的笔记的口语查询抢过来(语料里同域的别名共用同一个口语域词,而别名是程序按比例随机发的)。
|
|
119
119
|
真库里别名是人手写的、更克制,所以别拿这一行下结论 —— 判断别名只看探针的别名组(5/5)。
|
|
120
120
|
|
|
@@ -125,21 +125,34 @@ CI 里再跑一遍 `npm run bench:check` 跟 `bench/expectations.json` 对账。
|
|
|
125
125
|
而 `代理`、`部署` 既是同义键、也是笔记正文里的原词,剔掉等于把真证据扔了。
|
|
126
126
|
所以改成**降权不剔除**(`BRIDGED_FRAGMENT_WEIGHT = 0.5`,桥到的技术词算 `BRIDGE_WEIGHT = 1`)。
|
|
127
127
|
降权只针对键的**碎片**(单字、相邻双字),键本身照旧算满权重 —— 这一条是量出来的:
|
|
128
|
-
一开始连键本身一起降权,`verbatim` 对照组从 100% 掉到 95.1%,放开键本身后回到 98.
|
|
128
|
+
一开始连键本身一起降权,`verbatim` 对照组从 100% 掉到 95.1%,放开键本身后回到 98.7%,
|
|
129
129
|
而桥组一点没退。道理很直白:查询里原样出现的那个词是实打实的证据,降权的理由只是
|
|
130
130
|
它拆出来的碎片在别的笔记里到处都是。
|
|
131
131
|
|
|
132
|
-
代价是与「桥权重打回 0.5」相比总分少 0
|
|
132
|
+
代价是与「桥权重打回 0.5」相比总分少 1.0 个点(51.4% → 50.4%)。接受它的理由:
|
|
133
133
|
合成语料里绝大多数查询本来就有字面落点,总分被它们主导;桥是唯一在字面完全无交集时
|
|
134
134
|
还能工作的机制 —— 同一批桥查询,桥权重打回 0.5 只有 25.0%,碎片不降权 43.8%。
|
|
135
135
|
(历史值:修「碎片压桥」之前,那一版语料上这组查询只有 27.3%。)
|
|
136
136
|
|
|
137
|
-
「去掉字段加权」那一行要单独说:它现在是 -
|
|
137
|
+
「去掉字段加权」那一行要单独说:它现在是 -1.3pt(拿掉略微更差),**在一次引擎改动之前它是 +4pt**
|
|
138
138
|
(拿掉明显更好),而**真实库的中立对照一直说拿掉更差**(见下)。当时的冲突定位到机制上:
|
|
139
139
|
合成语料的标题是从正文模板派生的、与正文近乎重复,抬高标题权重只是放大域词噪声;
|
|
140
140
|
真实库的标题是人手提炼的。现在前两把尺子都偏向「留着字段加权」,但理由仍是上面这条 ——
|
|
141
141
|
不是冲突消失了,是那把合成尺子的标题变真实了一点。两者不一致时以对照 C 为准。
|
|
142
142
|
|
|
143
|
+
**长度归一的强度 B 是量出来的,不是抄的。** 教科书默认 0.75 我们抄了很久,三把尺子都说它偏大:
|
|
144
|
+
|
|
145
|
+
| 尺子 | B=0.75 | B=0.4 |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| 这份合成卷子 | hit@1 49.9% · hit@4 66.6% | hit@1 50.4% · hit@4 67.5% |
|
|
148
|
+
| LongMemEval-S 500 题 | @1 87.8% · @5 97.4% · 覆盖 91.5% | @1 88.4% · @5 98.2% · 覆盖 92.5% |
|
|
149
|
+
| 真库对照 C(中立那把) | top1 141/181 · recall@4 168/181 | top1 150/181 · recall@4 177/181 |
|
|
150
|
+
|
|
151
|
+
机制:同一个词在长会话、长笔记里出现得更多,`len/avgLen` 这个惩罚就变成「谁长谁吃亏」,
|
|
152
|
+
与相关性无关。代价在对照 A 上看得见(top1 45→43),但 A 拿文件名当查询、结构性偏袒短名,
|
|
153
|
+
按一直以来的规矩以对照 C 为准。扫过的值是 0.2 / 0.3 / 0.4 / 0.5 / 0.6 / 0.75 / 0.9,
|
|
154
|
+
0.3~0.5 是一片平台,取中间偏下的 0.4。
|
|
155
|
+
|
|
143
156
|
### 桥组:32 条,65.6%
|
|
144
157
|
|
|
145
158
|
| 组 | 条数 | hit@1 | 说明 |
|
|
@@ -172,12 +185,12 @@ CI 里再跑一遍 `npm run bench:check` 跟 `bench/expectations.json` 对账。
|
|
|
172
185
|
| 组 | 条数 | 测什么 | 最近一次 |
|
|
173
186
|
|---|---|---|---|
|
|
174
187
|
| 口径 | 21 | 真实场景的中文口语问法 | recall@4 21/21 |
|
|
175
|
-
| 桥探针 | 10 | 全局同义表(大白话问英文/术语密集的笔记) | recall@4
|
|
188
|
+
| 桥探针 | 10 | 全局同义表(大白话问英文/术语密集的笔记) | recall@4 10/10 |
|
|
176
189
|
| 别名探针 | 5 | 笔记自己的 `aliases:`(关键词在全局桥里一个都没有) | recall@4 5/5 |
|
|
177
|
-
| 死桥自检 | 519 值 | 同义桥的每个值是否真的存在于语料(不存在等于白写) |
|
|
178
|
-
| 对照 A |
|
|
179
|
-
| 对照 B |
|
|
180
|
-
| 对照 C |
|
|
190
|
+
| 死桥自检 | 519 值 | 同义桥的每个值是否真的存在于语料(不存在等于白写) | 118 个死值(均为通用词) |
|
|
191
|
+
| 对照 A | 194 | 文件名当查询的底线(对标题权重有偏袒) | top1 43/194 · recall@4 96/194 |
|
|
192
|
+
| 对照 B | 169 | 笔记中文标题当查询的底线(同样偏袒) | top1 164/169 · recall@4 168/169 |
|
|
193
|
+
| 对照 C | 181 | 正文独有词当查询(该词在任何标题里都没有) | top1 150/181 · recall@4 177/181 |
|
|
181
194
|
|
|
182
195
|
对照 A 和 B 都拿标题当查询,所以它们对「标题里的词多计几遍」这类参数是**结构性偏袒**的:
|
|
183
196
|
抬高标题权重几乎必然让这两组变好。它们既不该当目标函数,也不能用来证明「抬高标题权重是安全的」。
|
|
@@ -216,34 +229,37 @@ haystack,题目分六类。
|
|
|
216
229
|
做法:一个 session = 一篇笔记(角色前缀保留),query = 问题原文(**不读** `question_date`,
|
|
217
230
|
也**不看** `has_answer` 标注),命中 = gold session 进 top-k。500 题跑 18 秒,零模型调用。
|
|
218
231
|
|
|
219
|
-
| 指标 | 全部 500 题 |
|
|
220
|
-
| --- | --- |
|
|
221
|
-
| RecallAny@1 | 86.2% |
|
|
222
|
-
| RecallAny@5 | **97.0
|
|
223
|
-
| Recall@5(gold 覆盖率) | 90.7% |
|
|
224
|
-
| MRR@5 | 0.9056 |
|
|
225
|
-
| RecallAny@10 | 98.2% |
|
|
232
|
+
| 指标 | 全部 500 题 | 教科书 BM25(同分词口径) |
|
|
233
|
+
| --- | --- | --- |
|
|
234
|
+
| RecallAny@1 | **88.4%** | 86.2% |
|
|
235
|
+
| RecallAny@5 | **98.2%** | 97.0% |
|
|
236
|
+
| Recall@5(gold 覆盖率) | **92.5%** | 90.7% |
|
|
237
|
+
| MRR@5 | **0.9202** | 0.9056 |
|
|
238
|
+
| RecallAny@10 | **98.8%** | 98.2% |
|
|
226
239
|
|
|
227
240
|
| 题型 | 题数 | RecallAny@1 | RecallAny@5 | Recall@5 | MRR@5 |
|
|
228
241
|
| --- | --- | --- | --- | --- | --- |
|
|
229
|
-
| knowledge-update | 78 |
|
|
230
|
-
| multi-session | 133 | 88.0% |
|
|
231
|
-
| single-session-assistant | 56 |
|
|
232
|
-
| single-session-preference | 30 |
|
|
233
|
-
| single-session-user | 70 |
|
|
234
|
-
| temporal-reasoning | 133 |
|
|
242
|
+
| knowledge-update | 78 | 98.7% | 100.0% | 99.4% | 0.994 |
|
|
243
|
+
| multi-session | 133 | 88.0% | 98.5% | 86.0% | 0.920 |
|
|
244
|
+
| single-session-assistant | 56 | 100.0% | 100.0% | 100.0% | 1.000 |
|
|
245
|
+
| single-session-preference | 30 | 46.7% | 90.0% | 90.0% | 0.627 |
|
|
246
|
+
| single-session-user | 70 | 92.9% | 100.0% | 100.0% | 0.950 |
|
|
247
|
+
| temporal-reasoning | 133 | 85.0% | 97.0% | 88.5% | 0.894 |
|
|
235
248
|
|
|
236
249
|
三句必须说在前面的实话:
|
|
237
250
|
|
|
238
|
-
1.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
251
|
+
1. **这 2.2 个点的来源要说清楚。** 本插件比教科书 BM25 高 2.2pt @1,差的那部分**全部**来自两处通用改动:
|
|
252
|
+
查询侧丢掉英文虚词(`what / is / the / of / did / how…`),以及把长度归一常数从教科书的 0.75
|
|
253
|
+
调到 0.4。两处都与中文无关 —— 是我们自己的机制(同义表、别名、字段加权、中文分词)在这份数据上
|
|
254
|
+
没被激活:LongMemEval 是英文的,session 没有标题、没有别名、没有中文口语,它们在这里没事可做。
|
|
255
|
+
B 的取值不是在这份数据上单独挑的(挑法见上:三把尺子一致,真库中立对照 +9/+9)。所以这张表
|
|
256
|
+
证明的是「英文词面检索不输、且基础卫生做到位」,**不是**「本插件的独门设计更好」。机制值多少,
|
|
257
|
+
回上面两把尺子看。
|
|
242
258
|
2. **别拿它跟别家数字直接相减。** thoth-mem 公布过 LongMemEval-S 470 题、RecallAny@5 91.9%。
|
|
243
259
|
我们用的是 cleaned 版 500 题 + 自己的 harness,题集版本、命中口径、读不读日期都未必一样,
|
|
244
260
|
相减没有意义。能担保的是:harness 与逐题结果都在仓库里(`bench/longmemeval.mjs`、
|
|
245
261
|
`bench/longmemeval-results.json`),可以逐题核。
|
|
246
|
-
3. **最差的是偏好题**(
|
|
262
|
+
3. **最差的是偏好题**(46.7%@1):「我喜欢什么」这类问题本身没什么词面抓手。这是零模型路线的
|
|
247
263
|
边界 —— 偏好类想做得更好,要么开可选的 `llm.js`,要么就老老实实把偏好写进标题。
|
|
248
264
|
|
|
249
265
|
复算:
|
|
@@ -576,7 +592,7 @@ node bench/longmemeval.mjs --data bench/.tmp/longmemeval_s_cleaned.json # 公
|
|
|
576
592
|
```
|
|
577
593
|
|
|
578
594
|
CI 跑 `npm test` 和 `npm run bench:check` 两件事。后者是防「数字悄悄漂走」的:任何一项
|
|
579
|
-
跟期望值的偏差超过 2
|
|
595
|
+
跟期望值的偏差超过 2 个百分点就红;桥组容差 3pt、verbatim 组 1pt(它们掉下来多半是我们改坏了)。
|
|
580
596
|
分数低不红 —— 绝对值会随语料演化,卡死绝对值只会逼人去改尺子;漂了才红,因为那说明
|
|
581
597
|
要么是回归、要么是刻意改动而没人说清楚。
|
|
582
598
|
|
|
@@ -625,10 +641,13 @@ CI 跑 `npm test` 和 `npm run bench:check` 两件事。后者是防「数字悄
|
|
|
625
641
|
| DSH-SEP | 每步 ~1250 字符 guidance + ≤4200 字符快照 | 同上 | 抽取走 `api.deepseek.com` |
|
|
626
642
|
| **本插件** | **0** | **≤480 字符,且只有指针** | **无** |
|
|
627
643
|
|
|
628
|
-
##
|
|
644
|
+
## 八条被实测证伪的路
|
|
629
645
|
|
|
630
646
|
| 尝试 | 结果 |
|
|
631
647
|
|---|---|
|
|
648
|
+
| 拿「结果要多样」当选桶护栏:MMR / 覆盖优先 | 在 LongMemEval-S 500 题上,MMR(对 tf 集合算 Jaccard,λ 取 0.15 / 0.3 / 0.5)与基线**逐位相同**(87.8% @1):不同 session 之间几乎不重叠,惩罚项无处发力。覆盖优先 β=1.0 让 @1 从 87.8% 掉到 86.4%,换来的 @5 只有 +0.2pt。机制:我们要解决的是「这个词的重心在别处」,不是「两条结果太像」。跨会话类题目(multi-session 133 题)真正的瓶颈是 gold session 里根本没有问题里的名词 —— 948 个 gold 里 16.5% 与问题零词重叠,19.1% 排在第 6~10 名、39.1% 在第 11~25 名,属于「差一点」,需要的是语义不是重排 |
|
|
649
|
+
| df 下限(把出现在超过一半文档里的词直接扔掉) | LongMemEval-S 上扫描 keep6 / keep12 / 0.7 三档:@1 与基线持平或更差(80.5% vs 81.5%),@5 一点不涨。英文虚词已经由查询侧停用词处理掉,索引侧再删一遍只会顺手动掉 `len` |
|
|
650
|
+
| 伪相关反馈查询扩展(拿 top-1 的高频词再搜一遍,RM3 那一路) | 在 LongMemEval-S 500 题上前 120 题实测:RecallAny@1 从 89.2% 掉到 **48.3%**。机制:扩出来的词全是 top-1 自己的词汇,等于把「已经赢了的文档」又投了一遍票,稍有偏差的第一次命中会被自己的回声推到第一。Recall@5 只涨 0.4pt,@1 腰斩 —— 这种改动在只看 top-5 的评测里会被误认为「有效」 |
|
|
632
651
|
| 提高分数门槛 / IDF 门槛过滤误召回 | 正例最低分 4.59、反例最高分 5.54,区间重叠切不开;IDF 门槛会把 recall 从 28/28 打到 13/28 |
|
|
633
652
|
| freshness 时间衰减(学灵枢 / dsh-mneme) | 六档半衰期扫描:recall 恒为 28/28、top-1 只有一档 +1(噪声级)。库里「旧」的那批恰好是基础事实,衰减就是误伤 |
|
|
634
653
|
| 时间乘性加权(学 hindsight) | 五档扫描净收益为零,`w ≥ 0.1` 起反而伤对照组 |
|
package/bm25.js
CHANGED
|
@@ -14,7 +14,22 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const K1 = 1.2;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* 长度归一的强度。0.75 是教科书默认值(也是我们最初抄的值),三把尺子都说它偏大:
|
|
19
|
+
*
|
|
20
|
+
* | 尺子 | B=0.75 | B=0.4 |
|
|
21
|
+
* | --- | --- | --- |
|
|
22
|
+
* | LongMemEval-S 500 题 | @1 87.8% · @5 97.4% · 覆盖 91.5% · MRR 0.9140 | @1 88.4% · @5 98.2% · 覆盖 92.5% · MRR 0.9196 |
|
|
23
|
+
* | 合成基准 704 查询 | @1 49.9% · @4 66.6% · verbatim 98.2% | @1 50.4% · @4 67.5% · verbatim 98.7% |
|
|
24
|
+
* | 真库对照 C(中立那把) | top1 141/181 · @4 168/181 | top1 150/181 · @4 177/181 |
|
|
25
|
+
*
|
|
26
|
+
* 机制:同一个词在长会话/长笔记里出现得更多,长度归一按 `len/avgLen` 把整篇压下去;
|
|
27
|
+
* 当库里绝大多数是长文档时,这个惩罚就变成「谁长谁吃亏」,与相关性无关。
|
|
28
|
+
* 代价在对照 A 上能看到(top1 45→43),但 A 拿文件名当查询、结构性偏袒短名,
|
|
29
|
+
* 所以按一直以来的规矩:以对照 C 为准,A/B 只当「别掉分」的底线。
|
|
30
|
+
* 扫过的值:0.2 / 0.3 / 0.4 / 0.5 / 0.6 / 0.75 / 0.9 —— 0.3~0.5 是一片平台,取中间偏下的 0.4。
|
|
31
|
+
*/
|
|
32
|
+
const B = 0.4;
|
|
18
33
|
|
|
19
34
|
/**
|
|
20
35
|
* 同义扩展项的权重:字典里搭出来的桥,命中也不该完全压过查询里的原词。
|
|
@@ -423,9 +438,45 @@ export function tokenize(text) {
|
|
|
423
438
|
return out;
|
|
424
439
|
}
|
|
425
440
|
|
|
426
|
-
/**
|
|
441
|
+
/**
|
|
442
|
+
* 英文虚词:查询里带着它们不加分,只稀释真正有区分度的词。
|
|
443
|
+
*
|
|
444
|
+
* 为什么只在**查询侧**过滤、索引侧一个词不动:索引侧删词会同时改掉每篇的
|
|
445
|
+
* `len`,那等于顺手动了长度归一 —— 长度归一刚在 2026-10-15 为 `TITLE_EXTRA`
|
|
446
|
+
* 的事校准过,不该为了这个另起一轮。实测也支持这个选择:查询+索引都过滤
|
|
447
|
+
* 90.0%、只过滤查询 90.8%(LongMemEval-S 前 120 题)。
|
|
448
|
+
*
|
|
449
|
+
* 数据来源:LongMemEval-S 500 题(英文),86.2% → 87.8% RecallAny@1、
|
|
450
|
+
* Recall@5 90.7% → 91.7%、MRR@5 0.904 → 0.914。中文查询几乎不受影响
|
|
451
|
+
* (中文词不进这张表)。
|
|
452
|
+
*
|
|
453
|
+
* 只收「语法词」:疑问词、be/do/have 的变位、介词、代词、限定词。
|
|
454
|
+
* 刻意**不收**任何可能带语义的词(如 `time`、`name`、`like`)——
|
|
455
|
+
* 那类词在短查询里往往是唯一的抓手,删了就是漏检。
|
|
456
|
+
*/
|
|
457
|
+
const EN_STOPWORDS = new Set([
|
|
458
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'if', 'then', 'than', 'that', 'this', 'these', 'those',
|
|
459
|
+
'what', 'which', 'who', 'whom', 'whose', 'when', 'where', 'why', 'how',
|
|
460
|
+
'is', 'are', 'was', 'were', 'be', 'been', 'being', 'am',
|
|
461
|
+
'do', 'does', 'did', 'doing', 'done',
|
|
462
|
+
'have', 'has', 'had', 'having',
|
|
463
|
+
'can', 'could', 'will', 'would', 'shall', 'should', 'may', 'might', 'must',
|
|
464
|
+
'of', 'in', 'on', 'at', 'to', 'from', 'by', 'with', 'for', 'about', 'as', 'into', 'over', 'after', 'before',
|
|
465
|
+
'it', 'its', 'my', 'me', 'we', 'our', 'you', 'your', 'he', 'him', 'she', 'her', 'they', 'them', 'their',
|
|
466
|
+
'there', 'here', 'so', 'such', 'no', 'not', 'only', 'just', 'also', 'very', 'much', 'many', 'more', 'most', 'some', 'any', 'all',
|
|
467
|
+
]);
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 查询词项:去重 + 丢掉英文虚词。
|
|
471
|
+
* 两个例外都保留原样,宁可留噪声也不把查询清空:
|
|
472
|
+
* · 只剩一个词项的查询(很可能那个词就是全部信息);
|
|
473
|
+
* · 全是虚词的查询(「what about this」这种),过滤后为空等于一条都召不回。
|
|
474
|
+
*/
|
|
427
475
|
export function queryTerms(text) {
|
|
428
|
-
|
|
476
|
+
const terms = [...new Set(tokenize(text))];
|
|
477
|
+
if (terms.length < 2) return terms;
|
|
478
|
+
const kept = terms.filter((term) => !EN_STOPWORDS.has(term));
|
|
479
|
+
return kept.length ? kept : terms;
|
|
429
480
|
}
|
|
430
481
|
|
|
431
482
|
/** 查询里命中的同义键(按原文字面,大小写不敏感)。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-engramory-recall",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Local BM25 auto-recall, fail-closed write guard, static doctor and a memory panel for the Engramory markdown store — no network, no model, no daemon.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|