# 改动文档:客户已留资后不再重复索要联系方式 - **日期**:2026-09-08 - **分支**:`chany_dev_dev` - **状态**:160 项测试全绿(基线 133,净增 27);真机 GLM 冒烟**未执行**(清单见 §6,部署前置项) - **实施档案**:[superpowers/plans/2026-09-08-stop-reasking-contact.md](superpowers/plans/2026-09-08-stop-reasking-contact.md)(经独立评审两轮+自查实证一轮收敛;执行期评审又抓出 4 处计划级缺陷,见 §5) --- ## 1. 背景与问题 客户已发 WhatsApp 号码后继续聊,AI 仍反复索要 WhatsApp/邮箱(生产实测:客户被迫三次重发同一号码,AI 五次开口要号)。逐轮取证定位六条根因: | # | 根因 | 一句话 | |---|---|---| | 1 | 捕获缺口 | 倒装("0712 345 678 is my whatsapp")/无is/msg-me/wa冒号等形态正则漏收,extractor 再漏则画像恒空,全防线失效 | | 2 | 同轮竞态 | 提醒/has_contact 用上一轮画像,发号当轮完全不设防 | | 3 | App 脚本打架 | 已留资仍无条件注入逐字"Drop your WhatsApp"脚本,与同提醒里"禁止再索要"互相矛盾(GLM 听逐字不听抽象) | | 4 | 触发词过宽 | "I'll call you later" 误命中索要触发 → 莫名注入要号脚本 | | 5 | 零号码墙自破 | 墙替换句不看 has_contact,文案本身就是索要——回显形态稍有出入即在刚发号后再要一次 | | 6 | 生产日志主因 | 4 次违规索要全部是 GLM 自由生成(逐字背 system.md 留资模板),确定性门控零拦截,提醒禁令被无视 | ## 2. 三层实现 ### 2.1 数据层(尽早尽全地看见"已留资") - **同轮即时扫描**(conversation.py):构建提醒前对当前消息跑一次纯正则 `scan_user_messages`,只并入本轮内存视图(提醒/has_contact 路由),**不落库**——持久化与邮件触发时序零变化 - **正则补四形态**(contact_pipeline.py):倒装(仅显式渠道词 whatsapp/wa/wechat/微信,防"2500000 is my number one choice"排位表述误绑)、`my number X`(无is)、`text/msg/ping me X`、`wa: X`、中文倒装"13800138000 我的微信" - **key 模糊归一**:精确别名优先 + contains 兜底——extractor 即兴 key("联系电话"/"WhatsApp账号")不再静默丢值 ### 2.2 提醒/过滤层(拆掉三条确定性索要引擎) - **App 提醒门控**:已留资(confirmed/suspect)时索要脚本换成"经理会主动联系你"版,与"禁止再索要"不再打架;web 分支不动 - **零号码墙路由**:已留资时替换句用新常量 `NO_CONTACT_ALREADY`(F6 同款修法) - **再索要墙(核心新增)**:`has_contact` 时输出流中的索要句**逐句静默丢弃**(丢句不掐断);整条回复全是索要句时兜底流出 `NO_CONTACT_ALREADY` 防空回复;模式集与 system.md 全部留资模板+生产日志 4 句原话逐字对齐(实证 13 命中/12 放行);未留资闸门全关——首次合法索要不受影响 - **触发词收窄**:第一人称将来时(`i'll`/`i will`/`let me`,含弯引号变体)定宽负向后顾排除 ### 2.3 prompt 层(软加固) - system.md"假称已留过号"改为**先回看记录**:确有号码→确认收到绝不要求重发 - 提醒"已提供"段附逐字话术(直接引用 `NO_CONTACT_ALREADY` 常量,与墙替换句同源不漂移) ## 3. 文件清单 | 文件 | 动作 | |---|---| | `app/services/conversation.py` | 同轮扫描/App门控/墙路由/再索要墙接线/触发词/逐字话术 | | `app/services/contact_pipeline.py` | 正则四形态+倒装正则+key模糊归一(含 `PHONE_ADJACENCY_SCAN_RE` 仅扫描层) | | `app/services/boundary_filter.py` | `NO_CONTACT_ALREADY` + `REASK_PATTERNS`/`asks_contact` | | `app/prompts/system.md` | 先查记录话术(1 处) | | `tests/test_contact_pipeline.py` | +11 | | `tests/test_lead_email_trigger.py` | +13 | | `tests/test_boundary_filter.py` | +3 | 前端零改动、无 schema、无新配置。提交序列(逐个可回滚): `06a758f` 同轮扫描 → `2ec78cd`+`cecb804` 正则扩充(+评审修正) → `3f87f03` key归一 → `7ccef5a` App门控 → `519d280` 墙路由 → `92a5bb6`+`694246b`+`7b96491` 再索要墙(+评审修正) → `ca935db` 触发词 → `7492274` prompt → `bc4e02b` 逐字话术 → `0aa0cef` 端到端。 ## 4. 效果对账(对生产日志场景) 用 2026-09 生产日志逐句重放:首次问价要号(合法)保留;后续 4 次违规索要——App 脚本/墙替换句类由 §2.2 门控根除,GLM 自由生成类由再索要墙**确定性丢弃**;客户仅需发一次号码。三条索要引擎(价格墙替换句、App脚本/零号码墙替换句、GLM 生成句)全部确定性关闭,残余仅剩"正则未覆盖表述的当轮失守(下一轮兜底)"与"再索要墙模式漏拦(GLM 改写措辞)"。 ## 5. 执行期评审抓出的计划缺陷(记录防重蹈) 1. 计划原版裸 `my number X` 分支放进共享正则会授予 `latest_stated_contacts` 覆盖权,击穿改号更正邮件链(实证复现 `['lead'] != ['lead','fix','fix']`)→ 拆为仅扫描层 `PHONE_ADJACENCY_SCAN_RE` 2. `whats app` 双空格/NBSP 变体 KeyError 会崩溃每消息热路径 → 归一化 helper + 回归测试 3. 计划的 web 分支测试断言 `"...WhatsApp button" in text.lower()` 永不通过(needle 大写)→ 修正 4. 再索要墙首版无闸门反向钉死/全丢兜底 → 补三测试(闸门双向变异验证) ## 6. 已知限制与残余(原样转录计划"残余风险") 1. **同轮扫描仍是正则**——未覆盖表述当轮失守,下一轮轮末管道兜底;正则永远不是全集 2. **prompt 层不可单测**——GLM 依从性靠真机冒烟;确定性防线才是主修 3. **`NO_CONTACT_ALREADY` 英文单语**——与既有 WEB/APP 常量一致(如需中英路由属独立增强) 4. **墙路由 confirmed-only**——suspect 时墙仍可能问一次,有意(suspect 本该礼貌确认) 5. **contains 兜底可能误绑怪 key**(如"无email")——概率极低且 value 仍过 split_validate 6. **新正则进入 latest_stated_contacts 即获覆盖权**——倒装只收显式渠道词,排位表述风险已实证排除并钉死 7. **同轮改号当轮视图显示旧值**——"已存优先"方向的两害取轻(不影响禁令触发) 8. **"We will call you" 仍触发**——we 形将来时不排除,已留资时被 App 门控兜住 9. **再索要墙是模式匹配不是语义理解**——GLM 改写措辞可能漏拦/误拦;护栏:仅已留资启用、渠道词强制、丢句不掐断(误拦只损失一句);**拦截日志 `Re-ask wall dropped sentence`(warning 级)回流补模式** 10. **再索要墙 tail 位闸门的 has_contact=False 分支无独立测试钉死**(loop 位已钉;两处共享同一表达式,风险极低,记录在案) ## 7. 部署与回滚 重启生效(prompt/代码启动加载);无迁移、无配置、无前端;回滚 = revert 对应 commit(见 §3 序列)。 ## 8. 真机 GLM 冒烟清单(⚠️ 未执行——部署前置项) | 场景 | 期望 | |---|---| | 发号后继续问价(同轮:"my whatsapp is +86..., how much?") | 确认收到 + 不再要号,报价转经理话术 | | 倒装发号("0712 345 678 is my whatsapp")后闲聊 2 轮 | 全程零索要 | | App 会话发号后再问"how can I contact you" | "经理会主动联系你"版话术,无 Drop your WhatsApp | | "I'll call you later" | 不触发索要脚本,正常道别 | | 发号后说 "I gave you my number already" | 先查记录确认收到,不要求重发 | | 生产日志复现:how the price → 发号 → BYD Song L → zero → 续聊 | 仅首次要号,后续零索要(与 2026-09 日志逐句比对) |