Files
ai-kefu-jc/docs/superpowers/plans/2026-08-20-company-contact-handoff.md
T
2026-09-29 14:15:12 +08:00

14 KiB
Raw Blame History

我方联系方式外发 v2:按钮指路 + 零号码红线(Company Contact Handoff)

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 客户索要我方联系方式时:web 客户 → 引导点击窗口顶部的 WhatsApp/邮箱按钮;App 客户(无按钮)→ 反向留资由客户经理主动联系。AI 在任何情况下不口头输出号码或邮箱——与站点动态配置的按钮值天然零冲突,防幻觉墙简化为无值比对的"零号码墙"。

Architecture: 话术(按钮指路 + source 分流)+ 提醒(触发词前移注入 + 来源标注)+ 零号码墙(AI 输出出现 ≥10 位数字串或邮箱即拦,仅客户回显豁免)。无新配置、无前端改动、无 schema。

Tech Stack: system.md / evaluator.md / conversation.py / boundary_filter.py / pytest


1. 背景(v1 → v2 的决策记录)

  • v1 设计(.env 全局真值注入 prompt + 真值比对墙 + 毒值过滤)历经四轮评审收敛
  • v1 被推翻的关键事实(用户澄清):前端 WhatsApp/邮箱按钮是按站点动态配置的(data-* 属性)——不同嵌入站点可配不同号码。后端单值注入必然与站点按钮冲突,且真值墙会误拦站点自配的正确号码
  • v2 决策(用户拍板):AI 永不口头报号/邮箱,只做两件事——web 指按钮(按钮配什么号都和 AI 无关,零冲突)、App 反向留资(客户经理主动联系)。v1 的配置管道、真值比对、尾缀匹配、毒值三入口过滤全部不再需要
  • v1 四轮评审中仍适用的发现全部继承:提醒前移防早退吞(R3-1)、独立叠加(#3)、词表含"你的"(R3-3)、互留条件排除 suspect(R4-4)、节奏豁免补索要场景(R4-2)、未提供渠道引导(R4-3)、冒烟路径与收尾任务(R5-2/R5-3)

2. 设计

D1:零号码红线(v2 核心)

AI 回复中永远不出现:

  • 任何 ≥10 位连续数字串(含带空格/横线/括号的分隔形态)
  • 任何邮箱地址

引导一律通过:按钮指路(web,按钮由站点动态配置、AI 不需要知道值)或反向留资(App,请客户留下联系方式由客户经理主动联系)。

误杀安全性(v1 已验证沿用):年份(4 位)、里程(逗号/空格断开 <10 位)、VIN(字母混合无长数字串)、续航(600km)、电池(85kWh)均天然不触发;AI 正常聊车内容零误杀。

D2:source 分流(App 无按钮的承接)

后端知道会话来源(conv.source),注入提醒层:

来源 客户索要联系方式时的话术
widget / embedded / standalone(web,页面顶部有 WhatsApp/Email 按钮) "Just tap the WhatsApp button at the top of this chat — our manager replies fast!"(中文同理)
app(无按钮) 反向留资:"Drop your WhatsApp and our manager will reach out to you directly with the full quote!"

D3:互留(已拍板:加)

  • web:给完按钮指路后轻问一句 "Drop yours too and our manager will send you the full quote!"——每会话最多一次;confirmed 或 suspect 时不加(suspect 时 D6 要求礼貌确认而非再索要)
  • App:反向留资本身就是主话术,不再叠加互留

D4:未提供渠道(继承 R4-3)

客户索要微信/Telegram/LINE/ins 等未配置渠道 → 引导到按钮("We mainly use WhatsApp — tap the button at the top, fastest way to reach us"),绝不提供/编造任何渠道账号。

D5:零号码墙(v1 真值墙的简化形态)

句子门控管道并列新增一类违规,无值比对:

AI 输出句中出现 ≥10 位数字串 或 邮箱
  ├─ 该串出现在近期客户消息中(客户号的礼貌回显)→ 放行
  └─ 否则 → 拦截该句,替换句按 source 分流:
       web → "Just tap the WhatsApp button at the top — our manager replies fast!"
       App → "Drop your WhatsApp and our manager will reach out directly!"
  • 无 allowed 集合、无国家码尾缀、无公司值依赖——墙不认识任何"正确号码",它只认识"AI 不该有号码"这一条
  • 客户回显豁免保留(便宜且防误杀礼貌确认:"Got it, 07012345678")
  • 替换句为通用句式(v1 R5-1 教训:不写死渠道名与值的绑定)
  • 墙的附带收益(R3-4 同理):任意场景幻觉出的热线号/邮箱一律被拦

D6:与现有体系交互(沿用 v1 核实 + 增补)

体系 结论
价格墙 并列两类违规、同一拦截管道,互不干扰
留资管道 AI 不再输出号码 → v1 的"公司号被回显当成客户号"(R4-1)主通路消失;残余=客户从按钮链接复制号码回显(罕见),记录接受
动态配置按钮 零冲突(AI 不报值,站点配什么都一致)
D6 禁止再索要 / suspect 确认 互留条件已排除;App 反向留资在已留资时由 D6 提醒压制
evaluator 索要我方联系方式 = 最强意向信号
前端/配置/schema 全部零改动

3. 文件结构

文件 动作 职责
app/prompts/system.md 修改 索要话术节(web/App 双话术)+ 零号码红线 + 未提供渠道 + 节奏豁免补索要
app/services/conversation.py 修改 CONTACT_ASK_TRIGGER_RE + 提醒前移/独立/互留条件/source 标注 + 墙接线(传 source 选替换句)
app/services/boundary_filter.py 修改 no_contact_value(sentence, user_history) 零号码判定 + 按来源的替换句
app/prompts/evaluator.md 修改 最强意向信号
tests/test_boundary_filter.py、tests/test_lead_email_trigger.py 修改 墙/提醒单测与集成

4. 任务分解

Task 15.1: system.md 话术与红线

  • "引流兜底"节后新增:
### 客户索要我方联系方式(最高意向信号,立即响应)
客户问"how can I contact you / 你们的WhatsApp多少"时:
- 系统提醒会告知当前会话来源。**网页会话**(有页面按钮)→ 指引点击:
  "Just tap the WhatsApp button at the top of this chat — our manager replies fast!"
  (邮箱同理:"Tap the Email button at the top.")
- **App 会话**(没有页面按钮)→ 反向留资:
  "Drop your WhatsApp and our manager will reach out to you directly with the full quote!"
- 中文版同理("直接点窗口顶部的 WhatsApp 按钮就行,客户经理回复很快~")
- 【互留】网页会话给完指路后可轻问一句:"Drop yours too and our manager will send you
  the full quote!"——每会话最多一次;客户已留过或疑似留过则不加
- 客户索要未提供的渠道(微信/Telegram/LINE/ins)→ "We mainly use WhatsApp — tap the
  button at the top, fastest way to reach us.",绝不提供或编造任何渠道账号
  • 红线区追加:- 回复中不能出现任何电话号码或邮箱地址——客户自己的号码也不回显(确认收到即可);引导联系一律用页面按钮(网页)或请客户留联系方式(App)
  • 节奏豁免补一类:"客户主动问价、问时效、提实质请求或索要我方联系方式时…"
  • 验证加载:python -c "from app.services.ai_service import SYSTEM_PROMPT as p; assert '索要我方联系方式' in p and '不能出现任何电话号码' in p; print('ok')"

Task 15.2: 提醒层

  • 触发词表(conversation.py 模块级):
CONTACT_ASK_TRIGGER_RE = re.compile(
    r"(your|our)\s+(whatsapp|number|phone|email|contact)|"
    r"(contact|reach|call|email)\s+(you|us)|"
    r"你(们)?的?\s?(whatsapp|号码|电话|微信|邮箱|联系方式)|"
    r"(怎么|如何)联系|给我.{0,4}(号码|电话|联系方式)",
    re.IGNORECASE,
)
  • _build_context_reminder 接线(签名加可选参数 source: str = "",调用处传 conv.source.value):
        # === 前移段(早退之前,与 boundary_note 并列) ===
        contact_note = ""
        if CONTACT_ASK_TRIGGER_RE.search(latest_msg or ""):
            if source == "app":
                contact_note = ("⚠️ 客户正在索要我方联系方式(最高意向信号)。当前为 App 会话,"
                                "没有页面按钮——请引导客户留下他的联系方式,客户经理会主动联系:"
                                "绝对禁止输出任何号码或邮箱。")
            else:
                contact_note = ("⚠️ 客户正在索要我方联系方式(最高意向信号)。引导点击窗口顶部的 "
                                "WhatsApp/Email 按钮即可——绝对禁止口头报出任何号码或邮箱。")
        if not raw_info:
            return "\n".join(x for x in (boundary_note, contact_note) if x)
        # === 体内注入段(confirmed/suspect 判定后) ===
        if contact_note:
            note = contact_note
            if source != "app" and not confirmed and not suspect:
                note += "客户尚未留过联系方式,可顺势轻问一句(每会话最多一次)。"
            parts.append(note)
  • 调用处 stream 流程加索要率日志:logger.info("Contact-ask reminder fired (source=%s)", conv.source.value)
  • 调用处传 source(找到 _build_context_reminder(...) 唯一调用点补参数)

Task 15.3: 零号码墙(boundary_filter.py + conversation.py 接线)

  • boundary_filter.py 追加:
LONG_NUMBER_RE = re.compile(r"\+?\d[\d\s\-().]{9,17}\d")   # ≥10位连续数字(允许分隔形态)
ANY_EMAIL_RE = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")

NO_CONTACT_WEB = ("Just tap the WhatsApp button at the top of this chat — "
                  "our manager replies fast!")
NO_CONTACT_APP = ("Drop your WhatsApp and our manager will reach out to you directly "
                  "with the full quote!")


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 "")
                                for msg in user_history if msg.get("role") == "user"):
            continue  # 客户回显豁免
        return True
    for m in ANY_EMAIL_RE.finditer(sentence or ""):
        if user_history and any(m.group(0).lower() in (msg.get("content") or "").lower()
                                for msg in user_history if msg.get("role") == "user"):
            continue
        return True
    return False

