Files
group_fqcd_jr/app/service/memory_taxonomy.py
T
张胜宇 e239eb778b docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富
   统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」;
   同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。
2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本),
   新增《文档规整方案与开发前待决事项-2026-09-17》。
3) 客服agent 四份交付文档首次纳入本分支。
2026-09-17 15:15:22 +08:00

105 lines
4.2 KiB
Python
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.
"""记忆语义键的受控词表与显式信号识别。
触发判定(`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)
)