1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富 统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」; 同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。 2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本), 新增《文档规整方案与开发前待决事项-2026-09-17》。 3) 客服agent 四份交付文档首次纳入本分支。
105 lines
4.2 KiB
Python
105 lines
4.2 KiB
Python
"""记忆语义键的受控词表与显式信号识别。
|
||
|
||
触发判定(`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)
|
||
)
|