(digits_only 从 contact_pipeline import。)

  • conversation.py 句子门控接线:价格违规判定之后并列 no_contact_value(sentence, history_for_ai);命中走同一拦截-掐断-替换路径,替换句按 conv.source.value == "app" 选 NO_CONTACT_APP / NO_CONTACT_WEB;日志行 No-contact wall replaced(对齐观测模式)。
  • 注意:现有集成测试的 FakeAI/MixedAI/DirtyAI 流式回复均不含 ≥10 位数字或邮箱("ok reply"/"Nice pick!"/"$25k")——确认零破坏。

Task 15.4: evaluator.md

  • 意向积极信号行追加:客户主动索要我方联系方式(WhatsApp/邮箱/电话),视为最强意向信号之一

Task 15.5: 测试与冒烟

  • 单测(test_boundary_filter.py):任意号码拦截("call us at +86 138 0013 8000")、任意邮箱拦截、客户回显豁免("Got it, 07012345678" + history 含之)、白名单安全("2024 model / 45,000 km / VIN LFV3A23C8K3123456 / 85kWh")、替换句无号码自检(NO_CONTACT_* 不含 ≥10 位数字/邮箱)
  • 单测(test_lead_email_trigger.py):索要触发提醒("how can I contact you"/"你们的whatsapp多少"/"你的微信多少");首条消息空画像也有提醒;app source 分流话术(含"主动联系"字样);已留资/suspect 无互留句;普通消息不触发;与价格提醒叠加共存
  • 集成(test_lead_email_trigger.py):NumberAI(流式回复含 "+86 138 0013 8000")→ 客户端收到的是按钮指路句且落库一致;app source 会话同样违规 → 收到留资句
  • 全量回归:预计 106 → ~120,全绿
  • 真机冒烟(重启服务,真实 GLM):
    1. web 独立页问 "how can I contact you" → 按钮指路,回复零号码零邮箱
    2. 问 "你们有微信吗" → 引导回 WhatsApp 按钮
    3. 双重请求 "怎么联系你们?多少钱" → 指按钮 + 不报价
    4. 连续索要 ×3 → 互留句只出现一次
    5. REST 模拟 App 会话(source=app)索要 → 反向留资话术
    6. 正常问车(续航/里程/年份)→ 数字正常回答,零误杀

