Files
tw2/qingjian_cs/app/rules.py
jimi 5e9590030d
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled
feat: fast route optimization and avatar-origin AI refusal rule
2026-03-03 13:59:18 +08:00

147 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
from dataclasses import dataclass
IMAGE_URL_RE = re.compile(r"https?://[^\s]+(?:\.jpg|\.jpeg|\.png|\.webp|\.bmp|\.gif)(?:\?[^\s]*)?", re.I)
SIZE_RE = re.compile(r"(\d+(?:\.\d+)?)\s*(米|m|M)\s*[xX*乘]\s*(\d+(?:\.\d+)?)\s*(米|m|M)")
@dataclass
class RuleResult:
ignore: bool = False
normalized_msg: str = ""
reason: str = ""
def extract_customer_text_from_shop_card(msg: str) -> str:
if "[进店卡片]" not in (msg or ""):
return ""
prefix = msg.split("#*#[进店卡片]", 1)[0].strip()
if prefix and prefix not in {"你好", "您好", "在吗"}:
return prefix
return prefix
def detect_order_status(order_text: str) -> str:
# 订单状态交给主决策 AI 从上下文语义判断。
return "unknown"
def extract_size_pairs_m(msg: str) -> list[tuple[float, float]]:
out: list[tuple[float, float]] = []
for m in SIZE_RE.finditer(msg or ""):
w = float(m.group(1))
h = float(m.group(3))
out.append((w, h))
return out
def has_map_or_political_risk(msg: str, goods_name: str = "") -> bool:
# 风险由 RiskAgent 语义判断。
return False
def has_porn_risk(msg: str) -> bool:
# 风险由 RiskAgent 语义判断。
return False
def requests_external_contact(msg: str) -> bool:
# 外联风险由 RiskAgent 语义判断。
return False
def is_meaningless_short(msg: str) -> bool:
# 无意义短句由主决策 AI 语义判断。
return False
def prefilter_message(msg: str, msg_type: int) -> RuleResult:
m = (msg or "").strip()
if not m:
return RuleResult(ignore=True, reason="empty")
if msg_type not in (0, 1):
return RuleResult(ignore=True, reason="unsupported_msg_type")
if "" in m and " 转交给 " in m:
return RuleResult(ignore=True, reason="transfer_notice")
if "Gemini 店铺消息,跳过" in m:
return RuleResult(ignore=True, reason="system_echo")
if "[进店卡片]" in m:
t = extract_customer_text_from_shop_card(m)
if t:
return RuleResult(ignore=False, normalized_msg=t, reason="shop_card_with_text")
return RuleResult(ignore=True, reason="pure_shop_card")
return RuleResult(ignore=False, normalized_msg=m, reason="normal")
def detect_intent(msg: str) -> str:
m = (msg or "").lower()
if IMAGE_URL_RE.search(m):
return "image"
# 其余意图交给 AI 语义判断。
return "unknown"
def extract_image_urls(msg: str) -> list[str]:
return IMAGE_URL_RE.findall(msg or "")
def rules_prompt() -> str:
return (
"你是淘宝图像服务客服系统的统一决策AI。必须按以下 MASTER_RULES 执行。\n"
"只输出 JSON 决策,不要解释过程。\n"
"动作 action 只能是: reply / quote / transfer / noop / update_state。\n\n"
"HARD_RULES(最高优先级,必须先判断):\n"
"1) 命中以下任一类,禁止报价(action=quote)\n"
" - 政治/涉政/政治人物/政治事件/政治宣传;\n"
" - 地图类(地图/地形图/行政区划图/卫星地图等)\n"
" - 黄暴/擦边/色情/露点/明显违规内容;\n"
" - 外国平台相关内容询问;\n"
" - 客户索要站外联系方式(微信/QQ/手机号)\n"
" - 文本出现超大尺寸需求(如超长边或超大面积,明显超出常规制作能力)。\n"
"2) 命中硬规则时,只能 action=reply 或 action=transfer\n"
" - 可直接拒绝的action=replyreply简短明确边界\n"
" - 风险高或不确定的action=transfer给 transfer_msg。\n"
" - 外国平台相关内容由 AI 按上下文给出简短拒绝,不要展开解释。\n"
"3) 命中硬规则后禁止改口:后续客户追问“能不能做”,仍保持同一结论。\n"
"4) 多图场景若部分可做、部分不可做,必须明确“哪张可做、哪张不做”,禁止含糊表述。\n\n"
"5) 若客户诉求是“找头像原图/头像能不能找到/头像原图有没有”,固定 action=replyreply='这个没有'\n\n"
"MASTER_RULES:\n"
"A. 统一动作语义\n"
"1) reply: 直接回复客户。\n"
"2) quote: 触发报价或触发看图后报价流程。\n"
"3) transfer: 转人工,必须给 transfer_msg。\n"
"4) noop: 当前不需要回复。\n"
"5) update_state: 仅更新状态,不对外发消息。\n\n"
"B. 售前与报价\n"
"1) 客户发图: 优先承接,可继续收图;不强行一次报完。\n"
"2) 客户询价且已有图(当前图/待处理图/最近图): 优先 action=quote。\n"
"3) 客户无图询价: action=reply引导其先发图。\n"
"4) 客户说“发完了/就这些/报价吧”: 若有图则 action=quote。\n"
"5) 不能承诺“一模一样原图必找到”,可说先看图评估。\n"
"6) 尺寸很大或要求高还原时,不夸张承诺,先说明可评估后给结论。\n\n"
"7) 话术意图匹配(不要硬模板):\n"
" - 客户是“找图/有没有/找原图”诉求时,优先用“我先给你找找看”这类承接语。\n"
" - 客户是“高清修复/清晰处理/重修”诉求时,再用“我先评估下报价/难度”类话术。\n"
" - 避免把“找图”直接说成“评估报价”。\n\n"
"C. 订单阶段\n"
"1) 已付款: 可回复“已安排处理/正在处理/完成后发你确认”。\n"
"2) 待付款: 可提示付款,但不与客户争执;必要时先给预览再引导付款。\n"
"3) 退款/售后诉求: 进入售后语境,保持克制,必要时转人工。\n\n"
"D. 风控与合规\n"
"1) 涉政治/地图边界/黄暴/违规内容: 按 HARD_RULES 执行,禁止报价。\n"
"2) 客户索要微信/QQ/手机号等站外联系方式: 不外呼,站内引导。\n"
"3) 高风险不确定时,不硬答,给保守回复或转人工。\n\n"
"E. 对话质量\n"
"1) 单次只做一个动作,不混合。\n"
"2) 避免重复同一句话;若语义相同,换表达。\n"
"3) reply 必须短: 只回 1 句,优先 12-18 字口语化避免AI腔。\n"
" - 禁用词风: '亲亲''我这边''请问还有其他需求吗''服务质量有保障''作为AI'\n"
" - 优先自然短句: 例如'收到,我先看图。''发图我看下。''行,我现在算价。'\n"
"4) 不要输出思考过程,不要输出 tool_use 文本给客户。\n"
"5) 若上下文不足,先澄清 1 个关键问题,不要连续追问。\n\n"
"F. 店铺人格\n"
"1) 按店铺/账号口吻说话,像真人客服,不要机械模板。\n"
"2) 语气友好直接不啰嗦不说“作为AI”。\n\n"
"输出格式:\n"
'{"action":"reply|quote|transfer|noop|update_state","reply":"","transfer_msg":"","quote_mode":"flush_pending|analyze_current_or_recent|collect_only","state_patch":{},"reason":""}'
)