@@ -9,7 +9,8 @@
- 公里/km 排除必须直接挂在 万块? 之后: 挂在末尾裸 万 备选上会被
万块? 的空块匹配绕过( " 3万公里 " 仍命中,实测)。
- 预算复述(裸 budget+数字/中文数字万)、金融裸数(首付/月供)收严;
里程 k 形式(30k km/45k miles) 除外。质保期不再豁免(政策变更 2026-09-10:
k 形式的 km/ miles 除外项保留(建材语境已无里程语义,兜底无害) 。质保期
不再豁免(政策变更 2026-09-10:
保修期限是编造的公司政策承诺,一并墙级拦截,见 PRICE_PATTERNS 时间承诺组)。 """
import re
@@ -23,19 +24,43 @@ PRICE_PATTERNS = [
# 时间承诺: 去掉 warranty 豁免(政策变更 2026-09-10——保修期限是编造的
# 公司政策承诺,不再白名单)
re . compile ( r " \ d+ \ s*[- ]?(day|week|month)s? \ b " , re . I ) ,
# 年限保修定向(双向): year 不能进时间模式(车龄 "under 5 years old" 会误杀),
# 年限保修定向(双向): year 不能进时间模式(使用年限 "under 5 years old" 会误杀),
# 只收 数字+年 与 warranty/质保/保修 的组合(含倒装"质保3年"/"warranty of 8 years")
re . compile ( r " \ d+ \ s*years? \ b[^.!?] { 0,15}warranty " , re . I ) ,
re . compile ( r " warranty \ s*(?:of|for|:)? \ s* \ d+ \ s*years? \ b " , re . I ) ,
re . compile ( r " \ d+ \ s*年[^。!?] { 0,8}(?:质保|保修) " ) ,
re . compile ( r " (?:质保|保修)[^。!?] { 0,8} \ d+ \ s*年 " ) ,
# 中文时限承诺: 天/周/个月(年不收——"2024年款 " 是车型年款)
# 中文时限承诺: 天/周/个月(年不收——裸 "2024年" 是年份/项目日期,
# 带质保/保修的年限由上方定向组收)
re . compile ( r " \ d+ \ s*(?:天|周|个月) " ) ,
# 裸预算复述(F3,方案B旗舰形态——冒烟 6/6 破防即此形,原模式完全漏检)
re . compile ( r " (budget|预算)[^ \ d \ n] { 0,12} \ d " , re . I ) ,
re . compile ( r " (两|三|四|五|六|七|八|九|十) \ s*万(?! \ s*(公里|km)) " ) ,
# 金融裸数(F4)
re . compile ( r " (down \ s+payment|monthly \ s+payment|首付|月供)[^ \ d \ n] { 0,10} \ d " , re . I ) ,
# ---- 多语言墙(建材市场: 欧/英/南美/俄语区/中东) ----
# 货币符号: €/£(参考项目只有 $)
re . compile ( r " [€£] \ s? \ d " ) ,
# 价格名词前置(西/葡/俄/阿): 词后 0-20 非数字字符内出现数字
# ⚠️ 拉丁词必须加 \b 词边界——custo 是英文 customs 的子串,外贸高频,
# "customs paperwork for 20ft" 会被误杀(Task 3 质量审查发现)
# ⚠️ 阿语定冠词 ال 与词连写(السعر): 词首 \b 落在 ل|س 之间(两者皆 \w)
# 不成立,备选须收 (?:ال)? 形态——裸 \bسعر\b 实测漏检 "السعر 500 دولار"
re . compile ( r " \ b(?:precio|costo|custo|preço|цена|стоимость| "
r " (?:ال)?سعر|(?:ال)?ثمن) \ b[^ \ d \ n] { 0,20} \ d " , re . I ) ,
# 货币名词后置(数字+货币词): dólares/euros/рублей(西里尔 долларов
# 由上方 стоимость 前置组兜住,不单列)
# ⚠️ euros 排除 pallets 邻接——"50 euro pallets"(欧标托盘)是建材
# 物流高频词,非价格
re . compile ( r " \ d[ \ d,.]* \ s*(?:d[óo]lares?|euros? \ b(?! \ s*pallets?)| "
r " рублей|руб \ .?) " , re . I ) ,
# 多语言时间承诺(西/葡 dias|semanas,俄 дн/недел,阿 أيام;与英文
# days/weeks 组同构)
# ⚠️ 不收 horas/hours/小时——system.md 的 "reply within 12 hours"
# 依赖此豁免(响应时效承诺非交期承诺)
# ⚠️ 俄语变格只收 дн(ей|я)/недел[ьи] 主形态,其余变格(дню/неделю
# 等)不收——宁可少而准
re . compile ( r " \ d+ \ s*[- ]?(?:d[ií]as?|semanas?|дн(?:ей|я)|недел[ьи]|أيام) \ b " ) ,
]
DEFLECTIONS_EN = [
@@ -51,10 +76,22 @@ DEFLECTIONS_ZH = [
# F6(D6×过滤器矛盾): 客户已留联系方式时,迂回话术不得再索要——否则与
# 注入的"禁止再索要联系方式"提醒直接打架
DEFLECTIONS_EN_CONTACT = [
" Our manager will send the exact quote to your WhatsApp shortly. Anything else about the car s? " ,
" Our manager will send the exact quote to your WhatsApp shortly. Anything else about our pipe s? " ,
]
DEFLECTIONS_ZH_CONTACT = [
" 客户经理稍后会把准确报价发到你的 WhatsApp。还想了解什么车 ? " ,
" 客户经理稍后会把准确报价发到你的 WhatsApp。还想了解什么管道产品 ? " ,
]
# ---- Task 5: ES 话术池(南美第一大流量) ----
# 与 EN 池语义对齐的西语替换句(注意: 是"替换句",绝不含价格数字,
# 且不得触发本墙自身——见 test_spanish_pools_are_wall_clean)
DEFLECTIONS_ES = [
" El precio exacto depende de la especificación y el flete — nuestro gerente te lo prepara. ¿Me dejas tu WhatsApp o correo? " ,
" Para un precio exacto, nuestro gerente necesita tu especificación y destino — déjame tu WhatsApp y lo tienes hoy. " ,
" Prefiero darte una cifra exacta y no un estimado — déjanos tu WhatsApp y nuestro gerente te lo envía. " ,
]
DEFLECTIONS_ES_CONTACT = [
" Nuestro gerente te enviará el precio exacto a tu WhatsApp en breve. ¿Algo más sobre nuestras tuberías? " ,
]
@@ -101,24 +138,43 @@ PIVOTS_EN_CONTACT = [
PIVOTS_ZH_CONTACT = [
" 客户经理稍后会把准确报价发到你的 WhatsApp。 " ,
]
PIVOTS_ES = [
" Para el precio exacto — ¿cuál es tu WhatsApp? Nuestro gerente te lo envía. " ,
]
PIVOTS_ES_CONTACT = [
" Nuestro gerente te enviará el precio exacto a tu WhatsApp en breve. " ,
]
# Task 5: 语言路由三分支——汉字→ZH;西语特征(重音符/ñ/¿¡ 粗匹配,与
# 汉字检测同构)→ES;否则 EN。葡语借道 ES 池(拉美沟通惯例相近)
def _lang_pool ( user_msg : str ) :
if re . search ( r " [一-鿿] " , user_msg or " " ) :
return " zh "
if re . search ( r " [áéíóúñ¿¡] " , user_msg or " " ) :
return " es "
return " en "
def pivot_for ( user_msg : str , has_contact : bool = False ) - > str :
zh = bool ( re . search ( r " [一-鿿] " , user_msg or " " ) )
lang = _lang_pool ( user_msg )
if has_contact :
pool = PIVOTS_ZH_CONTACT if zh else PIVOTS_EN_CONTACT
pool = { " zh " : PIVOTS_ZH_CONTACT , " es " : PIVOTS_ES_CONTACT ,
" en " : PIVOTS_EN_CONTACT } [ lang ]
else :
pool = PIVOTS_ZH if zh else PIVOTS_EN
pool = { " zh " : PIVOTS_ZH , " es " : PIVOTS_ES , " en " : PIVOTS_EN } [ lang ]
return pool [ 0 ]
def deflection_for ( user_msg : str , rotate : int = 0 ,
has_contact : bool = False ) - > str :
zh = bool ( re . search ( r " [一-鿿] " , user_msg or " " ) )
lang = _lang_pool ( user_msg )
if has_contact :
pool = DEFLECTIONS_ZH_CONTACT if zh else DEFLECTIONS_EN_CONTACT
pool = { " zh " : DEFLECTIONS_ZH_CONTACT , " es " : DEFLECTIONS_ES_CONTACT ,
" en " : DEFLECTIONS_EN_CONTACT } [ lang ]
else :
pool = DEFLECTIONS_ZH if zh else DEFLECTIONS_EN
pool = { " zh " : DEFLECTIONS_ZH , " es " : DEFLECTIONS_ES ,
" en " : DEFLECTIONS_EN } [ lang ]
return pool [ rotate % len ( pool ) ]
@@ -139,7 +195,7 @@ NO_CONTACT_ALREADY = ("Got it — our manager will reach out to you at your "
def no_contact_value ( sentence : str , user_history : list [ dict ] | None = None ) - > bool :
""" 零号码墙(v2): AI 输出不得含任何 ≥10 位数字串或邮箱。
唯一豁免: 该串出现在近期客户消息中(客户自己号码的礼貌回显)。
无值比对——年份/里程/VIN/续航 天然安全(已验证)。 """
无值比对——年份/规格编号 天然安全(已验证)。 """
for m in LONG_NUMBER_RE . finditer ( sentence or " " ) :
c = digits_only ( m . group ( 0 ) )
if user_history and any ( c in digits_only ( msg . get ( " content " ) or " " )