144 lines
5.8 KiB
Python
144 lines
5.8 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List
|
|
|
|
from utils.metrics_tracker import emit as metrics_emit
|
|
|
|
logger = logging.getLogger("cs_agent")
|
|
|
|
|
|
def load_system_inquiry_rules() -> Dict[str, Any]:
|
|
"""加载系统客服询单规则(全局 + 店铺覆盖)。"""
|
|
from config.config import (
|
|
SYSTEM_INQUIRY_ENABLED,
|
|
SYSTEM_INQUIRY_DEFAULT_ACTION,
|
|
SYSTEM_INQUIRY_DEFAULT_REPLY,
|
|
SYSTEM_INQUIRY_RULES_FILE,
|
|
)
|
|
|
|
enabled_env = os.getenv("SYSTEM_INQUIRY_ENABLED")
|
|
enabled = (
|
|
enabled_env.lower() in ("1", "true", "yes")
|
|
if isinstance(enabled_env, str)
|
|
else bool(SYSTEM_INQUIRY_ENABLED)
|
|
)
|
|
action = (os.getenv("SYSTEM_INQUIRY_DEFAULT_ACTION") or SYSTEM_INQUIRY_DEFAULT_ACTION or "silent").strip().lower()
|
|
reply = os.getenv("SYSTEM_INQUIRY_DEFAULT_REPLY") or SYSTEM_INQUIRY_DEFAULT_REPLY or ""
|
|
rules_file = os.getenv("SYSTEM_INQUIRY_RULES_FILE") or str(SYSTEM_INQUIRY_RULES_FILE)
|
|
defaults: Dict[str, Any] = {
|
|
"enabled": bool(enabled),
|
|
"default_action": action,
|
|
"default_reply": reply,
|
|
"sender_keywords": ["系统客服", "官方客服", "平台客服", "机器人客服", "商家客服系统"],
|
|
"message_keywords": ["系统询单", "代客咨询", "平台代问", "系统代发", "客服询单"],
|
|
"shops": {},
|
|
}
|
|
try:
|
|
p = Path(rules_file)
|
|
if p.exists():
|
|
with p.open("r", encoding="utf-8") as f:
|
|
loaded = json.load(f)
|
|
if isinstance(loaded, dict):
|
|
defaults.update(loaded)
|
|
except Exception as e:
|
|
logger.warning("系统询单规则加载失败,使用默认规则: %s", e)
|
|
return defaults
|
|
|
|
|
|
def normalize_kw_list(v: Any) -> List[str]:
|
|
if not isinstance(v, list):
|
|
return []
|
|
return [str(x).strip().lower() for x in v if str(x).strip()]
|
|
|
|
|
|
def resolve_system_inquiry_policy(client, acc_id: str) -> Dict[str, Any]:
|
|
"""根据店铺合并系统询单策略。"""
|
|
from config.config import SYSTEM_INQUIRY_SHOPS
|
|
|
|
rules = client._system_inquiry_rules or {}
|
|
if not bool(rules.get("enabled", True)):
|
|
return {"enabled": False}
|
|
|
|
shops_env = os.getenv("SYSTEM_INQUIRY_SHOPS", SYSTEM_INQUIRY_SHOPS or "")
|
|
shop_whitelist = [s.strip() for s in shops_env.split(",") if s.strip()]
|
|
if shop_whitelist and (acc_id or "") not in shop_whitelist:
|
|
return {"enabled": False}
|
|
|
|
policy: Dict[str, Any] = {
|
|
"enabled": True,
|
|
"action": str(rules.get("default_action", "silent")).strip().lower(),
|
|
"reply": str(rules.get("default_reply", "")).strip(),
|
|
"sender_keywords": normalize_kw_list(rules.get("sender_keywords")),
|
|
"message_keywords": normalize_kw_list(rules.get("message_keywords")),
|
|
}
|
|
shop_cfg = (rules.get("shops") or {}).get(acc_id or "", {})
|
|
if isinstance(shop_cfg, dict):
|
|
if "enabled" in shop_cfg and not bool(shop_cfg.get("enabled", True)):
|
|
return {"enabled": False}
|
|
if shop_cfg.get("action"):
|
|
policy["action"] = str(shop_cfg.get("action")).strip().lower()
|
|
if shop_cfg.get("reply"):
|
|
policy["reply"] = str(shop_cfg.get("reply")).strip()
|
|
if isinstance(shop_cfg.get("sender_keywords"), list):
|
|
policy["sender_keywords"] = normalize_kw_list(shop_cfg.get("sender_keywords"))
|
|
if isinstance(shop_cfg.get("message_keywords"), list):
|
|
policy["message_keywords"] = normalize_kw_list(shop_cfg.get("message_keywords"))
|
|
if policy["action"] not in ("silent", "reply", "transfer"):
|
|
policy["action"] = "silent"
|
|
return policy
|
|
|
|
|
|
def match_system_inquiry(client, data: dict, policy: Dict[str, Any]) -> bool:
|
|
"""识别是否为系统客服询单消息。"""
|
|
if not policy.get("enabled", False):
|
|
return False
|
|
|
|
from_name = client.to_chinese(data.get("from_name", "") or "").lower()
|
|
from_id = str(data.get("from_id", "") or "").lower()
|
|
msg = client.to_chinese(data.get("msg", "") or "").lower()
|
|
|
|
sender_hits = 0
|
|
for kw in policy.get("sender_keywords", []):
|
|
if kw and (kw in from_name or kw in from_id):
|
|
sender_hits += 1
|
|
message_hits = 0
|
|
for kw in policy.get("message_keywords", []):
|
|
if kw and kw in msg:
|
|
message_hits += 1
|
|
|
|
# 优先看发送者特征;纯文本命中时至少要求两个关键词,降低误判风险
|
|
return sender_hits > 0 or message_hits >= 2
|
|
|
|
|
|
async def handle_system_inquiry(client, data: dict) -> bool:
|
|
"""命中系统询单后按策略处理。"""
|
|
acc_id = data.get("acc_id", "")
|
|
policy = resolve_system_inquiry_policy(client, acc_id)
|
|
if not match_system_inquiry(client, data, policy):
|
|
return False
|
|
|
|
customer_id = data.get("from_id", "")
|
|
metrics_emit("system_inquiry_detected", customer_id=customer_id, acc_id=acc_id)
|
|
action = policy.get("action", "silent")
|
|
logger.info("系统询单命中 | 店铺:%s | 客户:%s | action:%s", acc_id, customer_id, action)
|
|
|
|
if action == "reply":
|
|
reply = await client._compose_ai_scene_reply(
|
|
original_msg=data,
|
|
scene="system_inquiry_reply",
|
|
intent_hint="这是系统客服询单消息,简短确认已收到并说明会跟进即可。",
|
|
fallback=(policy.get("reply") or "您好,这边已收到询单消息,稍后由人工客服跟进处理。"),
|
|
)
|
|
await client.send_reply(data, reply)
|
|
metrics_emit("system_inquiry_auto_reply", customer_id=customer_id, acc_id=acc_id)
|
|
return True
|
|
if action == "transfer":
|
|
await client.transfer_to_human(data, "系统询单转人工")
|
|
metrics_emit("system_inquiry_transfer", customer_id=customer_id, acc_id=acc_id)
|
|
return True
|
|
|
|
metrics_emit("system_inquiry_ignored", customer_id=customer_id, acc_id=acc_id)
|
|
return True
|