From a3ae98ef2e7613c912501e9bcca46c01e400611b Mon Sep 17 00:00:00 2001 From: AskaEth Date: Tue, 13 Oct 2026 21:06:33 +0800 Subject: [PATCH] =?UTF-8?q?docs(memory):=20N2=20=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E4=B8=89=E5=A4=84=E6=9B=B4=E6=AD=A3=EF=BC=88=E7=A4=BA=E4=BE=8B?= =?UTF-8?q?=20/=20=E6=A0=87=E7=82=B9=E5=AE=B9=E5=BF=8D=20/=20=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E9=94=81=EF=BC=89+=20=E9=80=90=E6=9D=A1=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 应答 review round4 的 p2#1。**只改注释**,行为代码、后缀表 golden、25 条语料、 主语表一字未动。三处都指向**更严格**的表述(无安全影响),且每一处都先实测再下笔 —— 上一轮的教训正是「注释里的例子自己没实测过」。 ## ① 示例 `生气得很` 与实现不符 → 换成实测覆盖的示例 原句举例「生气**得很**」,但 `得` **不在**后缀表里: 生气得很 → rest="得很" → rule=""(放行) 与它并列的另两个例子矛盾。换成实测 `rest=""` 的: 心情不错 → rest="" → rule="n2_emotion" 不开心 → rest="" → rule="n2_emotion" 心情很好 → rest="" → rule="n2_emotion" 并把反例留在原位(显式标注「反例」): 生气得很 → rest="得很" → rule=""(放行) ## ② 「任何不在表中的字符 ⇒ 放行」对标点不绝对 → 补齐容忍面 标点与空白是被 `onlyRetentionPunctuation` 收尾清掉的,因此原表述漏了这半边: 心情不错! → rest="" → rule="n2_emotion" ← 实测 defer(标点被容忍) 现改为「任何一个**非标点 / 非空白**的字符只要不在词表里,就会留在余部」。 ## ③ 「主语/时间词只允许出现在句首」不准确 → 按实现写「位置锁」 时间词也可**紧跟主语之后**。现按实现写成: 主语只在**位置 0** 被消费一次;时间词只在**紧随其后**被消费一次(无主语时它就是首词)。 实测: 今天好累 → rest="" → rule="n2_emotion" 主人今天好累 → rest="" → rule="n2_emotion" 今天主人好累 → rest="今天主人好累" → saw=false → rule=""(放行) 「指人/指时实体永远不可能落到宾语位置」这条**支柱本身不变**(review round4 已核), 变的只是对**位置**的准确描述。 ## ④ 顺手同批修掉的同句不准确:`retentionEmotionTimeWords` 的文档 它与 ③ 是**同一句主张的另一个落点**(「只允许出现在句首的时间词」),留着就等于把同一 处不准确留在盘上。已改成与 ③ 一致的表述并附同一条实测。 (`consumeRetentionPrefixes` 的「反复在句首消费」指的是**当前余部**的开头,是对辅助函数 自身语义的描述,不是文法主张,未动。) 验收:go build ./... + go vet ./... + go test -count=1 ./... 全绿; TestExtractionRetention 六个用例 69 个子例全 PASS。 --- internal/runtime/memory/extract_retention.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/runtime/memory/extract_retention.go b/internal/runtime/memory/extract_retention.go index ac4aaa5..23b751b 100644 --- a/internal/runtime/memory/extract_retention.go +++ b/internal/runtime/memory/extract_retention.go @@ -150,7 +150,9 @@ func judgeRetentionChatter(text string) retentionRule { // **为什么它在结构上不可能把带实体的句子判成 defer**(不是"概率很低",是结构性): // // 1. 匹配要求**整句被消费干净**(`retentionEmotionFormRest` 返回空余部)。任何 -// 一个字符只要不在封闭表里,就会留在余部 ⇒ 判不出空 ⇒ **放行**。 +// 一个**非标点 / 非空白**的字符只要不在词表里,就会留在余部 ⇒ 判不出空 ⇒ +// **放行**。(**标点与空白是被容忍的**:它们由 `onlyRetentionPunctuation` 在收尾 +// 时清掉,所以 `心情不错!` 实测 `rest=""` ⇒ 仍判 defer。) // // 2. **位置锁定 + 核心之后只允许四类修饰** —— 这才是真正保安全的一条。 // (早先这里写的是「封闭表里没有承载内容的词」,**那句被 reviewer 证伪** @@ -158,12 +160,16 @@ func judgeRetentionChatter(text string) retentionRule { // `压力大` 是动宾,而主语表里的 `主人`/`我`/`他` 本身就是指人实体。) // // 真正的机制是**位置**而不是"表里没有实词": -// - 主语 / 时间词**只允许出现在句首**、各至多一次 ⇒ `主人`/`我`/`你`/`他`/`今天` +// - **位置锁定**(按实现写):主语只在**位置 0** 被消费一次;时间词只在**紧随其后** +// 被消费一次(无主语时它就是首词)。两者都**没有**「后面还能再出现」的缝 —— +// `主人今天好累` 匹配(`rest=""`),而 `今天主人好累` 连核心都认不出 +// (`rest="今天主人好累"`, `saw=false` ⇒ 放行)。⇒ `主人`/`我`/`你`/`他`/`今天` // 这些**指人 / 指时的实体在文法里永远不可能落到宾语位置**(上一版正是死在 // 这里:`会想我` 的 `我` 被当成主语剥掉了); // - 核心之后只允许「程度 / 否定 / 情绪 / 语气」四类 token,而它们**只允许紧跟 // 在情绪核心之后** —— 核心本身必须先被消费掉,因此那一段在语义上只能是 -// **情绪的修饰**(「心情**不错**」「**不**开心」「生气**得很**」), +// **情绪的修饰**(「心情**不错**」「**不**开心」「心情**很好**」,三条均实测 +// `rest=""`;反例「生气**得很**」的 `得` 不在表里 ⇒ `rest="得很"` ⇒ 放行), // 装不下一个宾语短语。核心之后一旦出现四类之外的东西(`想`/`会`/`时`/`和`/ // `听音乐`/`妈妈`),**必然消费不掉** ⇒ 余部非空 ⇒ 放行。 // @@ -408,7 +414,9 @@ func retentionEmotionSuffixTokens() []string { return tokens } -// retentionEmotionTimeWords 是**只允许出现在句首**的时间词(「今天好累」)。 +// retentionEmotionTimeWords 是**只允许在「主语之后」出现一次**的时间词:无主语时 +// 它就是首词(「今天好累」),有主语时紧跟主语(「主人今天好累」)—— 之外的位置一律 +// 不匹配(`今天主人好累` 实测 `rest="今天主人好累"`, `saw=false` ⇒ 放行)。 // 它们让一句情绪陈述显形为「今天怎么样」,本身不含实体。 func retentionEmotionTimeWords() []string { return []string{