dsh-engramory-recall 0.6.1 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
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.6.2] — 2026-10-15
7
+
8
+ ### 变更
9
+
10
+ - **查询侧丢掉英文虚词**(`bm25.js` 的 `EN_STOPWORDS` + `queryTerms()`):`what / is / the / of /
11
+ did / how…` 这类语法词不再参与打分。只过滤查询、**索引侧一个词不动** —— 索引侧删词会顺带
12
+ 改掉每篇的 `len`,那等于动了长度归一,不值当。
13
+ LongMemEval-S 500 题:RecallAny@1 **86.2% → 87.8%**、Recall@5 90.7% → 91.5%、
14
+ MRR@5 0.9056 → 0.9155。自建合成基准与真库探针不变(那两把尺子的查询是中文的)。
15
+ 虚词表只收语法词,`time / name / like` 这类可能带语义的词一个不收;只剩虚词的查询退回原样,
16
+ 不清空(清空等于一条都召不回)。
17
+ - README 的 LongMemEval 章节据此重算:本插件现在比教科书 BM25 高 1.6pt @1,并写明**这 1.6pt
18
+ 全部来自这一处通用改动**,不是本插件独门机制的功劳。
19
+ - 「被实测证伪的路」加入第六条:**伪相关反馈查询扩展**(拿 top-1 的高频词再搜一遍)。
20
+ 实测 RecallAny@1 89.2% → 48.3%、Recall@5 只涨 0.4pt —— 只看 top-5 的话会被误判成有效。
21
+
6
22
  ## [0.6.1] — 2026-10-15
7
23
 
8
24
  ### 新增
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 97.0%(见下),但那只测英文词面
74
+ - **公开基准只跑了一半**:LongMemEval-S 500 题跑出 RecallAny@5 97.4%(见下),但那只测英文词面
75
75
  检索;`thoth-mem` 公布的 470 题版本(RecallAny@5 432/470、Recall@5 84.8%、NDCG@10 0.8165)
76
76
  用的是别的 harness,两边数字不可相减。真正的「跨插件排名」仍然不存在。
77
77
  - **无语义检索**:没有 embedding、没有向量库。同义改写、隐含意图要靠手工同义表覆盖,
@@ -216,34 +216,35 @@ haystack,题目分六类。
216
216
  做法:一个 session = 一篇笔记(角色前缀保留),query = 问题原文(**不读** `question_date`,
217
217
  也**不看** `has_answer` 标注),命中 = gold session 进 top-k。500 题跑 18 秒,零模型调用。
218
218
 
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% |
219
+ | 指标 | 全部 500 题 | 教科书 BM25(同分词口径) |
220
+ | --- | --- | --- |
221
+ | RecallAny@1 | **87.8%** | 86.2% |
222
+ | RecallAny@5 | **97.4%** | 97.0% |
223
+ | Recall@5(gold 覆盖率) | **91.5%** | 90.7% |
224
+ | MRR@5 | **0.9155** | 0.9056 |
225
+ | RecallAny@10 | **98.4%** | 98.2% |
226
226
 
227
227
  | 题型 | 题数 | RecallAny@1 | RecallAny@5 | Recall@5 | MRR@5 |
228
228
  | --- | --- | --- | --- | --- | --- |
229
- | knowledge-update | 78 | 94.9% | 100.0% | 97.4% | 0.971 |
230
- | multi-session | 133 | 88.0% | 96.2% | 82.6% | 0.915 |
231
- | single-session-assistant | 56 | 98.2% | 100.0% | 100.0% | 0.991 |
232
- | single-session-preference | 30 | 36.7% | 86.7% | 86.7% | 0.548 |
233
- | single-session-user | 70 | 91.4% | 98.6% | 98.6% | 0.944 |
234
- | temporal-reasoning | 133 | 82.7% | 96.2% | 87.6% | 0.882 |
229
+ | knowledge-update | 78 | 97.4% | 100.0% | 98.7% | 0.987 |
230
+ | multi-session | 133 | 88.0% | 97.0% | 85.3% | 0.919 |
231
+ | single-session-assistant | 56 | 100.0% | 100.0% | 100.0% | 1.000 |
232
+ | single-session-preference | 30 | 40.0% | 86.7% | 86.7% | 0.583 |
233
+ | single-session-user | 70 | 92.9% | 100.0% | 100.0% | 0.950 |
234
+ | temporal-reasoning | 133 | 85.0% | 96.2% | 86.6% | 0.892 |
235
235
 
