Files
Cyrene_v2/internal/runtime/memory/extract_retention.go
T
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

404 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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", "转换成", "截图", "打开这个", "关掉这个",
"搜一下", "查一下", "翻译一下", "订个闹钟", "设个闹钟",
}
}