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**:**纯**情绪陈述(「今天好累」「心情不错」)。 // // 🔴 判据是**白名单形态匹配**,不是黑名单剥离。这是本规则唯一正确的形态,理由: // // - 「实体」(宾语)的词表是**开放集合**—— 想我 / 想我们 / 想他 / 想和好 / 想妈妈 // / 听音乐 / 咬指甲 / 养猫 / …,永远列不全; // - 而「纯情绪句」的**形态是封闭的**:`[主语]? [时间]? [程度]* 情绪词 [程度|否定|情绪|语气]* [标点]*`, // 且**没有其它成分**。 // // ⇒ 判据 = 「**整句恰好落在上面这个形态里**才 defer,其余一律放行」。 // // **为什么它在结构上不可能把带实体的句子判成 defer**(不是"概率很低",是结构性): // // 1. 匹配要求**整句被消费干净**(`retentionEmotionFormRest` 返回空余部)。任何 // 一个**非标点 / 非空白**的字符只要不在词表里,就会留在余部 ⇒ 判不出空 ⇒ // **放行**。(**标点与空白是被容忍的**:它们由 `onlyRetentionPunctuation` 在收尾 // 时清掉,所以 `心情不错!` 实测 `rest=""` ⇒ 仍判 defer。) // // 2. **位置锁定 + 核心之后只允许四类修饰** —— 这才是真正保安全的一条。 // (早先这里写的是「封闭表里没有承载内容的词」,**那句被 reviewer 证伪** // —— 表里确实有:`心情` 是名词,`生气`/`害怕`/`紧张`/`没有`/`比较` 可作动词, // `压力大` 是动宾,而主语表里的 `主人`/`我`/`他` 本身就是指人实体。) // // 真正的机制是**位置**而不是"表里没有实词": // - **位置锁定**(按实现写):主语只在**位置 0** 被消费一次;时间词只在**紧随其后** // 被消费一次(无主语时它就是首词)。两者都**没有**「后面还能再出现」的缝 —— // `主人今天好累` 匹配(`rest=""`),而 `今天主人好累` 连核心都认不出 // (`rest="今天主人好累"`, `saw=false` ⇒ 放行)。⇒ `主人`/`我`/`你`/`他`/`今天` // 这些**指人 / 指时的实体在文法里永远不可能落到宾语位置**(上一版正是死在 // 这里:`会想我` 的 `我` 被当成主语剥掉了); // - 核心之后只允许「程度 / 否定 / 情绪 / 语气」四类 token,而它们**只允许紧跟 // 在情绪核心之后** —— 核心本身必须先被消费掉,因此那一段在语义上只能是 // **情绪的修饰**(「心情**不错**」「**不**开心」「心情**很好**」,三条均实测 // `rest=""`;反例「生气**得很**」的 `得` 不在表里 ⇒ `rest="得很"` ⇒ 放行), // 装不下一个宾语短语。核心之后一旦出现四类之外的东西(`想`/`会`/`时`/`和`/ // `听音乐`/`妈妈`),**必然消费不掉** ⇒ 余部非空 ⇒ 放行。 // // 3. 失败方向是**安全**的:认不出的形态一律**放行**(留进长期层,有 T1 与整理兜底), // 而**绝不**误弃。上一版(黑名单剥离)恰好相反 —— 它把「能当实体的词」也列进 // 剥离表,于是 `主人不开心时会想我` 被剥成空、判成 defer,而那条是**持久习惯**, // 判 defer 即 7 天后从所有读路径消失、随后被后台清扫**物理删除**(不可逆)。 // 黑名单那种形态**修不完**(review 举证 `会想和好` 被 `和`+`好` 吃掉), // 白名单把失败模式从「未知实体被吞」翻成「未知情绪句没认出」,方向才正确。 // // 🔴 **后缀表不得加入承载内容的词**(名词 / 动词 / 量词 / 新的指人词)。上面第 2 条 // 的整条安全性都建立在「四类之外一律消费不掉」上:往后缀表里塞一个名词,就会**静默** // 把「情绪 + 该名词作宾语」的持久事实判成 defer,而且**不会有任何行为测试变红** // (除非那条输入恰好在语料里)—— 这正是 review 点出的风险。两条 tripwire 钉住它: // // - `TestExtractionRetention_SuffixTableIsPinned`:把后缀表的**内容**逐 token 钉死, // **任何增删都立刻红**(改表必须显式改这份 golden ⇒ 变成一次可 review 的意图声明); // - `TestExtractionRetention_PersistentFactsAreNeverDeferred`:25 条持久事实/习惯 // 语料逐条断言「绝不 defer」,钉住**语义后果**。 // // 两条一起才够:golden 钉「表变了」,语料钉「变了以后语义还对不对」。 // // 长度不单独设限:全局 40 rune 的上限(judgeExtractionRetention)已经兜住。 func judgeRetentionEmotion(text string) retentionRule { rest, sawEmotion := retentionEmotionFormRest(text) if !sawEmotion || rest != "" { // 没认出情绪核心(与 N2 无关),或**没被消费干净**(含形态外的成分, // 也就是"不只是情绪")⇒ 放行。两个分支都是安全方向。 return retentionNone } return retentionEmotion } // retentionEmotionFormRest 尝试把 text 按「纯情绪形态」消费掉,返回 // (未能消费的余部, 是否消费到情绪核心)。**只有余部为空且消费到情绪核心**才意味着 // 「整句恰好是纯情绪陈述」。 // // 形态(顺序固定,各段均按**最长词优先**匹配): // // [主语]? [时间]? [程度]* 情绪核心 [程度|否定|情绪|语气]* 余部须只剩标点/空白 // // 余部同时是**可断言的证据**:带宾语的句子在这里必定留下非空余部(测试钉住它)。 func retentionEmotionFormRest(text string) (string, bool) { rest := text rest, _ = consumeRetentionPrefix(rest, retentionEmotionSubjects()) rest, _ = consumeRetentionPrefix(rest, retentionEmotionTimeWords()) rest = consumeRetentionPrefixes(rest, retentionEmotionDegreeWords()) core, ok := consumeRetentionPrefix(rest, retentionEmotionWords()) if !ok { // 认不出情绪核心 ⇒ sawEmotion=false,调用方放行。 return text, false } rest = consumeRetentionPrefixes(core, retentionEmotionSuffixTokens()) if onlyRetentionPunctuation(rest) { rest = "" } return rest, true } // consumeRetentionPrefix 在 text 的开头按**最长词优先**消费 tokens 里的一个词, // 返回余部与「是否消费到」。最长优先是必须的:`不` 与 `不错`、`好` 与 `好累` 同时 // 命中时,短词先消费会把长词拆开,于是纯情绪句反而消费不干净、漏判。 func consumeRetentionPrefix(text string, tokens []string) (string, bool) { best := "" for _, tok := range tokens { if tok == "" || !strings.HasPrefix(text, tok) { continue } if len(tok) > len(best) { best = tok } } if best == "" { return text, false } return text[len(best):], true } // consumeRetentionPrefixes 反复在句首消费,直到再也匹配不上(用于「可重复修饰」段)。 func consumeRetentionPrefixes(text string, tokens []string) string { for { next, ok := consumeRetentionPrefix(text, tokens) if !ok { return text } text = next } } // onlyRetentionPunctuation 报告 text 是否只由标点与空白构成(消费干净的最后一步: // 「今天好累!」剥到只剩一个标点,仍是「无实体」)。 func onlyRetentionPunctuation(text string) bool { for _, r := range text { if !strings.ContainsRune(retentionPunctuationRunes, r) { return false } } return true } // 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 的门槛恒是「整句被形态消费干净」, // 多收一个情绪词只会让**本该 defer 的**句子认得出来。 "累", } } // retentionEmotionSuffixTokens 是「情绪核心之后**允许**出现的修饰」的封闭集合: // 程度 / 否定 / 情绪 / 语气。**它不含任何承载内容的词** —— 这正是白名单判据在 // 结构上不会吞掉实体的原因(核心之后一旦出现 `想`/`会`/`时`/`和`/`我` 这类字, // 就必然消费不掉、留下非空余部 ⇒ 放行)。 // // 它是函数而不是包级变量(AGENTS §10 禁包级全局变量;同 tidyHabitMarkers 的手法); // 长度先按**最长词优先**排序,`不` 不会抢在 `不错` 前面被消费。 func retentionEmotionSuffixTokens() []string { tokens := make([]string, 0, 64) tokens = append(tokens, retentionEmotionDegreeWords()...) tokens = append(tokens, retentionEmotionNegations()...) tokens = append(tokens, retentionEmotionWords()...) tokens = append(tokens, retentionEmotionToneWords()...) sort.SliceStable(tokens, func(i, j int) bool { return len(tokens[i]) > len(tokens[j]) }) return tokens } // retentionEmotionTimeWords 是**只允许在「主语之后」出现一次**的时间词:无主语时 // 它就是首词(「今天好累」),有主语时紧跟主语(「主人今天好累」)—— 之外的位置一律 // 不匹配(`今天主人好累` 实测 `rest="今天主人好累"`, `saw=false` ⇒ 放行)。 // 它们让一句情绪陈述显形为「今天怎么样」,本身不含实体。 func retentionEmotionTimeWords() []string { return []string{ "今天", "明天", "昨天", "今晚", "昨晚", "今早", "此刻", "现在", "刚才", "刚刚", "最近", "这两天", "这几天", "这阵子", "这段时间", "目前", "当下", "一整天", } } // retentionEmotionSubjects 是**只允许出现在句首**的主语 / 称呼。把它限死在句首是 // 白名单判据的要害之一:`我`/`他`/`我们` 这些词**本身可以当实体**(「会想我」的 // 宾语就是它),若允许它们在任意位置被消费,`主人不开心时会想我` 就会被吞成空。 func retentionEmotionSubjects() []string { return []string{ "主人", "咱们", "我们", "你们", "他们", "她们", "自己", "我", "你", "他", "她", "它", "咱", } } // retentionEmotionDegreeWords 是程度词(只修饰强度,不引入实体)。 func retentionEmotionDegreeWords() []string { return []string{ "非常", "特别", "稍微", "真的", "真是", "十分", "比较", "有点", "有些", "很", "挺", "太", "好", "超", "蛮", "更", "最", } } // retentionEmotionNegations 是情绪核心之后的否定词(「心情**不**好」)。它只能出现在 // 后缀位;`不` 与 `没` 都不承载实体。 func retentionEmotionNegations() []string { return []string{"没有", "不", "没"} } // retentionEmotionToneWords 是句尾的语气词(「好累**啊**」「不错**了**」)。它们只出现 // 在句尾,不引入实体。 func retentionEmotionToneWords() []string { return []string{"啊", "呀", "啦", "哦", "喔", "嘛", "呢", "吧", "了", "的", "哎", "唉", "嗯", "哈"} } // retentionPunctuationRunes 是「消费干净」判定允许剩下的标点与空白。 const retentionPunctuationRunes = ",。!?、;:…~~·--,.!?;:()() \t\n" // retentionTimelyQueries 是 N4 的时效性**查询整句**词表(不是单词,理由见 // judgeRetentionTimelyData)。 func retentionTimelyQueries() []string { return []string{ "现在几点", "几点了", "现在时间", "当前时间", "今天天气", "明天天气", "天气怎么样", "天气如何", "天气怎样", "温度多少", "多少度", "气温多少", } } // retentionAskPrefixes 是 N3 的祈使开头词表。 func retentionAskPrefixes() []string { return []string{ "帮我", "请帮", "麻烦帮", "帮个忙", "提醒我", "叫我", "替我", "给我订", "给我买", "帮我查", "帮我搜", "帮我订", "帮我买", } } // retentionOneOffActions 是 N5 的一次性动作词表(位置不限 —— 它们描述的是 // 「转一次文件」这类有始有终的动作,而不是可以长期成立的偏好)。 func retentionOneOffActions() []string { return []string{ "转成pdf", "转成 pdf", "转换成", "截图", "打开这个", "关掉这个", "搜一下", "查一下", "翻译一下", "订个闹钟", "设个闹钟", } }