236
236
  三句必须说在前面的实话:
237
237
 
238
- 1. **这份数据量不出本插件的长处。** 同一份数据我们用教科书 BM25(同分词口径)也跑了一遍,
239
- 五个指标**一模一样**。原因很实在:LongMemEval 是英文的,session 没有标题、没有别名、
240
- 没有中文口语,本插件多出来的那几样在这里基本没被激活。它只说明「英文词面检索不输常规写法」,
241
- 机制到底值多少,回上面两把尺子看。
238
+ 1. **这 1.6 个点的来源要说清楚。** 本插件比教科书 BM25 高 1.6pt @1,差的那部分**全部**来自一处通用改动:
239
+ 查询侧丢掉英文虚词(`what / is / the / of / did / how…`)。是我们自己的机制(同义表、别名、
240
+ 字段加权、中文分词)在这份数据上没被激活 —— LongMemEval 是英文的,session 没有标题、
241
+ 没有别名、没有中文口语,它们在这里没事可做。所以这张表证明的是「英文词面检索不输、且
242
+ 基础卫生做到位」,**不是**「本插件的独门设计更好」。机制值多少,回上面两把尺子看。
242
243
  2. **别拿它跟别家数字直接相减。** thoth-mem 公布过 LongMemEval-S 470 题、RecallAny@5 91.9%。
243
244
  我们用的是 cleaned 版 500 题 + 自己的 harness,题集版本、命中口径、读不读日期都未必一样,
244
245
  相减没有意义。能担保的是:harness 与逐题结果都在仓库里(`bench/longmemeval.mjs`、
245
246
  `bench/longmemeval-results.json`),可以逐题核。
246
- 3. **最差的是偏好题**(36.7%@1):「我喜欢什么」这类问题本身没什么词面抓手。这是零模型路线的
247
+ 3. **最差的是偏好题**(40.0%@1):「我喜欢什么」这类问题本身没什么词面抓手。这是零模型路线的
247
248
  边界 —— 偏好类想做得更好,要么开可选的 `llm.js`,要么就老老实实把偏好写进标题。
248
249
 
249
250
  复算:
@@ -625,10 +626,11 @@ CI 跑 `npm test` 和 `npm run bench:check` 两件事。后者是防「数字悄
625
626
  | DSH-SEP | 每步 ~1250 字符 guidance + ≤4200 字符快照 | 同上 | 抽取走 `api.deepseek.com` |
626
627
  | **本插件** | **0** | **≤480 字符,且只有指针** | **无** |
627
628
 
628
- ## 五条被实测证伪的路
629
+ ## 六条被实测证伪的路
629
630
 
630
631
  | 尝试 | 结果 |
631
632
  |---|---|
633
+ | 伪相关反馈查询扩展(拿 top-1 的高频词再搜一遍,RM3 那一路) | 在 LongMemEval-S 500 题上前 120 题实测:RecallAny@1 从 89.2% 掉到 **48.3%**。机制:扩出来的词全是 top-1 自己的词汇,等于把「已经赢了的文档」又投了一遍票,稍有偏差的第一次命中会被自己的回声推到第一。Recall@5 只涨 0.4pt,@1 腰斩 —— 这种改动在只看 top-5 的评测里会被误认为「有效」 |
632
634
  | 提高分数门槛 / IDF 门槛过滤误召回 | 正例最低分 4.59、反例最高分 5.54,区间重叠切不开;IDF 门槛会把 recall 从 28/28 打到 13/28 |
