dsh-engramory-recall 0.9.0 → 0.11.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 +66 -0
- package/README.md +45 -12
- package/bm25.js +31 -6
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,72 @@
|
|
|
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.11.0] — 2026-10-15
|
|
7
|
+
|
|
8
|
+
### 新增
|
|
9
|
+
|
|
10
|
+
- **同义表 / aliases 的覆盖率尺子**(`bench/bridge-coverage.mjs`,`npm run bench:bridges`):
|
|
11
|
+
把「这张手写桥表到底有没有在工作」从感觉变成可复算的数字,四件事一起量 ——
|
|
12
|
+
- **死值**:真库与合成语料各算一份 `df`,每个值落进四个格:两边都活 90 · 只对真库有用 312 ·
|
|
13
|
+
**只对基准有用 39** · 两边都死 78(519 个值里 15.0%)。真库口径的全死值 117(22.5%),
|
|
14
|
+
整键全死 9 个键(`下载`、`轮转`、`并发`、`乱码`…)。
|
|
15
|
+
- **覆盖**:低 `df`(≤5)锚点词 2732 个,被桥值接住 118(4.3%);一个桥都接不住的 chunk 占 50%。
|
|
16
|
+
- **使用面**:合成语料 704 条走到 69 个键,探针 36 条真实 query 走到 44 个键,两卷都没走到的 76 个。
|
|
17
|
+
- **逐键消融**(`--ablate`):被走到的 101 个键里 57 个零贡献;整表拔掉在合成卷上是
|
|
18
|
+
hit@1 355→327、hit@4 475→365,在**真探针**上是 27→24 / 36→31。
|
|
19
|
+
`--json` 把结果落 `bench/.bridge-coverage.json`;`--store <path>` 可以对着任意一份记忆库跑。
|
|
20
|
+
- `scripts/probe-queries.mjs`:探针的三张配对表(口径 22 / 桥 10 / 别名 5)抽成共享模块,
|
|
21
|
+
探针与尺子跑**同一张卷子**,避免「改了探针忘了改尺子」。
|
|
22
|
+
|
|
23
|
+
### 文档
|
|
24
|
+
|
|
25
|
+
- README 的「去掉同义表」消融行加了折算说明:合成卷上的 -4.0pt 是**上界**(口语组/桥组查询本身就是
|
|
26
|
+
照着「需要桥」造的,39 个同义值只在这份卷子上有落点),真人查询上的同口径数字是 -3 @1 / -5 @4。
|
|
27
|
+
- README「桥和别名是怎么挖出来的」补第 6 条(覆盖率尺子)与上面那张表,并写明**表不动**的理由:
|
|
28
|
+
78 个「两边都死」的值是 `permission` / `idempotent` / `mojibake` / `xpath` 这类通用工程词,
|
|
29
|
+
插件是发布出去的,要服务任意一份记忆库;尺子的用途是把「自用可裁剪、发布留通用词」这笔账
|
|
30
|
+
量出来(`--store` 直接列出别人那份库的死值清单)。
|
|
31
|
+
- 自检清单补 `npm run bench:bridges`。
|
|
32
|
+
|
|
33
|
+
### 说明
|
|
34
|
+
|
|
35
|
+
- 本轮**没有改检索代码**:`bm25.js` 一个字节都没动。试过按「两卷都死」删掉那 78 个值,
|
|
36
|
+
实测两张卷子的分数逐字不变(正因为它逐字不变,就说明删与不删对**本机**质量没有影响,
|
|
37
|
+
而它对别人的库有影响),所以保留原样并把账记在 README 与 CHANGELOG 里。
|
|
38
|
+
|
|
39
|
+
## [0.10.0] — 2026-10-15
|
|
40
|
+
|
|
41
|
+
### 改进
|
|
42
|
+
|
|
43
|
+
- **查询侧再丢掉一类「求助/建议框架词」**(`bm25.js` 的 `EN_REQUEST_STOPWORDS`,14 个:
|
|
44
|
+
`tips / suggest / suggestions / recommend / recommendations / advice / think / thinking /
|
|
45
|
+
idea / ideas / helpful / good / upcoming / weekend`)。这些词是「同一类对话」的框架,不是话题:
|
|
46
|
+
一次求助会话里几乎必有它们,于是任何同流派的会话都会因为共现框架词被抬分 —— 那是 genre 在
|
|
47
|
+
匹配,不是 topic 在匹配;最吃亏的是「先说出偏好、再问建议」的查询,它的实词被框架词稀释成
|
|
48
|
+
「又一场给建议的对话」。
|
|
49
|
+
- **实测(LongMemEval-S 500 题)**:RecallAny@1 **88.4% → 89.2%**、MRR@5 **0.9202 → 0.9251**、
|
|
50
|
+
RecallAny@10 98.8% → 99.0%;最弱的偏好分片 @1 **46.7% → 60.0%**(MRR 0.627 → 0.708),
|
|
51
|
+
其余五个分片逐位不变。
|
|
52
|
+
- **词表只收「在失败样本里真出现过」的写法**,单复数不自动补齐 —— 补了就是没量过的猜测。
|
|
53
|
+
- 逐个消融:没有哪个词单独扛收益(去掉任意一个最多掉 1 题);只留子集与大表同分
|
|
54
|
+
⇒ 收益属于「一类词」,不是一个咒语。
|
|
55
|
+
- 对照实验:换成「按 df 比例自动丢高频查询词」的通用规则反而全线下跌(df<0.35 → @1 87.6%、
|
|
56
|
+
df<0.20 → 85.4%)⇒ 收益来自这一类词的**语义**,不是频次本身。
|
|
57
|
+
- 三道保险保证查询不被清空:单关键词查询不筛;全是虚词的查询退回原样;去掉框架词后为空
|
|
58
|
+
(`any tips` 这种)退回只过虚词的结果。
|
|
59
|
+
- 真库探针与合成基准:同一语料上新旧代码六项数字逐位相同(探针语料本身在此期间被别的会话
|
|
60
|
+
改过,指纹已重设基线);`npm run bench:check` 全部指标在容差内。
|
|
61
|
+
- README 的「八条被实测证伪的路」→「**九条**」,新增一条 **「拿日期当检索信号」**:
|
|
62
|
+
LongMemEval-S 500 题只有 39 题能抽出可用时间窗;硬过滤 11 题救回 / 11 题误伤;
|
|
63
|
+
948 个 gold 的新度百分位平均 0.459 ⇒ 日期侧没有先验;纯按日期排的引擎 @1 仅 3.8%。
|
|
64
|
+
机制写在表里:问题引用的是事件时间,不是 session 时间戳。
|
|
65
|
+
- README 的 LongMemEval 表、分题型表、B 对照表与「三句实话」按新数字更新(2.2pt → 3.0pt)。
|
|
66
|
+
|
|
67
|
+
### 测试
|
|
68
|
+
|
|
69
|
+
- `test/recall.test.mjs` 增加 2 个用例(框架词被丢而实词不丢;全是框架词时退回而不清空),
|
|
70
|
+
合计 213 例。
|
|
71
|
+
|
|
6
72
|
## [0.9.0] — 2026-10-15
|
|
7
73
|
|
|
8
74
|
### 新增
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
| **三年后还能跑** | 不需要编译原生模块、不需要常驻服务 | 带 `better-sqlite3` 编译、rust 工具链、独立 daemon 的方案,会在某次系统升级后让你回来看这篇 README |
|
|
36
36
|
|
|
37
37
|
四问都过了,才轮到比功能。功能这一层,本插件的取舍是**把不确定性变成可测量的东西**:
|
|
38
|
-
|
|
38
|
+
新机制必须在库上跑出数字,跑不出来就不上(文末九条被证伪的路就是这么留下的)。
|
|
39
39
|
|
|
40
40
|
## 跟同类插件对照
|
|
41
41
|
|
|
@@ -119,6 +119,12 @@ CI 里再跑一遍 `npm run bench:check` 跟 `bench/expectations.json` 对账。
|
|
|
119
119
|
| 同义键碎片不降权(回到改动前) | 49.0% | -1.4pt |
|
|
120
120
|
| 桥权重与同义表一致(都算 0.5) | 51.4% | +1.0pt |
|
|
121
121
|
|
|
122
|
+
「去掉同义表」这一行要打个折读:合成语料的口语组 / 桥组查询本身就是照着「需要桥」造的,
|
|
123
|
+
同义表也为它们补过词,所以 **-4.0pt 是这张表在**这份**卷子上的上界,不是它的通用价值**。
|
|
124
|
+
同口径在真人查询上的数字小得多:探针那 36 条真实 query,拔掉整表 hit@1 27 → 24、hit@4 36 → 31
|
|
125
|
+
(`npm run bench:bridges --ablate`)。成因也量出来了:519 个同义值里 **39 个只在合成语料里有落点**,
|
|
126
|
+
在真库上一个词项都不存在(见「覆盖率尺子」一节)。
|
|
127
|
+
|
|
122
128
|
「去掉笔记别名」是 **+6.7pt**,在总分上反而更好看 —— 这个语料里别名是把双刃剑:
|
|
123
129
|
它帮自己(写了别名的 37 篇 hit@1 94.6%,没写的 187 篇 23.0%),
|
|
124
130
|
也把同域别的笔记的口语查询抢过来(语料里同域的别名共用同一个口语域词,而别名是程序按比例随机发的)。
|
|
@@ -152,6 +158,8 @@ CI 里再跑一遍 `npm run bench:check` 跟 `bench/expectations.json` 对账。
|
|
|
152
158
|
|---|---|---|
|
|
153
159
|
| 这份合成卷子 | hit@1 49.9% · hit@4 66.6% | hit@1 50.4% · hit@4 67.5% |
|
|
154
160
|
| LongMemEval-S 500 题 | @1 87.8% · @5 97.4% · 覆盖 91.5% | @1 88.4% · @5 98.2% · 覆盖 92.5% |
|
|
161
|
+
|
|
162
|
+
(这张表单独量 B。查询侧后来又加了一类框架词过滤,@1 从 88.4% 再涨到 **89.2%** —— 见 LongMemEval 一节的第 1 条实话。)
|
|
155
163
|
| 真库对照 C(中立那把) | top1 141/181 · recall@4 168/181 | top1 150/181 · recall@4 177/181 |
|
|
156
164
|
|
|
157
165
|
机制:同一个词在长会话、长笔记里出现得更多,`len/avgLen` 这个惩罚就变成「谁长谁吃亏」,
|
|
@@ -240,26 +248,28 @@ haystack,题目分六类。
|
|
|
240
248
|
|
|
241
249
|
| 指标 | 全部 500 题 | 教科书 BM25(同分词口径) |
|
|
242
250
|
| --- | --- | --- |
|
|
243
|
-
| RecallAny@1 | **
|
|
251
|
+
| RecallAny@1 | **89.2%** | 86.2% |
|
|
244
252
|
| RecallAny@5 | **98.2%** | 97.0% |
|
|
245
253
|
| Recall@5(gold 覆盖率) | **92.5%** | 90.7% |
|
|
246
|
-
| MRR@5 | **0.
|
|
247
|
-
| RecallAny@10 | **
|
|
254
|
+
| MRR@5 | **0.9251** | 0.9056 |
|
|
255
|
+
| RecallAny@10 | **99.0%** | 98.2% |
|
|
248
256
|
|
|
249
257
|
| 题型 | 题数 | RecallAny@1 | RecallAny@5 | Recall@5 | MRR@5 |
|
|
250
258
|
| --- | --- | --- | --- | --- | --- |
|
|
251
259
|
| knowledge-update | 78 | 98.7% | 100.0% | 99.4% | 0.994 |
|
|
252
260
|
| multi-session | 133 | 88.0% | 98.5% | 86.0% | 0.920 |
|
|
253
261
|
| single-session-assistant | 56 | 100.0% | 100.0% | 100.0% | 1.000 |
|
|
254
|
-
| single-session-preference | 30 |
|
|
262
|
+
| single-session-preference | 30 | 60.0% | 90.0% | 90.0% | 0.708 |
|
|
255
263
|
| single-session-user | 70 | 92.9% | 100.0% | 100.0% | 0.950 |
|
|
256
264
|
| temporal-reasoning | 133 | 85.0% | 97.0% | 88.5% | 0.894 |
|
|
257
265
|
|
|
258
266
|
三句必须说在前面的实话:
|
|
259
267
|
|
|
260
|
-
1. **这
|
|
261
|
-
查询侧丢掉英文虚词(`what / is / the / of / did / how
|
|
262
|
-
|
|
268
|
+
1. **这 3.0 个点的来源要说清楚。** 本插件比教科书 BM25 高 3.0pt @1,差的那部分**全部**来自三处通用改动:
|
|
269
|
+
查询侧丢掉英文虚词(`what / is / the / of / did / how…`);查询侧再丢掉一类「求助/建议框架词」
|
|
270
|
+
(`tips / suggest / recommend / advice / think / good / weekend…` 共 14 个,这一处单独值
|
|
271
|
+
**+0.8pt @1**、MRR +0.005,最弱的偏好分片 @1 从 46.7% 抬到 60.0%);以及把长度归一常数从教科书的
|
|
272
|
+
0.75 调到 0.4。三处都与中文无关 —— 是我们自己的机制(同义表、别名、字段加权、中文分词)在这份数据上
|
|
263
273
|
没被激活:LongMemEval 是英文的,session 没有标题、没有别名、没有中文口语,它们在这里没事可做。
|
|
264
274
|
B 的取值不是在这份数据上单独挑的(挑法见上:三把尺子一致,真库中立对照 +9/+9)。所以这张表
|
|
265
275
|
证明的是「英文词面检索不输、且基础卫生做到位」,**不是**「本插件的独门设计更好」。机制值多少,
|
|
@@ -268,8 +278,10 @@ haystack,题目分六类。
|
|
|
268
278
|
我们用的是 cleaned 版 500 题 + 自己的 harness,题集版本、命中口径、读不读日期都未必一样,
|
|
269
279
|
相减没有意义。能担保的是:harness 与逐题结果都在仓库里(`bench/longmemeval.mjs`、
|
|
270
280
|
`bench/longmemeval-results.json`),可以逐题核。
|
|
271
|
-
3.
|
|
272
|
-
|
|
281
|
+
3. **最差的仍是偏好题**(60.0%@1,改动前 46.7%):「我喜欢什么」这类问题本身没什么词面抓手,
|
|
282
|
+
而且它的 16 个 miss 里有 11 个掉在第 2~4 名 —— 全是「另一场给建议的对话」,是同流派干扰而不是
|
|
283
|
+
够不着。丢掉求助框架词能把 @1 抬 13.3pt,剩下的差距才是零模型路线的边界:偏好类想做得更好,
|
|
284
|
+
要么开可选的 `llm.js`,要么就老老实实把偏好写进标题。
|
|
273
285
|
|
|
274
286
|
复算:
|
|
275
287
|
|
|
@@ -466,6 +478,25 @@ aliases: 梯子, 翻墙, 科学上网, 节点, 代理
|
|
|
466
478
|
5. **死值自检**:同义桥的每个值都要在语料里真的存在才有分。指向一个库里根本没有的词,
|
|
467
479
|
效果是写了等于没写:不报错、不降级,最安静的那种失效。探针每次跑都数一遍。
|
|
468
480
|
|
|
481
|
+
第 6 条是 0.11.0 补的:**覆盖率尺子**(`npm run bench:bridges`,`bench/bridge-coverage.mjs`)。
|
|
482
|
+
它把第 5 条从「数一个总数」变成「分格里看」,三张卷子各量各的:
|
|
483
|
+
|
|
484
|
+
| 量什么 | 数字 |
|
|
485
|
+
|---|---|
|
|
486
|
+
| 死值(真库口径) | **117 / 519(22.5%)**,整键全死 9 个键(`下载`、`轮转`、`并发`、`乱码`…) |
|
|
487
|
+
| 死值分格(真库 × 合成语料各一份 `df`) | 两边都活 **90** · 只对真库有用 **312** · **只对基准有用 39** · 两边都死 **78** |
|
|
488
|
+
| 覆盖:低 `df`(≤5)锚点词被桥接住 | **118 / 2732(4.3%)**;一个桥都接不住的 chunk 占 **50%** |
|
|
489
|
+
| 使用面 | 合成语料 704 条 → 走到 **69** 个键;探针 36 条真实 query → 走到 **44** 个键;两张卷子都没走到的键 **76(43%)** |
|
|
490
|
+
| 逐键消融 | 被走到的 101 个键里 **57 个**拔掉不影响任何名次;整表拔掉——合成 hit@1 355→327、hit@4 475→365,**真探针 27→24 / 36→31** |
|
|
491
|
+
|
|
492
|
+
**这轮量完的结论是「表不动」。** 78 个两边都死的值看着像该删的,但它们是 `permission /
|
|
493
|
+
idempotent / mojibake / xpath / selenium / watchdog / dsn / backoff` 这一类**通用工程词** ——
|
|
494
|
+
插件是发布出去的,要服务任意一份记忆库,别人库里当然可能有它们。尺子的用途不是自动删词,
|
|
495
|
+
而是把这笔账**量出来、摆明**:谁想按自己的库裁剪,`npm run bench:bridges --store <自己的库>`
|
|
496
|
+
会直接列出他那一份的死值清单。覆盖率的 4.3% 同理 —— 它量的是表的大小,不是表的好坏:
|
|
497
|
+
手写表永远盖不住几千个锚点,真问题是「没被接住的锚点里有多少条真人 query 会问」,
|
|
498
|
+
而那要靠探针的口径/桥组回答(现在 21/21 与 10/10)。
|
|
499
|
+
|
|
469
500
|
第 5 条当场抓出 32 个值指向不存在的词(`latency`、`slide`、`cert`、`gpt`…),
|
|
470
501
|
其中 7 个是同一轮刚加的,也就是说「按语料锚点写桥」这件事当时并没有做到。删掉后剩 25 个,
|
|
471
502
|
但那 25 个不该删:它们随插件一起发布,要服务的是任意一份记忆库(别人库里当然可能有
|
|
@@ -778,6 +809,7 @@ npm run check # 接线自检:配置键认不认、库路径对不对、装
|
|
|
778
809
|
npm run probe # 召回质量探针:3 组 query + 三道对照,跑在真实记忆库上
|
|
779
810
|
npm run probe -- --save # 把这次结果存成基线,之后每次跑自动比对
|
|
780
811
|
npm run bench:check # 合成基准 + 和 bench/expectations.json 对账(CI 跑的就是它)
|
|
812
|
+
npm run bench:bridges # 同义表覆盖率尺子:死值、覆盖、使用面、逐键消融(--ablate / --json / --store)
|
|
781
813
|
npm run bench:seam # 语义接缝的代价与天花板(四种坏提供方 + 作弊上界)
|
|
782
814
|
npm run bench:mcp # MCP 这一层:冷启动、每步往返、协议帧字节数
|
|
783
815
|
npm run mcp:smoke # 拿脚本当 MCP 客户端,把服务器拉起来走一遍真实对话
|
|
@@ -834,12 +866,13 @@ CI 跑 `npm test` 和 `npm run bench:check` 两件事。后者是防「数字悄
|
|
|
834
866
|
| DSH-SEP | 每步 ~1250 字符 guidance + ≤4200 字符快照 | 同上 | 抽取走 `api.deepseek.com` |
|
|
835
867
|
| **本插件** | **0** | **≤480 字符,且只有指针** | **无** |
|
|
836
868
|
|
|
837
|
-
##
|
|
869
|
+
## 九条被实测证伪的路
|
|
838
870
|
|
|
839
871
|
| 尝试 | 结果 |
|
|
840
872
|
|---|---|
|
|
841
873
|
| 拿「结果要多样」当选桶护栏: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 名,属于「差一点」,需要的是语义不是重排 |
|
|
842
|
-
| df 下限(把出现在超过一半文档里的词直接扔掉) | LongMemEval-S 上扫描 keep6 / keep12 / 0.7 三档:@1 与基线持平或更差(80.5% vs 81.5%),@5 一点不涨。英文虚词已经由查询侧停用词处理掉,索引侧再删一遍只会顺手动掉 `len
|
|
874
|
+
| df 下限(把出现在超过一半文档里的词直接扔掉) | LongMemEval-S 上扫描 keep6 / keep12 / 0.7 三档:@1 与基线持平或更差(80.5% vs 81.5%),@5 一点不涨。英文虚词已经由查询侧停用词处理掉,索引侧再删一遍只会顺手动掉 `len`。它的**查询侧版本**同样无效:把出现在 >35% / >20% 文档里的查询词丢掉,@1 从 88.4% 掉到 87.6% / 85.4% —— 高频词本身不脏,脏的是「求助/建议」那一类词(见「效果」一节的第 1 条实话) |
|
|
875
|
+
| 拿日期当检索信号(解析查询里的时间词做时间窗过滤) | `haystack_dates` 与 `question_date` 都在数据集里,喂进去也救不回来:500 题只有 **39 题**能抽出可用时间窗(其余要么没时间词,要么问的是「多少天前」这类间隔 —— 那 96 题现在 @1 已经 89.6%)。硬过滤的账是 **11 题救回 / 11 题误伤**,净收益 0。更根本的机制:**问题引用的是事件时间,不是 session 时间戳** —— `How many different museums did I visit in December?`(问于 2026-03-03)的 gold 全在 3 月;`Which pair of shoes did I clean last month?`(问于 05-24)的 gold 就在当天,而「上个月」那个窗里躺着 23 个干扰 session。日期侧的先验也不成立:948 个 gold 在各自 haystack 里的**新度百分位平均 0.459**(0.5 = 完全随机),纯按日期排的引擎 @1 只有 3.8%(我们 89.2%) |
|
|
843
876
|
| 伪相关反馈查询扩展(拿 top-1 的高频词再搜一遍,RM3 那一路) | 在 LongMemEval-S 500 题上前 120 题实测:RecallAny@1 从 89.2% 掉到 **48.3%**。机制:扩出来的词全是 top-1 自己的词汇,等于把「已经赢了的文档」又投了一遍票,稍有偏差的第一次命中会被自己的回声推到第一。Recall@5 只涨 0.4pt,@1 腰斩 —— 这种改动在只看 top-5 的评测里会被误认为「有效」 |
|
|
844
877
|
| 提高分数门槛 / IDF 门槛过滤误召回 | 正例最低分 4.59、反例最高分 5.54,区间重叠切不开;IDF 门槛会把 recall 从 28/28 打到 13/28 |
|
|
845
878
|
| freshness 时间衰减(学灵枢 / dsh-mneme) | 六档半衰期扫描:recall 恒为 28/28、top-1 只有一档 +1(噪声级)。库里「旧」的那批恰好是基础事实,衰减就是误伤 |
|
package/bm25.js
CHANGED
|
@@ -467,16 +467,41 @@ const EN_STOPWORDS = new Set([
|
|
|
467
467
|
]);
|
|
468
468
|
|
|
469
469
|
/**
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
470
|
+
* 求助/建议类通用词(查询侧)。这些词是「同一类对话」的框架,不是话题:
|
|
471
|
+
* 一次求助会话里几乎必有 tips / suggest / advice / thinking,于是任何一次同流派会话都会
|
|
472
|
+
* 因为共现这几个框架词而被抬分 —— 那是 genre(同一类对话)在匹配,不是 topic 在匹配。
|
|
473
|
+
* 最吃亏的是「说出偏好再问建议」的查询:它的实词(巧克力曲奇、鸡尾酒、卧室家具)才是信息,
|
|
474
|
+
* 却被一堆框架词稀释成「又一场给建议的对话」。
|
|
475
|
+
*
|
|
476
|
+
* 实测(LongMemEval-S 500 题,2026-10-15,`bench/.tmp/recon-queryclean.mjs`):
|
|
477
|
+
* · 查询侧去掉这一类词 → Any@1 88.4% → 89.2%、MRR 0.9202 → 0.9251;
|
|
478
|
+
* preference 分片 @1 46.7% → 60.0%,其余五个分片逐位不变。
|
|
479
|
+
* · 换成「按 df 比例自动丢高频查询词」的通用规则则全线下跌(df<0.35 → @1 87.6%、
|
|
480
|
+
* df<0.20 → 85.4%)⇒ 收益来自这一类词的**语义**,不是频次本身(补上一条更早的证伪:
|
|
481
|
+
* df 下限同样无效)。
|
|
482
|
+
* · 14 个词逐个消融:没有哪个词单独扛收益(去掉任意一个最多掉 1 题),15 词子集与
|
|
483
|
+
* 35 词大表同分 ⇒ 收益是「一类词」,不是一个咒语。
|
|
484
|
+
* 只收「在失败样本里真出现过」的写法:单复数不自动补齐(补了就是没量过的猜测)。
|
|
485
|
+
*/
|
|
486
|
+
const EN_REQUEST_STOPWORDS = new Set([
|
|
487
|
+
'tips', 'suggest', 'suggestions', 'recommend', 'recommendations', 'advice',
|
|
488
|
+
'think', 'thinking', 'idea', 'ideas', 'helpful', 'good', 'upcoming', 'weekend',
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* 查询词项:去重 + 丢掉英文虚词 + 丢掉求助/建议类框架词。
|
|
493
|
+
* 三道保险都保留原样,宁可留噪声也不把查询清空:
|
|
494
|
+
* · 只剩一个词项的查询(很可能那个词就是全部信息,「tips」这种就别筛了);
|
|
495
|
+
* · 全是虚词的查询(「what about this」这种),过滤后为空等于一条都召不回;
|
|
496
|
+
* · 去掉框架词后为空(「any tips?」)→ 退回只过虚词的结果,而不是清空。
|
|
474
497
|
*/
|
|
475
498
|
export function queryTerms(text) {
|
|
476
499
|
const terms = [...new Set(tokenize(text))];
|
|
477
500
|
if (terms.length < 2) return terms;
|
|
478
|
-
const
|
|
479
|
-
|
|
501
|
+
const withoutFunction = terms.filter((term) => !EN_STOPWORDS.has(term));
|
|
502
|
+
const base = withoutFunction.length ? withoutFunction : terms;
|
|
503
|
+
const kept = base.filter((term) => !EN_REQUEST_STOPWORDS.has(term));
|
|
504
|
+
return kept.length ? kept : base;
|
|
480
505
|
}
|
|
481
506
|
|
|
482
507
|
/** 查询里命中的同义键(按原文字面,大小写不敏感)。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-engramory-recall",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|
|
@@ -81,7 +81,8 @@
|
|
|
81
81
|
"bench:seam": "node bench/semantic-seam.mjs",
|
|
82
82
|
"bench:mcp": "node bench/mcp-overhead.mjs",
|
|
83
83
|
"mcp:smoke": "node scripts/mcp-smoke.mjs",
|
|
84
|
-
"release:verify": "node scripts/verify-release.mjs"
|
|
84
|
+
"release:verify": "node scripts/verify-release.mjs",
|
|
85
|
+
"bench:bridges": "node bench/bridge-coverage.mjs"
|
|
85
86
|
},
|
|
86
87
|
"bin": {
|
|
87
88
|
"engramory-recall-mcp": "mcp.js"
|