Files
ai-kefu-jc/docs/superpowers/specs/2026-10-08-dual-contact-and-country-code-design.md
T

162 lines
10 KiB
Markdown
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.
# 联系方式双要 + 国家代码完整化 — 设计文档
- 日期: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 墙认不出) | 低 | 模板句按语言规则保持英文/中文原版(现有规则覆盖) |
| 双要增加对话轮次导致流失 | 低 | 分步+一次性+不坚持三重克制;观察期画像完整率对比 |