Task 15.6: 收尾(R5-3 惯例)

  • 全量回归最终确认
  • changelog 文档(docs/2026-08-20-company-contact-handoff-changelog.md)
  • 单提交;重启本地服务交付冒烟

5. 决策点(全部已定)

# 决策 状态
1 互留一句 已定:加,每会话一次,confirmed/suspect 排除
2 引导方式 已定(2026-08-20 v2):按钮指路 + 零号码红线——溶解动态配置难题,App 由反向留资承接
3 前端同步 随 v2 消失——AI 不报值,站点按钮自由配置

6. 风险与残余

风险/残余 处置
GLM 被客户磨狠了仍报号 零号码墙确定性拦截(无值比对,比 v1 更严)
客户坚持要"号码本身" web:点按钮即加号(wa.me 直达);App:反向留资后客户经理主动联系——接受
客户从按钮链接复制号码回显 → 被留资管道误捕获 AI 从不报号后主通路消失;残余极罕见,出现再补毒值 belt(记录)
纯字母渠道 ID(微信号)编造 零号码墙拦不住字母 ID——prompt"绝不提供未配置渠道"兜底(继承 R4-3 残余)
提醒/话术被价格提醒叠加时指令过载 两提醒内容兼容(指按钮+不报价);冒烟 #3 验证
回滚 纯 prompt+提醒+墙扩展,git revert 单提交即回滚