633
635
  | freshness 时间衰减(学灵枢 / dsh-mneme) | 六档半衰期扫描:recall 恒为 28/28、top-1 只有一档 +1(噪声级)。库里「旧」的那批恰好是基础事实,衰减就是误伤 |
634
636
  | 时间乘性加权(学 hindsight) | 五档扫描净收益为零,`w ≥ 0.1` 起反而伤对照组 |
package/bm25.js CHANGED
@@ -423,9 +423,45 @@ export function tokenize(text) {
423
423
  return out;
424
424
  }
425
425
 
426
- /** 查询词项去重(打分时同一词项不重复计权)。 */
426
+ /**
427
+ * 英文虚词:查询里带着它们不加分,只稀释真正有区分度的词。
428
+ *
429
+ * 为什么只在**查询侧**过滤、索引侧一个词不动:索引侧删词会同时改掉每篇的
430
+ * `len`,那等于顺手动了长度归一 —— 长度归一刚在 2026-10-15 为 `TITLE_EXTRA`
431
+ * 的事校准过,不该为了这个另起一轮。实测也支持这个选择:查询+索引都过滤
432
+ * 90.0%、只过滤查询 90.8%(LongMemEval-S 前 120 题)。
433
+ *
434
+ * 数据来源:LongMemEval-S 500 题(英文),86.2% → 87.8% RecallAny@1、
435
+ * Recall@5 90.7% → 91.7%、MRR@5 0.904 → 0.914。中文查询几乎不受影响
436
+ * (中文词不进这张表)。
437
+ *
438
+ * 只收「语法词」:疑问词、be/do/have 的变位、介词、代词、限定词。
439
+ * 刻意**不收**任何可能带语义的词(如 `time`、`name`、`like`)——
440
+ * 那类词在短查询里往往是唯一的抓手,删了就是漏检。
441
+ */
442
+ const EN_STOPWORDS = new Set([
443
+ 'the', 'a', 'an', 'and', 'or', 'but', 'if', 'then', 'than', 'that', 'this', 'these', 'those',
444
+ 'what', 'which', 'who', 'whom', 'whose', 'when', 'where', 'why', 'how',
445
+ 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'am',
446
+ 'do', 'does', 'did', 'doing', 'done',
447
+ 'have', 'has', 'had', 'having',
448
+ 'can', 'could', 'will', 'would', 'shall', 'should', 'may', 'might', 'must',
449
+ 'of', 'in', 'on', 'at', 'to', 'from', 'by', 'with', 'for', 'about', 'as', 'into', 'over', 'after', 'before',
450
+ 'it', 'its', 'my', 'me', 'we', 'our', 'you', 'your', 'he', 'him', 'she', 'her', 'they', 'them', 'their',
451
+ 'there', 'here', 'so', 'such', 'no', 'not', 'only', 'just', 'also', 'very', 'much', 'many', 'more', 'most', 'some', 'any', 'all',
452
+ ]);
453
+
454
+ /**
455
+ * 查询词项:去重 + 丢掉英文虚词。
456
+ * 两个例外都保留原样,宁可留噪声也不把查询清空:
457
+ * · 只剩一个词项的查询(很可能那个词就是全部信息);
458
+ * · 全是虚词的查询(「what about this」这种),过滤后为空等于一条都召不回。
459
+ */
427
460
  export function queryTerms(text) {
428
- return [...new Set(tokenize(text))];
461
+ const terms = [...new Set(tokenize(text))];
462
+ if (terms.length < 2) return terms;
463
+ const kept = terms.filter((term) => !EN_STOPWORDS.has(term));
464
+ return kept.length ? kept : terms;
429
465
  }
430
466
 
431
467
  /** 查询里命中的同义键(按原文字面,大小写不敏感)。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-engramory-recall",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
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",