# 联系方式双要 + 国家代码完整化 — 设计文档 - 日期:2026-10-08 - 状态:已与需求方确认方向与漏洞修法 - 线上版本:服务运行于 chat2.agent.hnxinyouhe.com(部署手册 docs/deploy-production.md) ## 1. 背景与目标 当前 AI 客服索要联系方式是"WhatsApp 或邮箱二选一",且不引导国家代码。需求方要求: 1. **双要**:邮箱和 WhatsApp 都要(画像更全,客户经理多一条触达通道) 2. **国家代码完整化**:WhatsApp 号码必须含国家代码(如 +966 5x xxx xxxx), 否则客户经理无法回访海外客户 非目标: - 不改变"任一 confirmed 即触发邮件"的线索及时性原则(双要是画像增强, 不是线索交付门槛) - v1 不动前端(输入提示记 v2 备选) ## 2. 现状分析(实测,2026-10-08) | 现状 | 位置 | 问题 | |---|---|---| | 引导话术二选一 | `app/prompts/system.md` 联系方式引导策略节("Drop your WhatsApp or email?") | 无双要引导、无国家代码引导 | | 号码校验只查位数 | `app/services/contact_pipeline.py` split_validate(7-15 位即 confirmed) | 无国家代码的裸号照样确认并进入线索,客户经理无法回访 | | 再索要墙一刀切 | `app/services/conversation.py` stream 循环(has_contact=True 丢一切索要句) | 客户留 WhatsApp 后 AI 补问邮箱会被墙**静默丢句**——不改墙双要做不成 | | 无状态感知提醒 | `app/services/conversation.py` _build_context_reminder | 没有"有 WhatsApp 缺邮箱""号码无国家代码"的提醒注入 | ## 3. 方案选型 **选定:方案 B(提示词 + 状态提醒 + 墙精确化,三层闭环)** 已否决: - 方案 A(只改提示词):新话术"补要邮箱"会被再索要墙丢句,行不通 - 方案 C(无国家代码降级 suspect、凑齐才发线索):延迟线索交付、纠缠客户, 转化率受损 ## 4. 详细设计 ### 4.1 话术升级(system.md「联系方式引导策略」节) 1. **WhatsApp 引导带国家代码**(首轮索要即引导完整格式): - 英文:"What's your WhatsApp number with the country code (like +966 5x xxx xxxx)? Our manager will reach out directly." - 中文:"你WhatsApp多少?带上国家代码(比如+966…),客户经理直接加你。" 2. **分步双要**(一次只问一个,先 WhatsApp 后邮箱——用"正式报价单 PDF"做天然理由): - WhatsApp 到手后轻补:"Got it! And an email address would help too — our manager will send the formal quotation PDF there." - 中文:"收到!邮箱也留一个呗?客户经理把正式报价单 PDF 发你邮箱。" 3. **只肯留一个时不坚持**:拿到哪个都确认致谢,第二个渠道只顺带一提, 绝不纠缠(转化率优先) 4. **国家代码追问话术**(引导**重发完整号**,而非只补国家码——客户重发 完整号会走现有"改号覆盖"机制,**管道零改动**): - "Just so our manager dials it right — could you send the full number with the country code, like +966 5x xxx xxxx?" 5. 已有【好差示例】节补 2 组:①客户给裸号 → 差:直接"Got it"收下; 好:礼貌请其重发完整号(一次);②已有 WhatsApp → 差:再问 WhatsApp; 好:轻补邮箱一次 6. **⚠️ 既有全局指令同步改写(第三轮自查发现,不改则功能自锁)**:语言规则 中"客户已留资时不用任何索要句,改确认式"的硬指令会抑制邮箱轻补行为 (GLM 遵守更显眼的老禁令、拒绝执行新策略)——必须改写为"已留资时不再 索要**已在手的渠道**(改确认式);**缺失渠道**可按【联系方式引导策略】 轻补一次" 7. **新话术纳入模板句清单**:国家代码追问句、邮箱轻补句加进语言规则开头的 模板句枚举(固定短句/转接句/互留句之外新增两席)——保证西语等小语种 客户场景同样"整句用英文/中文原版,绝不翻译",与既有语言规则自洽 ### 4.2 状态感知提醒(conversation.py _build_context_reminder) 复用现有注入机制,新增两个一次性提醒(互斥场景见 4.3 计数): 1. **邮箱轻补提醒**:条件 = confirmed 含 WhatsApp(或电话)、不含 email、 `_email_asked` 未标记 → 注入逐字英文/中文模板句(含"轻描淡写、问过不追"指引) 2. **国家代码确认提醒**:条件 = confirmed 号码均不以 `+`/`00` 开头、 `_cc_asked` 未标记 → 注入 4.1-4 的追问模板句 + "仅此一次,客户拒绝即作罢" ### 4.3 一次性计数(防死缠/spam,防漏洞 1/3) - raw_info 内部状态键:`_email_asked`、`_cc_asked` - 注入提醒的同时落计数;条件判断先查计数——**每个提醒每会话最多注入一次** - ⚠️ **推送过滤(spec 审查发现的必需项)**:`lead_push_service._build_payload` 的 `profile_out = dict(raw_info)` 是整体拷贝且**不过滤 `_` 前缀**(渲染侧 两处有过滤,推送端没有)——必须同步加 `{k: v for k, v in raw_info.items() if not str(k).startswith("_")}` 级过滤, 并补推送 payload 断言,否则 `_email_asked` 等内部状态泄进生产 CRM - ⚠️ **计数写回的实现约束**:`_build_context_reminder` 是 staticmethod 且收到 的是内存副本——调用方须在流式开始前显式写回 `profile.raw_info` 并提交 (轮末 extractor 合并起点是 `dict(profile.raw_info or {})`,提前写入可存活) ### 4.3b 提醒优先级(两提醒同轮并发时的串行规则,spec 审查补充) 主场景恰恰是并发:客户常"留裸号且无邮箱"。**一次只注入一个,顺序固定**: 1. **国家代码确认优先**(先把手上的号码补全闭环) 2. 邮箱轻补仅在以下任一条件满足后才允许注入:号码已带国家代码前缀、 `_cc_asked` 已消耗(问过一次,无论客户是否配合) 3. 即两个提醒由计数与优先级共同串行化——AI 任何一轮只追问一件事 ### 4.4 再索要墙精确化(boundary_filter.py asks_contact) - 签名升级:`asks_contact(sentence, confirmed_channels: set | None = None)` (None = 原行为全丢,兼容无参调用) - 渠道分辨:精确渠道词(whatsapp / e-mail / 邮箱 / 微信…)命中 → 仅当 该渠道**已在 confirmed_channels** 才丢;不在手则放行 - **泛渠道词(number / phone / contact / 电话 / 联系方式 / 号码)保守处理**: 客户已有任何号码渠道在手 → 照丢(宁丢勿放,防重复索要惹恼客户) - **精确词与泛词同句共存**(如 "leave your number or email"):任一命中 判丢即丢(保守方向) - 中文/英文模式同规则 - conversation.py 调用点传入 confirmed 渠道集合(该状态已在前移区算好) ### 4.5 预期行为明确(不是 bug) - 邮箱后到会触发一封"更正/补充邮件"(客户经理先后收 WhatsApp 版、补邮箱版) —— 信息更全,属预期 - 客户重发完整号走现有"改号覆盖"机制 → 会消耗一次 correction 邮件/推送 (与邮箱后到共享每会话 3 封上限,正常预算内) - 客户拒绝补国家代码:号码按现状照常 confirmed,线索照发(软引导不设卡) - 客户配合后又换了个**新的裸号**:不再二次追问(`_cc_asked` 一次性计数是 会话级,防死缠优先于数据完美) ## 5. 不动清单 | 不动 | 理由 | |---|---| | split_validate 的 7-15 位 confirmed 标准 | 国家代码用软引导收敛,不降级延迟线索 | | 邮件触发 D1(任一 confirmed 即发) | 线索及时性原则 | | extractor.md 联系方式字段与指令 | 四层合并契约稳定,无需感知新策略 | | 前端 | v2 备选:输入框 placeholder 提示国家代码 | ## 6. 测试策略 1. **墙精确化单测**(test_boundary_filter.py):有 WhatsApp+问 email → 放行; 有 WhatsApp+问 WhatsApp → 丢;有号码+问泛"number" → 丢;无参调用 → 原全丢行为 2. **提醒注入单测**(test_lead_email_trigger.py 风格):构造"有 WhatsApp 无邮箱" 画像 → reminder 含邮箱轻补句;标记 `_email_asked` 后 → 不再含;裸号 → 含 国家代码追问句;`_cc_asked` 后 → 不含 3. **prompt 静态断言**:system.md 含 "with the country code" / 邮箱轻补模板句 / 好差示例新增组 4. **新模板句过墙回归断言**(spec 审查修正):两个新模板句(国家代码追问句、 邮箱轻补句)**逐字原文在所有渠道状态下均不命中 asks_contact(放行)**—— 墙只拦意译索要形态,不得拦系统注入话术(注入时对应渠道恰是已确认状态); 若未来扩 REASK 模式或改模板措辞导致命中,此断言报警,防功能自锁 5. **推送 payload 断言**:`_email_asked`/`_cc_asked` 不得出现在 _build_payload 产物中(`_` 前缀过滤生效) 6. **计数持久化集成测试**(第三轮自查补充):用现有 manager 流程测试设施 (mock LLM)跑两轮——第一轮触发提醒注入后 `_email_asked`/`_cc_asked` 已 落库,第二轮 reminder 不再含对应注入句(防"写回失效退化为每轮都问") 7. 全量 pytest 绿(注:`asks_contact` 无参调用语义完全不变,现有墙断言 无需改动;仅新参数行为需上述新测试) 8. **真实 LLM 验收**(生产冒烟后补):①客户给裸号 `0501234567` → AI 礼貌请其 重发完整号(仅一次);②客户留 WhatsApp → AI 轻补邮箱一次;③只留邮箱的 客户 → 不被纠缠要 WhatsApp(分步双要以 WhatsApp 起手,邮箱-only 客户 场景 AI 不硬要) ## 7. 风险登记 | 风险 | 等级 | 缓解 | |---|---|---| | 墙放行后 GLM 仍过度索要 | 低 | 4.3 一次性计数收口;真实验收观察 | | 追问话术被翻译成小语种(REASK 墙认不出) | 低 | 模板句按语言规则保持英文/中文原版(现有规则覆盖) | | 双要增加对话轮次导致流失 | 低 | 分步+一次性+不坚持三重克制;观察期画像完整率对比 |