refactor: split websocket flows and add brain action decision pipeline

This commit is contained in:
2026-03-02 16:04:33 +08:00
parent 4022ed8f7a
commit b5153048c4
22 changed files with 2325 additions and 2426 deletions

View File

@@ -0,0 +1,143 @@
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