- Added `ThresholdRepository` for managing customer loss threshold configurations and notifications. - Introduced `threshold_service` to handle loss threshold alerts based on customer portfolio performance. - Enhanced `customer_prompts` to include new intent for querying product net values. - Updated `customer_service` to integrate new threshold alert functionality into existing workflows. - Implemented `sanitize_postprocess` for improved compliance handling in customer interactions. - Enhanced course documentation to reflect updates in advisor training modules and interactive elements. This update significantly improves the customer experience by providing proactive loss threshold notifications and enhancing the overall service framework.
96 lines
3.2 KiB
Python
96 lines
3.2 KiB
Python
"""Wave 2:客户 Agent prompt 常量与关键词快路由纯函数测试(不依赖 DB/LLM)。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.service.customer_prompts import (
|
|
DATA_QUERY_INTENTS,
|
|
INTENT_SYSTEM,
|
|
PROFILE_EXTRACT_SYSTEM,
|
|
REJECT_BY_KIND,
|
|
VALID_INTENTS,
|
|
keyword_route,
|
|
)
|
|
|
|
|
|
def test_intent_constants():
|
|
assert len(VALID_INTENTS) == 13
|
|
assert len(DATA_QUERY_INTENTS) == 5
|
|
# 数据查询意图均在合法集内
|
|
assert DATA_QUERY_INTENTS <= VALID_INTENTS
|
|
# 意图分类 prompt 覆盖标签
|
|
for intent in VALID_INTENTS:
|
|
assert intent in INTENT_SYSTEM
|
|
|
|
|
|
def test_profile_prompt_delegates_to_slots():
|
|
"""抽槽 prompt 由槽位表派生,包含高敏规则。"""
|
|
assert "basic.age_band" in PROFILE_EXTRACT_SYSTEM
|
|
assert "threshold_pref_summary" in PROFILE_EXTRACT_SYSTEM
|
|
assert "高敏感" in PROFILE_EXTRACT_SYSTEM
|
|
|
|
|
|
def test_reject_reply_kinds():
|
|
assert set(REJECT_BY_KIND) == {"advice", "predict", "compare", "realtime"}
|
|
for text in REJECT_BY_KIND.values():
|
|
assert "游客" not in text # 客户视角话术不得出现"游客"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"message,expected_intent",
|
|
[
|
|
("我的持仓怎么样", "holding_query"),
|
|
("我买了什么基金", "holding_query"),
|
|
("最近亏了多少", "holding_query"),
|
|
("最近的交易记录", "transaction_query"),
|
|
("我上个月申购了啥", "transaction_query"),
|
|
("我的风评是什么", "risk_assessment_query"),
|
|
("风险测评过期了吗", "risk_assessment_query"),
|
|
("我能买R3产品吗", "suitability_check"),
|
|
("这个基金适合我买吗", "suitability_check"),
|
|
],
|
|
)
|
|
def test_keyword_route_data_queries(message, expected_intent):
|
|
result = keyword_route(message)
|
|
assert result is not None
|
|
intent, reply = result
|
|
assert intent == expected_intent
|
|
assert reply == ""
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"message,expected",
|
|
[
|
|
("转人工", ("transfer_human", "")),
|
|
("我要投诉", ("transfer_human", "")),
|
|
("推荐个稳赚的基金", ("reject", "advice")),
|
|
("建议买什么好", ("reject", "advice")),
|
|
("这个基金明天会涨吗", ("reject", "predict")),
|
|
("和其他平台比哪个好", ("reject", "compare")),
|
|
("现在实时净值多少", ("reject", "realtime")),
|
|
("我能买什么产品", ("suitability_check", "")),
|
|
("005827的净值是多少", ("nav_query", "")),
|
|
],
|
|
)
|
|
def test_keyword_route_transfer_and_reject(message, expected):
|
|
assert keyword_route(message) == expected
|
|
|
|
|
|
def test_keyword_route_reject_priority_over_suitability():
|
|
""""R3会涨吗"含预测关键词,应 reject 而非适当性查询。"""
|
|
intent, kind = keyword_route("R3产品还会涨吗")
|
|
assert intent == "reject"
|
|
assert kind == "predict"
|
|
|
|
|
|
def test_keyword_route_suitability_vs_advice():
|
|
""""能买"是适当性查询,"推荐买"是投顾拒绝。"""
|
|
assert keyword_route("R3我能买吗")[0] == "suitability_check"
|
|
assert keyword_route("推荐我买R3")[0] == "reject"
|
|
|
|
|
|
def test_keyword_route_no_hit():
|
|
assert keyword_route("今天天气不错") is None
|
|
assert keyword_route("") is None
|