dsh-engramory-recall 0.9.0 → 0.10.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 +33 -0
- package/README.md +19 -12
- package/bm25.js +31 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
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.10.0] — 2026-10-15
|
|
7
|
+
|
|
8
|
+
### 改进
|
|
9
|
+
|
|
10
|
+
- **查询侧再丢掉一类「求助/建议框架词」**(`bm25.js` 的 `EN_REQUEST_STOPWORDS`,14 个:
|
|
11
|
+
`tips / suggest / suggestions / recommend / recommendations / advice / think / thinking /
|
|
12
|
+
idea / ideas / helpful / good / upcoming / weekend`)。这些词是「同一类对话」的框架,不是话题:
|
|
13
|
+
一次求助会话里几乎必有它们,于是任何同流派的会话都会因为共现框架词被抬分 —— 那是 genre 在
|
|
14
|
+
匹配,不是 topic 在匹配;最吃亏的是「先说出偏好、再问建议」的查询,它的实词被框架词稀释成
|
|
15
|
+
「又一场给建议的对话」。
|
|
16
|
+
- **实测(LongMemEval-S 500 题)**:RecallAny@1 **88.4% → 89.2%**、MRR@5 **0.9202 → 0.9251**、
|
|
17
|
+
RecallAny@10 98.8% → 99.0%;最弱的偏好分片 @1 **46.7% → 60.0%**(MRR 0.627 → 0.708),
|
|
18
|
+
其余五个分片逐位不变。
|
|
19
|
+
- **词表只收「在失败样本里真出现过」的写法**,单复数不自动补齐 —— 补了就是没量过的猜测。
|
|
20
|
+
- 逐个消融:没有哪个词单独扛收益(去掉任意一个最多掉 1 题);只留子集与大表同分
|
|
21
|
+
⇒ 收益属于「一类词」,不是一个咒语。
|
|
22
|
+
- 对照实验:换成「按 df 比例自动丢高频查询词」的通用规则反而全线下跌(df<0.35 → @1 87.6%、
|
|
23
|
+
df<0.20 → 85.4%)⇒ 收益来自这一类词的**语义**,不是频次本身。
|
|
24
|
+
- 三道保险保证查询不被清空:单关键词查询不筛;全是虚词的查询退回原样;去掉框架词后为空
|
|
25
|
+
(`any tips` 这种)退回只过虚词的结果。
|
|
26
|
+
- 真库探针与合成基准:同一语料上新旧代码六项数字逐位相同(探针语料本身在此期间被别的会话
|
|
27
|
+
改过,指纹已重设基线);`npm run bench:check` 全部指标在容差内。
|
|
28
|
+
- README 的「八条被实测证伪的路」→「**九条**」,新增一条 **「拿日期当检索信号」**:
|
|
29
|
+
LongMemEval-S 500 题只有 39 题能抽出可用时间窗;硬过滤 11 题救回 / 11 题误伤;
|
|
30
|
+
948 个 gold 的新度百分位平均 0.459 ⇒ 日期侧没有先验;纯按日期排的引擎 @1 仅 3.8%。
|
|
31
|
+
机制写在表里:问题引用的是事件时间,不是 session 时间戳。
|
|
32
|
+
- README 的 LongMemEval 表、分题型表、B 对照表与「三句实话」按新数字更新(2.2pt → 3.0pt)。
|
|
33
|
+
|
|
34
|
+
### 测试
|
|
35
|
+
|
|
36
|
+
- `test/recall.test.mjs` 增加 2 个用例(框架词被丢而实词不丢;全是框架词时退回而不清空),
|
|
37
|
+
合计 213 例。
|
|
38
|
+
|
|
6
39
|
## [0.9.0] — 2026-10-15
|
|
7
40
|
|
|
8
41
|
### 新增
|
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
|
|
|
@@ -152,6 +152,8 @@ CI 里再跑一遍 `npm run bench:check` 跟 `bench/expectations.json` 对账。
|
|
|
152
152
|
|---|---|---|
|
|
153
153
|
| 这份合成卷子 | hit@1 49.9% · hit@4 66.6% | hit@1 50.4% · hit@4 67.5% |
|
|
154
154
|
| LongMemEval-S 500 题 | @1 87.8% · @5 97.4% · 覆盖 91.5% | @1 88.4% · @5 98.2% · 覆盖 92.5% |
|
|
155
|
+
|
|
156
|
+
(这张表单独量 B。查询侧后来又加了一类框架词过滤,@1 从 88.4% 再涨到 **89.2%** —— 见 LongMemEval 一节的第 1 条实话。)
|
|
155
157
|
| 真库对照 C(中立那把) | top1 141/181 · recall@4 168/181 | top1 150/181 · recall@4 177/181 |
|
|
156
158
|
|
|
157
159
|
机制:同一个词在长会话、长笔记里出现得更多,`len/avgLen` 这个惩罚就变成「谁长谁吃亏」,
|
|
@@ -240,26 +242,28 @@ haystack,题目分六类。
|
|
|
240
242
|
|
|
241
243
|
| 指标 | 全部 500 题 | 教科书 BM25(同分词口径) |
|
|
242
244
|
| --- | --- | --- |
|
|
243
|
-
| RecallAny@1 | **
|
|
245
|
+
| RecallAny@1 | **89.2%** | 86.2% |
|
|
244
246
|
| RecallAny@5 | **98.2%** | 97.0% |
|
|
245
247
|
| Recall@5(gold 覆盖率) | **92.5%** | 90.7% |
|
|
246
|
-
| MRR@5 | **0.
|
|
247
|
-
| RecallAny@10 | **
|
|
248
|
+
| MRR@5 | **0.9251** | 0.9056 |
|
|
249
|
+
| RecallAny@10 | **99.0%** | 98.2% |
|
|
248
250
|
|
|
249
251
|
| 题型 | 题数 | RecallAny@1 | RecallAny@5 | Recall@5 | MRR@5 |
|
|
250
252
|
| --- | --- | --- | --- | --- | --- |
|
|
251
253
|
| knowledge-update | 78 | 98.7% | 100.0% | 99.4% | 0.994 |
|
|
252
254
|
| multi-session | 133 | 88.0% | 98.5% | 86.0% | 0.920 |
|
|
253
255
|
| single-session-assistant | 56 | 100.0% | 100.0% | 100.0% | 1.000 |
|
|
254
|
-
| single-session-preference | 30 |
|
|
256
|
+
| single-session-preference | 30 | 60.0% | 90.0% | 90.0% | 0.708 |
|
|
255
257
|
| single-session-user | 70 | 92.9% | 100.0% | 100.0% | 0.950 |
|
|
256
258
|
| temporal-reasoning | 133 | 85.0% | 97.0% | 88.5% | 0.894 |
|
|
257
259
|
|
|
258
260
|
三句必须说在前面的实话:
|
|
259
261
|
|
|
260
|
-
1. **这
|
|
261
|
-
查询侧丢掉英文虚词(`what / is / the / of / did / how
|
|
262
|
-
|
|
262
|
+
1. **这 3.0 个点的来源要说清楚。** 本插件比教科书 BM25 高 3.0pt @1,差的那部分**全部**来自三处通用改动:
|
|
263
|
+
查询侧丢掉英文虚词(`what / is / the / of / did / how…`);查询侧再丢掉一类「求助/建议框架词」
|
|
264
|
+
(`tips / suggest / recommend / advice / think / good / weekend…` 共 14 个,这一处单独值
|
|
265
|
+
**+0.8pt @1**、MRR +0.005,最弱的偏好分片 @1 从 46.7% 抬到 60.0%);以及把长度归一常数从教科书的
|
|
266
|
+
0.75 调到 0.4。三处都与中文无关 —— 是我们自己的机制(同义表、别名、字段加权、中文分词)在这份数据上
|
|
263
267
|
没被激活:LongMemEval 是英文的,session 没有标题、没有别名、没有中文口语,它们在这里没事可做。
|
|
264
268
|
B 的取值不是在这份数据上单独挑的(挑法见上:三把尺子一致,真库中立对照 +9/+9)。所以这张表
|
|
265
269
|
证明的是「英文词面检索不输、且基础卫生做到位」,**不是**「本插件的独门设计更好」。机制值多少,
|
|
@@ -268,8 +272,10 @@ haystack,题目分六类。
|
|
|
268
272
|
我们用的是 cleaned 版 500 题 + 自己的 harness,题集版本、命中口径、读不读日期都未必一样,
|
|
269
273
|
相减没有意义。能担保的是:harness 与逐题结果都在仓库里(`bench/longmemeval.mjs`、
|
|
270
274
|
`bench/longmemeval-results.json`),可以逐题核。
|
|
271
|
-
3.
|
|
272
|
-
|
|
275
|
+
3. **最差的仍是偏好题**(60.0%@1,改动前 46.7%):「我喜欢什么」这类问题本身没什么词面抓手,
|
|
276
|
+
而且它的 16 个 miss 里有 11 个掉在第 2~4 名 —— 全是「另一场给建议的对话」,是同流派干扰而不是
|
|
277
|
+
够不着。丢掉求助框架词能把 @1 抬 13.3pt,剩下的差距才是零模型路线的边界:偏好类想做得更好,
|
|
278
|
+
要么开可选的 `llm.js`,要么就老老实实把偏好写进标题。
|
|
273
279
|
|
|
274
280
|
复算:
|
|
275
281
|
|
|
@@ -834,12 +840,13 @@ CI 跑 `npm test` 和 `npm run bench:check` 两件事。后者是防「数字悄
|
|
|
834
840
|
| DSH-SEP | 每步 ~1250 字符 guidance + ≤4200 字符快照 | 同上 | 抽取走 `api.deepseek.com` |
|
|
835
841
|
| **本插件** | **0** | **≤480 字符,且只有指针** | **无** |
|
|
836
842
|
|
|
837
|
-
##
|
|
843
|
+
## 九条被实测证伪的路
|
|
838
844
|
|
|
839
845
|
| 尝试 | 结果 |
|
|
840
846
|
|---|---|
|
|
841
847
|
| 拿「结果要多样」当选桶护栏: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
|
|
848
|
+
| 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 条实话) |
|
|
849
|
+
| 拿日期当检索信号(解析查询里的时间词做时间窗过滤) | `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
850
|
| 伪相关反馈查询扩展(拿 top-1 的高频词再搜一遍,RM3 那一路) | 在 LongMemEval-S 500 题上前 120 题实测:RecallAny@1 从 89.2% 掉到 **48.3%**。机制:扩出来的词全是 top-1 自己的词汇,等于把「已经赢了的文档」又投了一遍票,稍有偏差的第一次命中会被自己的回声推到第一。Recall@5 只涨 0.4pt,@1 腰斩 —— 这种改动在只看 top-5 的评测里会被误认为「有效」 |
|
|
844
851
|
| 提高分数门槛 / IDF 门槛过滤误召回 | 正例最低分 4.59、反例最高分 5.54,区间重叠切不开;IDF 门槛会把 recall 从 28/28 打到 13/28 |
|
|
845
852
|
| 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.10.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",
|