AskaEth
|
b1d51d8de2
|
fix(memory): N2 判据改为白名单形态匹配(结构上不再吞实体)
## 上一版为什么没修完(review round2 举证)
上一版把 N2 实现成**黑名单剥离**:`strings.ReplaceAll` 扫整句、把「情绪词 + 时间词 +
主语 + 程度 + 虚词 + 标点」统统删掉,**残差为空才 defer**。它的要害是剥离表里混进了
**本身就能当实体的词** —— 人称词(`我`/`我们`/`他`/`咱`/`自己`)与可作内容的单字
(`好`/`会`/`要`/`想`/`对`/`在`/`有`/`是`/`时`/`得`/`和`)。宾语恰好由这些词构成时,
实体被吞、残差为空 ⇒ **仍判 defer**:
主人不开心时会想我 → residual="" → defer ✗(持久习惯进临时层,7 天后物理删除)
主人难过时会想我们 → residual="" → defer ✗
主人心情不好时会想他 → residual="" → defer ✗
主人不开心时会想和好 → residual="" → defer ✗(`和`+`好` 两个词项各吃掉一半)
主人难过时会想妈妈 → residual="妈妈" → create ← 对照:宾语没被表吃掉
⇒ 这证伪了上一版代码里声称的不变量(「还剩一个实体就必须放行」),也说明**黑名单这
条路走不到头**:「实体」是开放集合(想我/想我们/想他/想和好/想妈妈/听音乐/咬指甲/
养猫/…),永远列不全。
## 新判据:白名单**形态**匹配
「纯情绪句」的**形态是封闭的**,据此改成「整句恰好落在形态里才 defer,其余一律放行」:
[主语]? [时间]? [程度]* 情绪核心 [程度|否定|情绪|语气]* 余部须只剩标点/空白
实现在 `retentionEmotionFormRest`(唯一判据点):
- 主语与时间词**只允许出现在句首**、各至多一次;
- 情绪核心**必须**消费到一个情绪词,否则 sawEmotion=false ⇒ 放行;
- 核心之后**只**允许「程度 / 否定 / 情绪 / 语气」四类修饰(`retentionEmotionSuffixTokens`);
- 最后要求**整句消费干净**(余部只剩标点/空白);
- 各段一律**最长词优先**(否则 `不` 会抢在 `不错` 前、`好` 会拆掉 `好累`)。
## 为什么它在**结构上**不可能再把带实体的句子判成 defer
1. 判 defer 的门槛是**整句被封闭表消费干净**。表里**没有承载内容的词** —— 没有动词、
名词、量词。任何一个形态外的字(`想`/`会`/`时`/`和`/`我`/`妈妈`/`听音乐`)都无法被
消费,必然留在余部 ⇒ 余部非空 ⇒ **放行**。
2. 主语/时间词**限死句首**:`我`/`他`/`我们` 出现在核心之后时**没有**任何词项能吃掉它
(后缀表里没有它们)—— 这正是上一版翻车的地方,现在从结构上排除了。
3. 失败模式从「**未知实体被吞**(危险、不可逆)」翻成「**未知情绪句没被认出**(安全:
多留,有 T1 与整理兜底)」。方向与 design/23 §18.3 的 fail-safe「宁可多留,不可
误弃」一致。
## 配套测试
- 正例清单补 5 条**对抗形态**(宾语由「能当实体的词」构成):`主人不开心时会想我` /
`主人难过时会想我们` / `主人心情不好时会想他` / `主人不开心时会想和好` /
`主人难过时会想妈妈` —— 与上一轮的 5 条(听音乐/咬指甲/养猫/喜欢吃甜的/找朋友聊天)
合计 10 条「带宾语的情绪句」正例;
- `TestExtractionRetention_EmotionWithObjectIsNotDeferred` 改为断言**余部**(未被形态
消费掉的部分)+最终结论:纯情绪 ⇒ 余部为空 ⇒ defer;带宾语 ⇒ 余部非空(
`时会想我` / `时会听音乐` / …)⇒ 放行。**「带宾语 ⇒ 余部非空」这条不变量现在是被
判据本身保证的**,不再是靠一张表的完备性。
## 修前必红 / 修后绿(原样输出)
修前(把 `judgeRetentionEmotion` 换成 commit 979d1cf 的黑名单实现,逐字):
```
--- FAIL: TestExtractionRetention_EmotionWithObjectIsNotDeferred (0.00s)
--- FAIL: …/主人难过时会想我们 extract_retention_test.go:122: judgeExtractionRetention("主人难过时会想我们") = "n2_emotion", wantEmotionDefer=false
--- FAIL: …/主人心情不好时会想他 … = "n2_emotion", wantEmotionDefer=false
--- FAIL: …/主人不开心时会想和好 … = "n2_emotion", wantEmotionDefer=false
--- FAIL: …/主人不开心时会想我 … = "n2_emotion", wantEmotionDefer=false
--- FAIL: TestExtractionRetention_PositivesAreNeverDeferred (0.00s)
--- FAIL: …/情绪+代词宾语(想我) 正例 "主人不开心时会想我" 被拦成 "n2_emotion" —— 把真记忆丢进临时层
--- FAIL: …/情绪+代词宾语(想他) … --- FAIL: …/(想我们) … --- FAIL: …/(想和好) …
FAIL
```
修后:`TestExtractionRetention_{UpperCaseASCII,NegativesAreDeferred,
EmotionWithObjectIsNotDeferred,PositivesAreNeverDeferred}` 全 PASS(共 44 个子例)。
验收:go build ./... + go vet ./... + go test -count=1 ./... 全绿。
|
2026-10-13 20:47:59 +08:00 |
|
AskaEth
|
979d1cfbb2
|
fix(memory): 留存规则 N2 改按实体判定(持久习惯不再被误弃)
## 改了什么(行为变更)
`judgeRetentionEmotion` 原判据是「含情绪词 ∧ 无阿拉伯数字 ∧ ≤24 rune」—— 它把
design/03 §10.5 对 N2 的定义「**词项 + 无实体**」里的「无实体」这一维**顶替成了
「无数字」**,方向恰好落在 fail-safe 的反面。
反例(review 走 `judgeIngest` 真链路复现):
主人心情不好时会听音乐 → 旧判据 defer / t0_low_value
主人焦虑时会咬指甲 → 旧判据 defer / t0_low_value
这两条是**稳定习惯**(正向清单),判 defer 即 7 天后从所有读路径消失(读侧时效闸,
design/03 §9.6)、随后被后台清扫**物理删除**(design/23 §19)—— **不可逆**,与
mission 文本写死的 fail-safe「宁可多留,不可误弃」相反。
新判据(设计与 mission 允许,不改设计):**剥掉情绪词 / 时间词 / 主语 / 程度词 /
虚词 / 标点之后,余下为空才判 defer**;只要还剩一个实体(听音乐 / 咬指甲 / 养猫
/ 找朋友聊天)就放行。实现是 `retentionResidual` + 一张**长词优先**的剥离表
(长词优先是必须的:先剥「好」会把「好累」拆成光杆「累」而永远剥不掉)。
配套:
- 剥离表新增「不错」(情绪评价,整词)与光杆「累」—— 只靠虚词表里的「不」会把
「心情不错」剥成「错」、把「主人今天真的特别累」剥成「累」而漏判;
- 去掉 N2 专属的 24-rune 上限(判据恒是「无实体」,长度由全局 40-rune 上限兜住);
- 数字不再单独设防:它不在剥离表里 ⇒ 必然留在残差里 ⇒ 一律放行(与旧
hasRetentionDigit 等价,但由同一条判据统一给出)。
## 为什么这是 p1 而不是精度问题
判错的方向不对称:漏判只是噪声留进长期层(有 T1 与整理兜底),误弃是**不可逆的
数据丢失**。因此 N2 宁可漏判,绝不误弃。
## 配套测试(此前这条缝**完全没被覆盖**)
- 正例清单新增 5 条「带宾语的情绪句」:`主人心情不好时会听音乐` /
`主人焦虑时会咬指甲` / `主人心情低落时会养猫` / `主人压力大的时候喜欢吃甜的` /
`主人难过时会去找朋友聊天`(旧正例里没有这一类样本,所以旧缺陷一路全绿);
- 新增 `TestExtractionRetention_EmotionWithObjectIsNotDeferred`:逐条断言**残差**
与最终结论 —— 带宾语 ⇒ 不 defer、纯情绪 ⇒ defer,两者必须给出相反结论;
- 新增反例 `主人今天真的特别累`(纯情绪、无实体)钉住「不是靠长度躲过」。
修前必红 / 修后绿(把 N2 退回旧判据后原样输出):
正例 "主人心情不好时会听音乐" 被拦成 "n2_emotion" —— 那是把真记忆丢进临时层
正例 "主人焦虑时会咬指甲" 被拦成 "n2_emotion" —— …
judgeExtractionRetention("主人心情不好时会听音乐") = "n2_emotion", wantEmotionDefer=false
--- FAIL: TestExtractionRetention_PositivesAreNeverDeferred
--- FAIL: TestExtractionRetention_EmotionWithObjectIsNotDeferred
还原后:TestExtractionRetention_{UpperCaseASCII,EmotionWithObjectIsNotDeferred,
NegativesAreDeferred,PositivesAreNeverDeferred} 全 PASS。
验收:go build ./... + go vet ./... + go test -count=1 ./... 全绿。
|
2026-10-13 20:37:57 +08:00 |
|