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(
|
def air_release_ok(
|
||||||
*,
|
*,
|
||||||
work_order_no: str,
|
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:
|
def air_release_fail(*, work_order_no: str, operator: str, reason: str) -> str:
|
||||||
why = (reason or "").strip() or "TMS未返回原因"
|
why = (reason or "").strip() or "TMS未返回原因"
|
||||||
return (
|
return (
|
||||||
f"工单{work_order_no} 空运释放舱位失败\n"
|
f"工单{work_order_no} 空运释放舱位失败。\n"
|
||||||
f"操作人:{operator}\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_LLM = "llm"
|
||||||
STEP_TMS_QUOTE = "tms_quote"
|
STEP_TMS_QUOTE = "tms_quote"
|
||||||
STEP_TMS_CABIN = "tms_cabin"
|
STEP_TMS_CABIN = "tms_cabin"
|
||||||
|
STEP_TMS_LOCK = "tms_lock"
|
||||||
|
STEP_TMS_RELEASE = "tms_release"
|
||||||
STEP_GROUP = "group"
|
STEP_GROUP = "group"
|
||||||
|
|
||||||
SYS_EXC_LLM_FALLBACK = "大模型服务异常,IT排查中..."
|
SYS_EXC_LLM_FALLBACK = "大模型服务异常,IT排查中..."
|
||||||
@@ -2497,6 +2511,8 @@ IMPACT_LLM_NO_TICKET = "尚未建单,不落后台工单"
|
|||||||
IMPACT_LLM = "当前识别暂停,已有字段与工单状态保持不变"
|
IMPACT_LLM = "当前识别暂停,已有字段与工单状态保持不变"
|
||||||
IMPACT_TMS_QUOTE = "当前询价流程暂停,识别字段与原业务节点保持不变"
|
IMPACT_TMS_QUOTE = "当前询价流程暂停,识别字段与原业务节点保持不变"
|
||||||
IMPACT_TMS_CABIN = "当前锁舱/释放暂停,工单状态保持不变"
|
IMPACT_TMS_CABIN = "当前锁舱/释放暂停,工单状态保持不变"
|
||||||
|
IMPACT_TMS_LOCK = "锁舱失败,工单状态保持不变"
|
||||||
|
IMPACT_TMS_RELEASE = "释放舱位失败,工单状态保持不变"
|
||||||
IMPACT_GROUP = "当前建群/加人暂停,已建群不新建第二个群"
|
IMPACT_GROUP = "当前建群/加人暂停,已建群不新建第二个群"
|
||||||
|
|
||||||
|
|
||||||
@@ -2519,7 +2535,7 @@ def system_exception_recovery(step: str) -> str:
|
|||||||
key = (step or "").strip()
|
key = (step or "").strip()
|
||||||
if key == STEP_LLM or key == STEP_LLM_NO_TICKET:
|
if key == STEP_LLM or key == STEP_LLM_NO_TICKET:
|
||||||
return SYS_EXC_RECOVERY_LLM
|
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
|
return SYS_EXC_RECOVERY_CABIN
|
||||||
if key == STEP_GROUP:
|
if key == STEP_GROUP:
|
||||||
return SYS_EXC_RECOVERY_GROUP
|
return SYS_EXC_RECOVERY_GROUP
|
||||||
@@ -2533,6 +2549,10 @@ def system_exception_impact(step: str) -> str:
|
|||||||
return IMPACT_LLM_NO_TICKET
|
return IMPACT_LLM_NO_TICKET
|
||||||
if key == STEP_LLM:
|
if key == STEP_LLM:
|
||||||
return IMPACT_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:
|
if key == STEP_TMS_CABIN:
|
||||||
return IMPACT_TMS_CABIN
|
return IMPACT_TMS_CABIN
|
||||||
if key == STEP_GROUP:
|
if key == STEP_GROUP:
|
||||||
@@ -2554,11 +2574,11 @@ def system_exception_alert_payload(
|
|||||||
系统异常通知卡:走询价消息服务,不走询价小助手。
|
系统异常通知卡:走询价消息服务,不走询价小助手。
|
||||||
|
|
||||||
五行对齐现网示例:异常类型、接口/服务、错误码、影响范围、发生时间。
|
五行对齐现网示例:异常类型、接口/服务、错误码、影响范围、发生时间。
|
||||||
|
标题不加 [test]:运维要看的就是这张卡本身,test_prefix 仅保留兼容入参。
|
||||||
"""
|
"""
|
||||||
no = (work_order_no or "").strip() or "无"
|
no = (work_order_no or "").strip() or "无"
|
||||||
|
_ = test_prefix
|
||||||
title = "系统异常通知"
|
title = "系统异常通知"
|
||||||
if test_prefix:
|
|
||||||
title = "[test] " + title
|
|
||||||
rows = [
|
rows = [
|
||||||
{"keyname": "异常类型", "value": (exception_type or "").strip() or "-"},
|
{"keyname": "异常类型", "value": (exception_type or "").strip() or "-"},
|
||||||
{"keyname": "接口/服务", "value": (service or "").strip() or "-"},
|
{"keyname": "接口/服务", "value": (service or "").strip() or "-"},
|
||||||
@@ -2596,11 +2616,10 @@ def system_exception_alert_text(
|
|||||||
occurred_at: str,
|
occurred_at: str,
|
||||||
test_prefix: bool = False,
|
test_prefix: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""告警卡纯文本兜底(通道降级时仍能看懂)。"""
|
"""告警卡纯文本兜底(通道降级时仍能看懂)。标题同样不加 [test]。"""
|
||||||
no = (work_order_no or "").strip() or "无"
|
no = (work_order_no or "").strip() or "无"
|
||||||
|
_ = test_prefix
|
||||||
head = "系统异常通知"
|
head = "系统异常通知"
|
||||||
if test_prefix:
|
|
||||||
head = "[test] " + head
|
|
||||||
lines = [
|
lines = [
|
||||||
"询价机器人系统告警",
|
"询价机器人系统告警",
|
||||||
head,
|
head,
|
||||||
@@ -2612,3 +2631,101 @@ def system_exception_alert_text(
|
|||||||
f"发生时间:{(occurred_at or '').strip() or '-'}",
|
f"发生时间:{(occurred_at or '').strip() or '-'}",
|
||||||
]
|
]
|
||||||
return "\n".join(lines)
|
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:
|
if ROOT not in sys.path:
|
||||||
sys.path.insert(0, ROOT)
|
sys.path.insert(0, ROOT)
|
||||||
|
|
||||||
|
from agent.policy import inquiry_copy as copy
|
||||||
from agent.policy.event_exception import (
|
from agent.policy.event_exception import (
|
||||||
WAIT_ADOPT,
|
WAIT_ADOPT,
|
||||||
WAIT_AIR_QUOTE,
|
WAIT_AIR_QUOTE,
|
||||||
@@ -102,5 +103,65 @@ class TestMentions(unittest.TestCase):
|
|||||||
self.assertEqual(conversation_of(wait_kind=WAIT_DEAL, collab_chat_id=""), "private")
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user