Files
group_fqcd_jr/app/service/memory_taxonomy.py
T

105 lines
4.2 KiB
Python
Raw Normal View History

"""记忆语义键的受控词表与显式信号识别。
触发判定(`MemoryService.should_extract_memory`)与模型抽取校验
(`MemoryExtractionService`)共用同一份定义,避免出现"能触发但写不进"或
"写进去但召回时无人认识"的错位。词表只描述持久事实/偏好/约束/目标,
不含会话流水信息,因此不需要新增库表或库列。
"""
DEFAULT_MEMORY_KEYS: frozenset[str] = frozenset({
"preference:risk_level",
"preference:horizon",
"preference:product_type",
"preference:communication",
"constraint:liquidity",
"constraint:loss_tolerance",
"constraint:exclusion",
"profile:occupation",
"profile:family",
"profile:income_stability",
"profile:investment_experience",
"goal:target",
"goal:retirement",
})
# memory_unit.memory_type 是 String(24),取值必须落在该集合内,与键前缀同构。
MEMORY_TYPES: frozenset[str] = frozenset({
"preference", "constraint", "profile", "goal", "fact",
})
# 只有这些业务事件本身构成持久事实,普通运行事件不得触发记忆写入。
BUSINESS_EVENT_TYPES: frozenset[str] = frozenset({
"risk.assessment_completed",
"suitability.assessment_completed",
"trade.completed",
})
# 受控键 -> 用户明确陈述时使用的表述。模式必须足够具体:像"我想买基金"这类
# 意图、以及任何普通问答都不应命中,否则记忆会被会话噪音污染。
SIGNAL_PATTERNS: dict[str, tuple[str, ...]] = {
"preference:risk_level": (
"风险偏好", "风险承受", "风险等级", "稳健型", "保守型", "激进型", "平衡型",
"低风险", "中风险", "中高风险", "高风险",
),
"preference:horizon": (
"投资期限", "投资周期", "期限是", "长期持有", "短期持有", "长期投资", "短期投资",
"年内不用", "三年内不用", "不赎回", "打算持有", "计划持有", "持有几年",
),
# 不得使用宽泛的"偏好":它会与"风险偏好"重叠,导致从风险偏好陈述里误抽出
# 产品类型偏好。产品类型只认明确列举或"只买/只投"这类排他表述。
"preference:product_type": (
"只买", "只投", "只做", "倾向买", "倾向于买", "更喜欢买",
"偏好股票", "偏好债券", "偏好货币", "偏好指数",
),
"preference:communication": (
"沟通方式", "联系我", "推送", "短信通知",
),
"constraint:liquidity": (
"流动性", "随时赎回", "随时取", "不能锁定", "急用钱",
),
"constraint:loss_tolerance": (
"不能亏", "不想亏", "怕亏", "亏损承受", "最大回撤", "回撤",
"不接受亏损", "接受不了亏损", "不能接受亏损", "受不了亏损",
),
"constraint:exclusion": (
"不买", "不投", "别推荐", "不要推荐", "禁止",
),
"profile:occupation": (
"我从事", "我的职业", "职业是", "我是做",
),
"profile:family": (
"已婚", "未婚", "结婚", "有孩子", "孩子", "子女", "配偶",
"家庭负担", "赡养", "家庭", "养家",
),
"profile:income_stability": (
"收入稳定", "月收入", "工资", "收入来源",
),
"profile:investment_experience": (
"投资经验", "新手", "第一次买基金", "做过股票",
),
# 去掉裸"打算":它在"我打算三年内不赎回"这类期限陈述里不构成目标收益目标。
"goal:target": (
"我的目标是", "计划在", "打算买", "打算投", "攒够", "目标收益", "买房", "留学",
),
"goal:retirement": (
"养老", "退休",
),
}
# 抽取结果必须落在词表内;配置中心可在 default 之外追加受控键。
MEMORY_KEY_NAMESPACE = "memory"
MEMORY_KEY_CONFIG_KEY = "vocabulary"
MEMORY_KEY_FIELD = "memory_keys"
def detect_memory_signals(content: str) -> tuple[str, ...]:
"""识别消息中明确陈述的持久事实/偏好,返回命中的受控键(按词表顺序稳定输出)。"""
normalized = content.strip()
if not normalized:
return ()
return tuple(
key
for key, patterns in SIGNAL_PATTERNS.items()
if any(pattern in normalized for pattern in patterns)
)