## 改了什么(行为变更)
`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 ./... 全绿。
404 lines
20 KiB
Go
404 lines
20 KiB
Go
package memory
|
||
|
||
import (
|
||
"sort"
|
||
"strings"
|
||
)
|
||
|
||
// 本文件是**提取留存规则闸**(design/03 §10,即 design/23 §15.1 D2 点名的
|
||
// 「另立规格」)的 A 级实现:**纯规则 + 词项匹配**,对一条**刚落库的**新记忆判
|
||
// 「它是不是结构上显然不值得长期留存」(design/03 §10.5 的反例清单 N1~N5)。
|
||
//
|
||
// 它回应主人的两条原话:「**收窄提取器,确实要给他一个主动式的记忆留存规则,
|
||
// 不能想到什么都往记忆里写**」与「**临时记忆到期删除**」。
|
||
//
|
||
// 🔴 落点硬约束(design/03 §10.3):**不许新造第二条写路径**。本闸**不新开入口**,
|
||
// 而是接进既有的唯一写路径 —— `judgeIngest`(preinsert_judge.go)在**候选比对
|
||
// 之前**先过一遍这里,命中即返回与 design/23 §18.1 第五态**同一枚举 `defer`、
|
||
// 同一落点 `expires_at`**。于是「提取得宽」由**同一条**入库路径收敛,不存在
|
||
// 绕过闸的写口;命中者**不进 T1**(§10.6:规则闸拦下的不进 T1,避免「模型推翻
|
||
// 规则」的架构回归)。
|
||
//
|
||
// 判据为什么必须是纯函数而不是提示词(design/03 §10.2):现行全仓唯一的留存判据
|
||
// 是一句自然语言(`llmextractor.go` 的 extractSystemPrompt),那**违反 AGENTS §9
|
||
// 「判据靠元数据,不靠提示词」**,且**不可验收**(提示词产出是自由文本,测试只能
|
||
// 断言「模型有没有听话」)。B 级提示词加固仍在做(正例 + 反例清单进 prompt,
|
||
// 见 llmextractor.go),但**验收防线在本文件**。
|
||
//
|
||
// 🔴 **fail-safe 方向 = 宁可多留,不可误弃**(design/23 §18.3)。因此每一条规则都
|
||
// 写得**保守**:宁可有噪声漏过闸(那条走 T1 / 长期层),也不把一条真记忆判成临时
|
||
// 层(到期会被删)。落实为两道**护栏**:
|
||
//
|
||
// 1. **正向清单一票否决**:内容里出现任何「长期事实 / 稳定偏好 / 关系里程碑 /
|
||
// 项目约定」的信号(复用 tidyHabitMarkers 的「持久」词族 + 本文件的正向词),
|
||
// 直接放行 —— 「主人喜欢用喵打招呼」虽然短,但它有「喜欢」,不是寒暄;
|
||
// 2. **显式「记住」一票否决**:内容里出现「记住 / 记下 / 别忘了」时放行 ——
|
||
// 主人明确要求记的东西,闸不许替他改成临时记忆。
|
||
//
|
||
// 一处**刻意不实现**(如实登记,不是遗漏):design/03 §10.5 的 **N6(助手自己的
|
||
// 输出 / 发言人归属)**。它是**来源属性**(这句话是谁说的),不是内容属性;本闸
|
||
// 只拿到落库条目(结构化溯源里没有「谁说的」这一位),无法机器判定。归属由 B 级
|
||
// prompt 的负例承担(「不要记助手自己说过的话」),上游提取侧若要硬判,需另立
|
||
// 结构化字段 —— 那超出本切片授权。**N7(不确定推断)**同理交给 T1(§10.5 明写
|
||
// 「拿不准交第五态」),不进本闸。
|
||
|
||
// retentionRule 是一条提取留存反例规则的匹配结果(留痕/日志用;`retentionNone`
|
||
// 表示「不是显然不值得记的」)。
|
||
type retentionRule string
|
||
|
||
const (
|
||
// retentionNone 表示没有任何反例规则命中 ⇒ 照常走既有判定(可能长期留存)。
|
||
retentionNone retentionRule = ""
|
||
// retentionChatter 对 design/03 §10.5 的 **N1 寒暄 / 致谢**。
|
||
retentionChatter retentionRule = "n1_chatter"
|
||
// retentionEmotion 对应 **N2 情绪波动**。
|
||
retentionEmotion retentionRule = "n2_emotion"
|
||
// retentionOneOffAsk 对应 **N3 一次性琐事**(祈使 + 无持续实体)。
|
||
retentionOneOffAsk retentionRule = "n3_one_off_ask"
|
||
// retentionTimelyData 对应 **N4 时效性数据**(当前时间 / 天气)。
|
||
retentionTimelyData retentionRule = "n4_timely_data"
|
||
// retentionOneOffTask 对应 **N5 一次性任务指令**。
|
||
retentionOneOffTask retentionRule = "n5_one_off_task"
|
||
)
|
||
|
||
// retentionLowValueReason 是第五态在**留痕 `reason` 列**上的编码(而不是上面那些
|
||
// 规则码)。它是刻意的:design/23 §18.4 把 `reason` 的闭集钉成
|
||
// `{t0_low_value, t1_defer, …}` —— 面板与运维按它分「谁判的 defer」,而**具体命中
|
||
// 哪条反例规则**属于更细的观测面,进日志、不进留痕列(留痕表的列宽与语义不改)。
|
||
const retentionLowValueReason = "t0_low_value"
|
||
|
||
// 规则的**保守阈值**:只对「短到装不下一个事实」的内容开火。
|
||
const (
|
||
// retentionMaxChatterRunes 是「只可能是寒暄」的长度上限(rune 计,不是字节:
|
||
// 中文 1 rune = 3 字节,按字节算会把阈值放大三倍)。
|
||
retentionMaxChatterRunes = 16
|
||
// retentionMaxImperativeRunes 是一次性指令的长度上限。它比前两者宽:
|
||
// 「帮我把 D 盘那个文档转成 pdf」这类真·一次性任务本来就长一些。
|
||
retentionMaxImperativeRunes = 40
|
||
)
|
||
|
||
// judgeExtractionRetention 对一条新记忆的正文施加提取留存规则(design/03 §10.5
|
||
// 的 N1~N5)。命中返回该规则码(调用方据此判 `defer`),否则返回 retentionNone。
|
||
//
|
||
// 它**不读任何状态、不写任何东西、零模型调用**:入参只有正文,出参只有规则码,
|
||
// 因此在写路径上是微秒级且可完全单测(design/03 §10.3 A 级的「零成本」要求)。
|
||
func judgeExtractionRetention(content string) retentionRule {
|
||
text := normalizeRetentionText(content)
|
||
if text == "" {
|
||
// 空 / 纯空白内容在 Add 的 normalize 就被拒了,走不到这里;真走到了也只
|
||
// 放行(**fail-safe**:闸的职责是「拦住显然不值得记的」,不是「替上游
|
||
// 判断该不该存在」—— 判成 defer 才是替它做决定)。
|
||
return retentionNone
|
||
}
|
||
if len([]rune(text)) > retentionMaxImperativeRunes {
|
||
// 长内容一律放行:它已经装得下可检索的事实,闸只拦「显然不值得记」。
|
||
return retentionNone
|
||
}
|
||
// 护栏①:显式「记住」是主人的直接指令,闸不许替他改判。
|
||
if containsAny(text, retentionRememberDirectives()) {
|
||
return retentionNone
|
||
}
|
||
// 护栏②:任何「持久」信号(习惯 / 稳定偏好 / 约定 / 长期事实)一票放行。
|
||
if containsAny(text, tidyHabitMarkers()) || containsAny(text, retentionDurableMarkers()) {
|
||
return retentionNone
|
||
}
|
||
// **情绪先于寒暄**是刻意的:「心情不错」既是情绪陈述、又含致谢词表里的
|
||
// 「不错」——先判情绪才能给出更具体的规则码(两条规则都判 defer,差别只在
|
||
// 「为什么」,而那正是留痕/日志要回答的)。
|
||
if rule := judgeRetentionEmotion(text); rule != retentionNone {
|
||
return rule
|
||
}
|
||
if rule := judgeRetentionChatter(text); rule != retentionNone {
|
||
return rule
|
||
}
|
||
if rule := judgeRetentionTimelyData(text); rule != retentionNone {
|
||
return rule
|
||
}
|
||
return judgeRetentionOneOff(text)
|
||
}
|
||
|
||
// judgeRetentionChatter 施加 **N1**:短且只由寒暄/致谢词构成的内容。
|
||
//
|
||
// 「短」是这条规则的**安全阀**:寒暄的真实形态就是一句话里没有别的信息,
|
||
// 一旦它长到装得下事实,就不该由本闸判(交 T1 / 长期层)。
|
||
func judgeRetentionChatter(text string) retentionRule {
|
||
if len([]rune(text)) > retentionMaxChatterRunes {
|
||
return retentionNone
|
||
}
|
||
if !containsAny(text, retentionChatterWords()) {
|
||
return retentionNone
|
||
}
|
||
// 带数字的短句一律放行:「7 点」「3 月 5 日」这类是事实的载体,
|
||
// 哪怕句子再短(design/23 §5.2-2 把数值当硬门,同一个取向)。
|
||
if hasRetentionDigit(text) {
|
||
return retentionNone
|
||
}
|
||
return retentionChatter
|
||
}
|
||
|
||
// judgeRetentionEmotion 施加 **N2**:**纯**情绪陈述(「今天好累」「心情不错」)。
|
||
//
|
||
// 🔴 判据是 design/03 §10.5 N2 的原文口径「**词项 + 无实体**」,不是「短 + 无数字」。
|
||
// 实现 = 把**情绪词 / 时间词 / 主语 / 程度词 / 虚词 / 标点**统统剥掉之后,**余下为空**
|
||
// 才判 defer;只要还剩一个实体,它就不是「情绪波动」而是**带情绪色彩的习惯或事实**
|
||
// ⇒ 放行。
|
||
//
|
||
// 为什么这条必须写成「无实体」而不是「够短」——这是**方向性**问题,不是精度问题:
|
||
// 判成 defer 的那条记忆 7 天后从所有读路径消失(读侧时效闸,design/03 §9.6),
|
||
// 随后被后台清扫**物理删除**(design/23 §19),**不可逆**。因此宁可漏判(噪声留进
|
||
// 长期层,有 T1 与整理兜底),**绝不误弃**(design/23 §18.3 的 fail-safe)。
|
||
// 反例(review 走真链路复现的误弃):`主人心情不好时会听音乐`、`主人焦虑时会咬指甲`
|
||
// 在「够短 + 无数字就 defer」的旧判据下会被扔进临时层 —— 而那两条是**持久习惯**。
|
||
//
|
||
// 数字不再单独设防:它不在剥离表里,因此**必然留在残差里** ⇒ 带数值的句子一律放行
|
||
// (等价于旧的 hasRetentionDigit 守卫,但由同一条「无实体」判据统一给出)。
|
||
// 长度也不再单独设限:全局 40 rune 的上限(judgeExtractionRetention)已经兜住。
|
||
func judgeRetentionEmotion(text string) retentionRule {
|
||
if !containsAny(text, retentionEmotionWords()) {
|
||
// 没有情绪词 ⇒ 与 N2 无关(本条只拦「情绪波动」这一类)。
|
||
return retentionNone
|
||
}
|
||
if retentionResidual(text) != "" {
|
||
// 剥掉情绪/时间/主语/程度/虚词后**还有实体**(听音乐 / 咬指甲 / 养猫 / …)
|
||
// ⇒ 这是「在某种心情下会做的**稳定行为**」,属于长期记忆,放行。
|
||
return retentionNone
|
||
}
|
||
return retentionEmotion
|
||
}
|
||
|
||
// retentionResidual 剥掉剥离表里的全部词项,返回剩下的**实体片段**
|
||
// (空串 = 整句只是一个情绪陈述,没有任何可长期留存的实体)。
|
||
//
|
||
// 它是 N2「无实体」那一半的唯一实现点,也是这条规则**方向正确**的关键:只有
|
||
// 「整句由情绪词 + 时间词 + 主语 + 虚词构成」时才可能得到空残差 ⇒ 才可能 defer。
|
||
func retentionResidual(text string) string {
|
||
rest := text
|
||
for _, tok := range retentionStripTokens() {
|
||
if tok != "" {
|
||
rest = strings.ReplaceAll(rest, tok, "")
|
||
}
|
||
}
|
||
return rest
|
||
}
|
||
|
||
// judgeRetentionTimelyData 施加 **N4**:时效性查询(当前时间 / 天气)。
|
||
//
|
||
// 它的词项是**整句形态**而不是单词(「几点」「天气」),因为「天气」单独出现可能
|
||
// 是主人对天气的**稳定偏好**(「主人怕冷,阴天心情差」)—— 那种句子带持久信号,
|
||
// 已被护栏②放行;这里只吃「现在几点」「明天天气怎么样」这类纯查询。
|
||
func judgeRetentionTimelyData(text string) retentionRule {
|
||
if containsAny(text, retentionTimelyQueries()) {
|
||
return retentionTimelyData
|
||
}
|
||
return retentionNone
|
||
}
|
||
|
||
// judgeRetentionOneOff 施加 **N3 / N5**:一次性的祈使请求(「帮我订个 7 点的闹钟」
|
||
// 「把这个文件转成 pdf」)。
|
||
//
|
||
// 判据是「**以祈使词开头或含一次性动作词**」而不是「含动词」:后者会把
|
||
// 「主人喜欢把这个文件转成 pdf」(稳定偏好)也拦下 —— 那种句子带持久信号,
|
||
// 已被护栏②放行。
|
||
func judgeRetentionOneOff(text string) retentionRule {
|
||
if hasRetentionPrefix(text, retentionAskPrefixes()) {
|
||
return retentionOneOffAsk
|
||
}
|
||
if containsAny(text, retentionOneOffActions()) {
|
||
return retentionOneOffTask
|
||
}
|
||
return retentionNone
|
||
}
|
||
|
||
// normalizeRetentionText 规范化正文供词项匹配:转小写(对 ASCII 词如 `hi` 有用,
|
||
// 中文不受影响)、把连续空白折叠成**一个空格**、去掉结尾的标点。
|
||
//
|
||
// 空白**折叠**而不是**删除**是刻意的:删光会让相邻两个英文单词粘成一个词,
|
||
// 于是 `as hi` 里冒出子串 `hi` 而被误判成寒暄 —— 折叠则保住词边界。
|
||
// 词项表里因此同时列了 `转成 pdf` 与 `转成pdf` 两种写法。
|
||
func normalizeRetentionText(content string) string {
|
||
text := strings.ToLower(strings.TrimSpace(content))
|
||
text = strings.Join(strings.Fields(text), " ")
|
||
return strings.TrimRight(text, "。!?!?.,,、~~ ")
|
||
}
|
||
|
||
// containsAny 报告 text 是否含 markers 里任一词项。
|
||
func containsAny(text string, markers []string) bool {
|
||
for _, m := range markers {
|
||
if m != "" && strings.Contains(text, m) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// hasRetentionPrefix 报告 text 是否以 prefixes 里任一词开头。
|
||
func hasRetentionPrefix(text string, prefixes []string) bool {
|
||
for _, p := range prefixes {
|
||
if p != "" && strings.HasPrefix(text, p) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// hasRetentionDigit 报告 text 是否含阿拉伯数字(数值是事实的载体,带数值即放行)。
|
||
func hasRetentionDigit(text string) bool {
|
||
for _, r := range text {
|
||
if r >= '0' && r <= '9' {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
// 下列词表一律是**函数**而不是包级变量(AGENTS §10 禁包级全局变量;
|
||
// 同 tidySystemSessions / tidyHabitMarkers 的手法)。
|
||
|
||
// retentionRememberDirectives 是「主人明确要求记住」的信号(护栏①)。
|
||
func retentionRememberDirectives() []string {
|
||
return []string{"记住", "记下", "别忘了", "不要忘", "别忘记"}
|
||
}
|
||
|
||
// retentionDurableMarkers 是正向清单(design/03 §10.4)的词族里、**不被
|
||
// tidyHabitMarkers 覆盖**的那些:长期事实 / 关系里程碑 / 项目约定 / 能力边界。
|
||
func retentionDurableMarkers() []string {
|
||
return []string{
|
||
// 长期事实
|
||
"生日", "纪念日", "住", "职业", "工作", "家庭", "家人", "孩子", "父母",
|
||
"过敏", "忌口", "健康状况", "疾病", "吃药", "身高", "体重", "年龄",
|
||
// 关系里程碑 / 约定
|
||
"约定", "承诺", "答应", "说好", "暗号", "口令", "称呼",
|
||
// 项目约定
|
||
"项目", "迁移号", "端口", "路径", "命名", "编号", "约定俗成",
|
||
// 能力边界(主人明确的「不要做 X」)
|
||
"不要做", "别做", "禁止", "不许",
|
||
// 稳定偏好(补 tidyHabitMarkers 之外的)
|
||
"口味", "爱好", "讨厌", "不喜欢", "偏爱", "嗜好",
|
||
}
|
||
}
|
||
|
||
// retentionChatterWords 是 N1 的寒暄 / 致谢词表。
|
||
func retentionChatterWords() []string {
|
||
return []string{
|
||
"谢谢", "多谢", "感谢", "辛苦", "麻烦你了", "早上好", "早安", "中午好",
|
||
"下午好", "晚上好", "晚安", "你好", "您好", "在吗", "收到", "好的",
|
||
"好呀", "好嘞", "嗯嗯", "哈哈", "嘿嘿", "呵呵", "么么", "拜拜", "再见",
|
||
"加油", "不错", "真棒", "太棒了", "hi", "hello", "hey", "thx", "thanks",
|
||
}
|
||
}
|
||
|
||
// retentionEmotionWords 是 N2 的情绪词表。
|
||
func retentionEmotionWords() []string {
|
||
return []string{
|
||
"好累", "累了", "开心", "高兴", "难过", "不开心", "焦虑", "压力大",
|
||
"心情", "疲惫", "难受", "无聊", "郁闷", "沮丧", "生气", "委屈",
|
||
"烦死", "好烦", "烦躁", "兴奋", "紧张", "害怕", "孤单", "寂寞",
|
||
// 「不错」是**情绪评价**(「心情不错」),因此它在剥离表里必须按**整词**
|
||
// 出现:只靠虚词表里的「不」会把它剥成光杆「错」而留下残差(漏判)。
|
||
"不错",
|
||
// 光杆「累」同样要收:「主人今天真的特别累」只有一个 `累` 而不含 `好累` /
|
||
// `累了`,漏掉它就会留下一字残差、把一条纯情绪句放行。收它**不会**带来误弃
|
||
// —— 判 defer 的门槛恒是「剥完什么都不剩」,多收一个词只会让残差更准确。
|
||
"累",
|
||
}
|
||
}
|
||
|
||
// retentionStripTokens 是 N2 判定「**无实体**」用的**剥离表**:把情绪词、时间词、
|
||
// 主语/称呼、程度词、虚词与标点全部拼在一起,**长词在前**。
|
||
//
|
||
// 长词在前是**必须**的,不是风格:`好` 在程度词里、`好累` 在情绪词里,若先剥 `好`,
|
||
// `好累` 就被拆成光杆 `累` 而永远剥不掉,残差非空 ⇒ 一条纯情绪句反而漏判。
|
||
// (最长优先剥 ⇒ 每条词项都能按它自己的完整性被认出来。)
|
||
//
|
||
// 它是函数而不是包级变量(AGENTS §10 禁包级全局变量;同 tidyHabitMarkers 的手法)。
|
||
func retentionStripTokens() []string {
|
||
tokens := make([]string, 0, 192)
|
||
tokens = append(tokens, retentionEmotionWords()...)
|
||
tokens = append(tokens, retentionEmotionTimeWords()...)
|
||
tokens = append(tokens, retentionEmotionSubjects()...)
|
||
tokens = append(tokens, retentionEmotionDegreeWords()...)
|
||
tokens = append(tokens, retentionEmotionFunctionWords()...)
|
||
tokens = append(tokens, retentionEmotionPunctuation()...)
|
||
sort.SliceStable(tokens, func(i, j int) bool {
|
||
return len([]rune(tokens[i])) > len([]rune(tokens[j]))
|
||
})
|
||
return tokens
|
||
}
|
||
|
||
// retentionEmotionTimeWords 是 N2 剥离表里的**时间词**:它们能让一句情绪陈述显形
|
||
// 为「今天怎么样」,但本身不含实体(「明天下午三点开会」那类带实体的句子不会因为
|
||
// 剥掉时间词而变空)。
|
||
func retentionEmotionTimeWords() []string {
|
||
return []string{
|
||
"今天", "明天", "昨天", "今晚", "昨晚", "今早", "此刻", "现在", "刚才", "刚刚",
|
||
"最近", "这两天", "这几天", "这阵子", "这段时间", "目前", "当下", "一整天",
|
||
}
|
||
}
|
||
|
||
// retentionEmotionSubjects 是 N2 剥离表里的**主语 / 称呼**。剥它们是为了让
|
||
// 「主人今天好累」与「今天好累」走同一条判据 —— 主语是谁不影响「这句话有没有实体」。
|
||
func retentionEmotionSubjects() []string {
|
||
return []string{
|
||
"主人", "咱们", "我们", "你们", "他们", "她们", "自己",
|
||
"我", "你", "他", "她", "它", "咱",
|
||
}
|
||
}
|
||
|
||
// retentionEmotionDegreeWords 是 N2 剥离表里的**程度词**(它们只修饰情绪强度,
|
||
// 不引入任何实体)。
|
||
func retentionEmotionDegreeWords() []string {
|
||
return []string{
|
||
"非常", "特别", "稍微", "真的", "真是", "十分", "比较", "有点", "有些",
|
||
"很", "挺", "太", "好", "超", "蛮", "更", "最",
|
||
}
|
||
}
|
||
|
||
// retentionEmotionFunctionWords 是 N2 剥离表里的**虚词**(助词 / 介词 / 连词 /
|
||
// 副词 / 情态词)。它们不承载实体,剥掉它们才能让「主人心情不好时会听音乐」剩下
|
||
// 「听音乐」这个**实体**、从而被放行。
|
||
func retentionEmotionFunctionWords() []string {
|
||
return []string{
|
||
"的时候", "的时候", "时候", "因为", "所以", "但是", "可是", "已经", "正在",
|
||
"觉得", "感觉", "感到", "变得", "没有", "可以",
|
||
"的", "地", "得", "了", "着", "过", "在", "会", "被", "是", "有", "和", "跟",
|
||
"与", "都", "也", "还", "就", "而", "及", "把", "让", "给", "对", "向", "从",
|
||
"到", "时", "之", "却", "又", "再", "要", "想", "不", "没", "能",
|
||
}
|
||
}
|
||
|
||
// retentionEmotionPunctuation 是 N2 剥离表里的**标点与空白**:一句「今天好累」被
|
||
// 剥到只剩一个逗号时,那仍然是「无实体」。
|
||
func retentionEmotionPunctuation() []string {
|
||
return []string{
|
||
",", "。", "!", "?", "、", ";", ":", "…", "~", "~", "·", "-", "-",
|
||
",", ".", "!", "?", ";", ":", "(", ")", "(", ")", " ", "\t",
|
||
}
|
||
}
|
||
|
||
// retentionTimelyQueries 是 N4 的时效性**查询整句**词表(不是单词,理由见
|
||
// judgeRetentionTimelyData)。
|
||
func retentionTimelyQueries() []string {
|
||
return []string{
|
||
"现在几点", "几点了", "现在时间", "当前时间",
|
||
"今天天气", "明天天气", "天气怎么样", "天气如何", "天气怎样",
|
||
"温度多少", "多少度", "气温多少",
|
||
}
|
||
}
|
||
|
||
// retentionAskPrefixes 是 N3 的祈使开头词表。
|
||
func retentionAskPrefixes() []string {
|
||
return []string{
|
||
"帮我", "请帮", "麻烦帮", "帮个忙", "提醒我", "叫我", "替我",
|
||
"给我订", "给我买", "帮我查", "帮我搜", "帮我订", "帮我买",
|
||
}
|
||
}
|
||
|
||
// retentionOneOffActions 是 N5 的一次性动作词表(位置不限 —— 它们描述的是
|
||
// 「转一次文件」这类有始有终的动作,而不是可以长期成立的偏好)。
|
||
func retentionOneOffActions() []string {
|
||
return []string{
|
||
"转成pdf", "转成 pdf", "转换成", "截图", "打开这个", "关掉这个",
|
||
"搜一下", "查一下", "翻译一下", "订个闹钟", "设个闹钟",
|
||
}
|
||
}
|