feat: 事件异常会话话术与询价消息服务卡片
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2439,6 +2439,17 @@ def air_lock_fail(*, work_order_no: str, operator: str, reason: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def air_cabin_fail_reason(*, tms_reason: str = "", flight_date: str = "", today: str = "") -> str:
|
||||
"""
|
||||
锁舱/释放失败原因:原样回 TMS 的 failReason。
|
||||
没有报价也可以锁舱,禁止改写成「报价选项不存在」或「航班已起飞」。
|
||||
flight_date / today 只为兼容旧调用,不再用来改写原文。
|
||||
"""
|
||||
_ = flight_date
|
||||
_ = today
|
||||
return (tms_reason or "").strip()
|
||||
|
||||
|
||||
def air_release_ok(
|
||||
*,
|
||||
work_order_no: str,
|
||||
@@ -2459,9 +2470,10 @@ def air_release_ok(
|
||||
def air_release_fail(*, work_order_no: str, operator: str, reason: str) -> str:
|
||||
why = (reason or "").strip() or "TMS未返回原因"
|
||||
return (
|
||||
f"工单{work_order_no} 空运释放舱位失败\n"
|
||||
f"工单{work_order_no} 空运释放舱位失败。\n"
|
||||
f"操作人:{operator}\n"
|
||||
f"释放舱位失败的原因:{why}"
|
||||
f"释放舱位失败的原因:{why}\n"
|
||||
"已通知IT运维排查,请稍等。"
|
||||
)
|
||||
|
||||
|
||||
@@ -2484,6 +2496,8 @@ STEP_LLM_NO_TICKET = "llm_no_ticket"
|
||||
STEP_LLM = "llm"
|
||||
STEP_TMS_QUOTE = "tms_quote"
|
||||
STEP_TMS_CABIN = "tms_cabin"
|
||||
STEP_TMS_LOCK = "tms_lock"
|
||||
STEP_TMS_RELEASE = "tms_release"
|
||||
STEP_GROUP = "group"
|
||||
|
||||
SYS_EXC_LLM_FALLBACK = "大模型服务异常,IT排查中..."
|
||||
@@ -2497,6 +2511,8 @@ IMPACT_LLM_NO_TICKET = "尚未建单,不落后台工单"
|
||||
IMPACT_LLM = "当前识别暂停,已有字段与工单状态保持不变"
|
||||
IMPACT_TMS_QUOTE = "当前询价流程暂停,识别字段与原业务节点保持不变"
|
||||
IMPACT_TMS_CABIN = "当前锁舱/释放暂停,工单状态保持不变"
|
||||
IMPACT_TMS_LOCK = "锁舱失败,工单状态保持不变"
|
||||
IMPACT_TMS_RELEASE = "释放舱位失败,工单状态保持不变"
|
||||
IMPACT_GROUP = "当前建群/加人暂停,已建群不新建第二个群"
|
||||
|
||||
|
||||
@@ -2519,7 +2535,7 @@ def system_exception_recovery(step: str) -> str:
|
||||
key = (step or "").strip()
|
||||
if key == STEP_LLM or key == STEP_LLM_NO_TICKET:
|
||||
return SYS_EXC_RECOVERY_LLM
|
||||
if key == STEP_TMS_CABIN:
|
||||
if key in {STEP_TMS_CABIN, STEP_TMS_LOCK, STEP_TMS_RELEASE}:
|
||||
return SYS_EXC_RECOVERY_CABIN
|
||||
if key == STEP_GROUP:
|
||||
return SYS_EXC_RECOVERY_GROUP
|
||||
@@ -2533,6 +2549,10 @@ def system_exception_impact(step: str) -> str:
|
||||
return IMPACT_LLM_NO_TICKET
|
||||
if key == STEP_LLM:
|
||||
return IMPACT_LLM
|
||||
if key == STEP_TMS_LOCK:
|
||||
return IMPACT_TMS_LOCK
|
||||
if key == STEP_TMS_RELEASE:
|
||||
return IMPACT_TMS_RELEASE
|
||||
if key == STEP_TMS_CABIN:
|
||||
return IMPACT_TMS_CABIN
|
||||
if key == STEP_GROUP:
|
||||
@@ -2554,11 +2574,11 @@ def system_exception_alert_payload(
|
||||
系统异常通知卡:走询价消息服务,不走询价小助手。
|
||||
|
||||
五行对齐现网示例:异常类型、接口/服务、错误码、影响范围、发生时间。
|
||||
标题不加 [test]:运维要看的就是这张卡本身,test_prefix 仅保留兼容入参。
|
||||
"""
|
||||
no = (work_order_no or "").strip() or "无"
|
||||
_ = test_prefix
|
||||
title = "系统异常通知"
|
||||
if test_prefix:
|
||||
title = "[test] " + title
|
||||
rows = [
|
||||
{"keyname": "异常类型", "value": (exception_type or "").strip() or "-"},
|
||||
{"keyname": "接口/服务", "value": (service or "").strip() or "-"},
|
||||
@@ -2596,11 +2616,10 @@ def system_exception_alert_text(
|
||||
occurred_at: str,
|
||||
test_prefix: bool = False,
|
||||
) -> str:
|
||||
"""告警卡纯文本兜底(通道降级时仍能看懂)。"""
|
||||
"""告警卡纯文本兜底(通道降级时仍能看懂)。标题同样不加 [test]。"""
|
||||
no = (work_order_no or "").strip() or "无"
|
||||
_ = test_prefix
|
||||
head = "系统异常通知"
|
||||
if test_prefix:
|
||||
head = "[test] " + head
|
||||
lines = [
|
||||
"询价机器人系统告警",
|
||||
head,
|
||||
@@ -2612,3 +2631,101 @@ def system_exception_alert_text(
|
||||
f"发生时间:{(occurred_at or '').strip() or '-'}",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def event_exception_session_text(
|
||||
*,
|
||||
exception_type: str,
|
||||
work_order_no: str,
|
||||
names: list[str] | None,
|
||||
hours: float,
|
||||
mention: bool,
|
||||
test_prefix: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
事件异常会话两行话术。
|
||||
|
||||
mention=True 时姓名前加 @,多人空格拼接。小时数用后台规则值。
|
||||
"""
|
||||
from agent.policy.event_exception import format_timeout_hours
|
||||
|
||||
no = (work_order_no or "").strip() or "-"
|
||||
kind = (exception_type or "").strip() or "事件异常"
|
||||
hour_text = format_timeout_hours(hours)
|
||||
people = [str(n).strip() for n in (names or []) if str(n).strip()]
|
||||
if mention:
|
||||
tagged = " ".join(f"@{n}" for n in people) if people else "相关人员"
|
||||
else:
|
||||
tagged = " ".join(people) if people else "您"
|
||||
body = (
|
||||
f"关联询价工单 {no}。{tagged}已超过 {hour_text} 工作小时 无响应,"
|
||||
f"事件异常已记录工单,请及时处理。"
|
||||
)
|
||||
text = f"{kind}\n{body}"
|
||||
if test_prefix and not text.startswith("[test]"):
|
||||
return f"[test] {text}"
|
||||
return text
|
||||
|
||||
|
||||
def event_exception_alert_payload(
|
||||
*,
|
||||
card_title: str,
|
||||
work_order_no: str,
|
||||
hours: float,
|
||||
test_prefix: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
事件异常通知卡:走询价消息服务,不走询价小助手。
|
||||
|
||||
对齐现网「询价机器人事件告警」:标题用响应异常,正文用「您已超过」。
|
||||
"""
|
||||
from agent.policy.event_exception import format_timeout_hours
|
||||
|
||||
no = (work_order_no or "").strip() or "-"
|
||||
title = (card_title or "").strip() or "事件响应异常"
|
||||
if test_prefix and not title.startswith("[test]"):
|
||||
title = f"[test] {title}"
|
||||
hour_text = format_timeout_hours(hours)
|
||||
from agent.config import get_settings
|
||||
|
||||
base = str(getattr(get_settings(), "public_base_url", "") or "").strip().rstrip("/")
|
||||
action_url = base or "https://work.weixin.qq.com"
|
||||
return {
|
||||
"msgtype": "template_card",
|
||||
"wecom_app": "notify",
|
||||
"template_card": {
|
||||
"card_type": "text_notice",
|
||||
"source": {"desc": "询价机器人事件告警", "desc_color": 0},
|
||||
"main_title": {
|
||||
"title": title,
|
||||
"desc": f"关联询价工单 {no}",
|
||||
},
|
||||
"sub_title_text": f"您已超过 {hour_text} 工作小时无响应,事件异常已记录工单,请及时处理。",
|
||||
"card_action": {"type": 1, "url": action_url},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def event_exception_alert_text(
|
||||
*,
|
||||
card_title: str,
|
||||
work_order_no: str,
|
||||
hours: float,
|
||||
test_prefix: bool = False,
|
||||
) -> str:
|
||||
"""应用卡片纯文本兜底。"""
|
||||
payload = event_exception_alert_payload(
|
||||
card_title=card_title,
|
||||
work_order_no=work_order_no,
|
||||
hours=hours,
|
||||
test_prefix=test_prefix,
|
||||
)
|
||||
card = payload.get("template_card") or {}
|
||||
main = card.get("main_title") or {}
|
||||
lines = [
|
||||
str((card.get("source") or {}).get("desc") or "询价机器人事件告警"),
|
||||
str(main.get("title") or ""),
|
||||
str(main.get("desc") or ""),
|
||||
str(card.get("sub_title_text") or ""),
|
||||
]
|
||||
return "\n".join(x for x in lines if x)
|
||||
|
||||
@@ -12,6 +12,7 @@ ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if ROOT not in sys.path:
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
from agent.policy import inquiry_copy as copy
|
||||
from agent.policy.event_exception import (
|
||||
WAIT_ADOPT,
|
||||
WAIT_AIR_QUOTE,
|
||||
@@ -102,5 +103,65 @@ class TestMentions(unittest.TestCase):
|
||||
self.assertEqual(conversation_of(wait_kind=WAIT_DEAL, collab_chat_id=""), "private")
|
||||
|
||||
|
||||
class TestEventExceptionCopy(unittest.TestCase):
|
||||
def test_group_air(self) -> None:
|
||||
text = copy.event_exception_session_text(
|
||||
exception_type="航线响应超时",
|
||||
work_order_no="WO202606180003",
|
||||
names=["张三"],
|
||||
hours=0.25,
|
||||
mention=True,
|
||||
)
|
||||
self.assertEqual(
|
||||
text,
|
||||
"航线响应超时\n关联询价工单 WO202606180003。@张三已超过 0.25 工作小时 无响应,事件异常已记录工单,请及时处理。",
|
||||
)
|
||||
|
||||
def test_group_two_names(self) -> None:
|
||||
text = copy.event_exception_session_text(
|
||||
exception_type="产品响应超时",
|
||||
work_order_no="WO1",
|
||||
names=["张三", "李四"],
|
||||
hours=2,
|
||||
mention=True,
|
||||
)
|
||||
self.assertIn("@张三 @李四已超过 2 工作小时", text)
|
||||
|
||||
def test_private_sales_no_at(self) -> None:
|
||||
text = copy.event_exception_session_text(
|
||||
exception_type="销售响应超时",
|
||||
work_order_no="WO1",
|
||||
names=["王五"],
|
||||
hours=48,
|
||||
mention=False,
|
||||
)
|
||||
self.assertIn("王五已超过 48 工作小时", text)
|
||||
self.assertNotIn("@王五", text)
|
||||
|
||||
def test_test_prefix(self) -> None:
|
||||
text = copy.event_exception_session_text(
|
||||
exception_type="销售响应超时",
|
||||
work_order_no="WO1",
|
||||
names=["王五"],
|
||||
hours=1,
|
||||
mention=False,
|
||||
test_prefix=True,
|
||||
)
|
||||
self.assertTrue(text.startswith("[test] "))
|
||||
|
||||
def test_app_card(self) -> None:
|
||||
payload = copy.event_exception_alert_payload(
|
||||
card_title="销售响应异常",
|
||||
work_order_no="WO202609090199",
|
||||
hours=48,
|
||||
)
|
||||
self.assertEqual(payload["wecom_app"], "notify")
|
||||
card = payload["template_card"]
|
||||
self.assertEqual(card["source"]["desc"], "询价机器人事件告警")
|
||||
self.assertEqual(card["main_title"]["title"], "销售响应异常")
|
||||
self.assertEqual(card["main_title"]["desc"], "关联询价工单 WO202609090199")
|
||||
self.assertIn("您已超过 48 工作小时无响应", card["sub_title_text"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user