Co-Authored-By: Claude Code <noreply@anthropic.com>
23 KiB
建材(jiancai)双要话术实施计划
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: jiancai 首次索要联系方式的话术改为 WhatsApp + 邮箱一起要(双要),已留资收尾话术渠道中性化,缺失渠道补问通用化——纯话术层(3 个 seed_profiles 文件 + 同包测试锚同步),引擎代码零改动,生产经 admin API 整体对齐出厂。
Architecture: 词表/提示词全配置化(CLAUDE.md Phase 1),话术分布在 seed_profiles/jiancai/prompts/system.md(LLM 索要话术池与原则节)与 seed_profiles/jiancai/wall.json(deflections/pivots 注入 $DEFLECTION_POOL、reminders 系统提醒逐字句)。引擎的邮箱轻补注入机制(_email_asked 一次性 + cc_settled 串行门控)与防骚扰墙(放行"索要缺失渠道")原样保留。spec:docs/superpowers/specs/2026-11-02-jiancai-dual-contact-ask-design.md。
Tech Stack: pytest(静态锚 + 引擎行为测试,Windows 需 PYTHONUTF8=1,venv 在 .venv/Scripts/python);生产为 admin HTTPS API(https://chat.ai.hnxinyouhe.com/api/platform,配置版本管线热生效)。
纪律(全程有效):
- 临时文件用完即删,验收 =
git status干净(CLAUDE.md 硬约束) - 每个 commit 必须绿(测试锚同步与数据文件改动同 commit)
- 生产凭据不写入任何仓库文件,执行时向用户取
统一措辞常量(全计划引用,逐字使用)
- 通用化补问子句(三处 reminder 内嵌):
lead_given.forbidden专用(带"仅"):缺失渠道(号码或邮箱)仅可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠contact_ask.app_already/lead_given.boundary_tail共用(无"仅"):缺失渠道——号码或邮箱——可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠- ("仅可"版与"可"版的前缀差异是测试 :684/:693 的判别依据——
缺失渠道(号码或邮箱)仅可…不含子串缺失渠道——号码或邮箱——可…,判别力保留)
- App 反向留资句(三处一字不差):
Drop your WhatsApp and email — our manager will reach out to you directly with the full quote!(wallno_contact.app=reminders.contact_ask.app逐字句 = system.md L214;现状 "full quotation!" vs "full quote!" 一词分叉,本次统一为上句) - 已留资确认句(渠道中性):
Got it — our manager will reach out to you shortly with the full quote!
Task 1: wall.json 双要 + 中性化 + 提醒通用化,同包测试锚同步
Files:
-
Modify:
seed_profiles/jiancai/wall.json -
Create:
tests/test_dual_ask_static.py -
Modify:
tests/test_lead_email_trigger.py:681,692-693、tests/test_cc_integration.py:122 -
Step 1: 新增 wall 侧静态锚测试(先红)
创建 tests/test_dual_ask_static.py:
"""双要话术静态锚(spec 2026-11-02-jiancai-dual-contact-ask §5.3)。
首次索要联系方式 = WhatsApp+邮箱一起要(双要);已留资后的确认/收尾话术
渠道中性(邮箱-only 客户不再听到"发到你 WhatsApp")。wall.json 侧锚在
本文件前半,system.md/company_facts.md 侧锚在后半(Task 2 追加)。
"""
import json
from pathlib import Path
SEED = Path(__file__).resolve().parents[1] / "seed_profiles" / "jiancai"
def _wall() -> dict:
return json.loads((SEED / "wall.json").read_text(encoding="utf-8"))
# ---- wall 侧 ----
def test_deflections_dual_ask_all_languages():
w = _wall()
assert any("and email" in s for s in w["deflections"]["en"])
assert any("和邮箱" in s for s in w["deflections"]["zh"])
assert any("y tu correo" in s for s in w["deflections"]["es"])
def test_pivots_dual_ask_all_languages():
w = _wall()
assert "and email" in w["pivots"]["en"][0]
assert "和邮箱" in w["pivots"]["zh"][0]
assert "y tu correo" in w["pivots"]["es"][0]
def test_post_contact_templates_channel_neutral():
"""已留资确认/收尾模板不含渠道词(三语逐一钉死,防只用 en 子串漏检)。"""
w = _wall()
for group in ("deflections_contact", "pivots_contact"):
for lang, items in w[group].items():
for s in items:
assert "whatsapp" not in s.lower(), (group, lang, s)
assert "whatsapp" not in w["no_contact"]["already"].lower()
def test_app_reverse_ask_dual_and_synced():
"""App 反向留资句双要,且 no_contact.app = contact_ask.app 逐字句同源。"""
w = _wall()
app = w["no_contact"]["app"]
assert "and email" in app
assert app in w["reminders"]["contact_ask"]["app"]
- Step 2: 跑新测试确认红
Run: PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py -q
Expected: 4 failed(deflections 仍为 "or email"、contact 模板仍含 WhatsApp、app 句未双要)
- Step 3: 改 wall.json(逐字段替换值,键结构不动)
| 字段 | 新值 |
|---|---|
deflections.en(3 条全换) |
① Prices vary a lot by config and shipping — our manager will prepare an exact quote for you. What's your WhatsApp and email? ② For an exact price our manager needs your config and destination — drop your WhatsApp and email and you'll have the quote today. ③ I'd rather give you an exact number than a rough guess — leave your WhatsApp and email and our manager will send it over. |
deflections.zh(2 条全换) |
① 价格要看配置和目的地,客户经理会给你准确报价——留个WhatsApp和邮箱? ② 我让客户经理按你的情况出准确报价,留个WhatsApp和邮箱,今天就能发你。 |
deflections.es(3 条全换) |
① El precio exacto depende de la especificación y el flete — nuestro gerente te lo prepara. ¿Me dejas tu WhatsApp y tu correo? ② Para un precio exacto, nuestro gerente necesita tu especificación y destino — déjanos tu WhatsApp y correo y lo tienes hoy. ③ Prefiero darte una cifra exacta y no un estimado — déjanos tu WhatsApp y correo y nuestro gerente te lo envía. |
pivots.en[0] |
Actually, for the exact price — what's your WhatsApp and email? Our manager will send it over. |
pivots.zh[0] |
具体的报价,客户经理会发给你——留个WhatsApp和邮箱? |
pivots.es[0] |
Para el precio exacto — ¿me dejas tu WhatsApp y correo? Nuestro gerente te lo envía. |
deflections_contact.en[0] |
Our manager will send you the exact quote shortly. Anything else about our pipes? |
deflections_contact.zh[0] |
客户经理稍后会把准确报价发给你。还想了解什么管道产品? |
deflections_contact.es[0] |
Nuestro gerente te enviará el precio exacto en breve. ¿Algo más sobre nuestras tuberías? |
pivots_contact.en[0] |
Our manager will send you the exact price shortly. |
pivots_contact.zh[0] |
客户经理稍后会把准确报价发给你。 |
pivots_contact.es[0] |
Nuestro gerente te enviará el precio exacto en breve. |
no_contact.app |
Drop your WhatsApp and email — our manager will reach out to you directly with the full quote! |
no_contact.already |
Got it — our manager will reach out to you shortly with the full quote! |
reminders.contact_ask.app 逐字句 |
整段改为:⚠️ 客户正在索要我方联系方式(最高意向信号)。当前为 App 会话,没有页面按钮——禁止使用任何 tap/button 按钮话术!请逐字使用这句:"Drop your WhatsApp and email — our manager will reach out to you directly with the full quote!"(中文同理引导客户留联系方式)。绝对禁止输出任何号码或邮箱。 |
reminders.contact_ask.app_already |
整段改为:⚠️ 客户正在索要我方联系方式,且客户已留过自己的联系方式。请逐字使用这句:"Our manager will reach out to you shortly with the full quote!"(中文同理)。绝对禁止输出任何号码或邮箱,也绝对禁止再索要客户已提供过的联系方式(缺失渠道——号码或邮箱——可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠;号码缺国家代码时按系统提醒回显补全后的完整号码请客户确认——仅系统提供的号码,禁止自编)。 |
reminders.lead_given.forbidden |
整段改为:⚠️ 客户已提供联系方式({shown})。绝对禁止再索要客户已提供过的联系方式;缺失渠道(号码或邮箱)仅可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠;号码缺国家代码时按系统提醒回显补全后的完整号码请客户确认(仅系统提供的号码,禁止自编)。 |
reminders.lead_given.boundary_tail |
整段改为:客户已留过联系方式——边界请求直接告知'客户经理会把准确报价/安排发给你'即可,禁止再索要联系方式(缺失渠道——号码或邮箱——可再礼貌提一次:邮箱按轻补指引逐字句、号码自然补问,不纠缠;号码缺国家代码时按系统提醒回显补全后的完整号码请客户确认——仅系统提供的号码,禁止自编)。 |
不动:reminders.email_light、cc_templates、cc_guide、reask_patterns、triggers、zero_number、no_contact.web、reminders.contact_ask.web、reminders.lead_given.respond_script/suspect、reminders.boundary.*、reminders.language_follow.*。
锚串自检(改后必在):。绝对禁止再索要客户已提供过(forbidden)、也绝对禁止再索要客户已提供过(app_already)、即可,禁止再索要联系方式(boundary_tail),三处各自 260 字符内都有 回显补全后的完整号码。
-
Step 4: 同步既有测试锚(4 处 + 1 处注释)
-
tests/test_cc_integration.py:122:assert "缺失渠道仅可按轻补指引提一次" in text→assert "缺失渠道(号码或邮箱)仅可再礼貌提一次" in text(行尾注释保留) -
tests/test_lead_email_trigger.py:454:assert "缺失渠道可按轻补指引提一次" in text→assert "缺失渠道——号码或邮箱——可再礼貌提一次" in text -
tests/test_lead_email_trigger.py:681:assert "reach out to you at your WhatsApp" in text→assert "reach out to you shortly with the full quote" in text -
tests/test_lead_email_trigger.py:692-693::692同 :681 替换;:693assert "缺失渠道可按轻补指引提一次" in text→assert "缺失渠道——号码或邮箱——可再礼貌提一次" in text;同步更新 :686-688 注释:内联引文缺失渠道可按轻补指引提一次→缺失渠道——号码或邮箱——可再礼貌提一次,括注(:651 版是"仅可按",不构成子串)→(forbidden 版是"仅可",不构成子串) -
tests/test_boundary_filter.py:104:assert "WhatsApp?" in pivot_for("how much?")→assert "WhatsApp" in pivot_for("how much?")(新 pivots.en[0] 为…WhatsApp and email?,带问号的WhatsApp?子串消失,计划审查实证必红;:105 反向断言不受影响) -
Step 5: 跑受影响测试文件全量
Run: PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py tests/test_lead_email_trigger.py tests/test_cc_integration.py tests/test_boundary_filter.py -q
Expected: 全绿。若有其他硬编码旧句红掉(如 no_contact 墙测试锚旧句),按同一语义同步锚串后重跑至绿。
- Step 6: Commit
git add seed_profiles/jiancai/wall.json tests/test_dual_ask_static.py tests/test_lead_email_trigger.py tests/test_cc_integration.py tests/test_boundary_filter.py
git commit -m "feat(wall): 建材首次索要改双要(WhatsApp+邮箱一起要)+已留资话术渠道中性+缺失渠道补问通用化"
Task 2: system.md + company_facts.md 双要改写
Files:
-
Modify:
seed_profiles/jiancai/prompts/system.md(16 处点位) -
Modify:
seed_profiles/jiancai/prompts/company_facts.md:30 -
Modify:
tests/test_dual_ask_static.py(追加 md 侧锚) -
Step 1: 追加 md 侧静态锚(先红)
在 tests/test_dual_ask_static.py 末尾追加:
# ---- system.md / company_facts.md 侧 ----
def _md(name: str) -> str:
return (SEED / "prompts" / name).read_text(encoding="utf-8")
def test_system_md_dual_ask_principle_and_pool():
src = _md("system.md")
assert "双渠道一次要齐" in src
assert "WhatsApp and email" in src # en 话术池
assert "WhatsApp和邮箱" in src # zh 话术池
# 邮箱轻补两条逐字句原文存活(与引擎 email_light 模板逐字一致)
assert ("Got it! And an email address would help too — our manager "
"will send the formal quotation PDF there.") in src
assert "收到!邮箱也留一个呗?客户经理把正式报价单 PDF 发你邮箱。" in src
def test_app_reverse_ask_line_matches_wall():
w = _wall()
assert w["no_contact"]["app"] in _md("system.md") # L214 与 wall 一字不差
def test_company_facts_dual_ask():
assert "WhatsApp 和邮箱" in _md("company_facts.md")
Run: PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py -q
Expected: 追加的 3 个测试红,Task 1 的 4 个保持绿。
- Step 2: 改 system.md(逐点位精确替换)
改写原则:保留锚串——with the country code、formal quotation PDF、轻补、国家代码(≥2 处)、已在手的渠道、缺失渠道、邮箱轻补句、唯一例外、回显补全后的完整号码(≥4 处)、客户自己的号码也不回显、is that right;邮箱轻补 en/zh 两条逐字句原文不动;CC 回显节(L196-199)与好/差例 L157-167 不动。
| 点位(现行内容特征) | 改为 |
|---|---|
L7 模板句族:转接句 "drop your WhatsApp and they'll reach out directly."、互留句 "Drop yours too..." |
转接句 "drop your WhatsApp and email and they'll reach out directly."、互留句 "Drop your WhatsApp and email too...";同句内 **缺失渠道**可按【联系方式引导策略】轻补一次 → **缺失渠道**(号码或邮箱)可再礼貌提一次(见【联系方式引导策略】);邮箱轻补句 条目与其余句族不动 |
| L25 转接句 | I'll connect you with our manager to arrange that — drop your WhatsApp and email and they'll reach out directly. |
| L35 | Tell me your WhatsApp and email — our manager will work out an accurate quotation and send the PDF over. |
| L69 | …That's exactly why I want our manager to send you the full quotation — drop your WhatsApp and email and our manager will reply within 12 hours. |
| L74 | Got it — our manager will reach out to you shortly!(去 at your WhatsApp) |
| L140 中文好例 | …AI用中文回复"价格要看规格、数量和目的地——我让客户经理给你出准确报价,留个WhatsApp和邮箱?" |
| L152 样品好例 | I'll connect you with our manager to arrange that — drop your WhatsApp and email and they'll reach out directly.(与 L25 一字不差) |
| L155 | Our manager will send you the full quotation.(去 to your WhatsApp) |
| L179-182 en 话术池(4 条全换) | ① What's your WhatsApp and email? Our manager will reach out on WhatsApp and send the full quotation PDF to your email. ② Let me connect you with our manager who handles [country] market — drop your WhatsApp and email and they'll reach out directly. ③ I can have our team send you a full quotation PDF — leave your WhatsApp and email here. ④ Want an exact quote? Drop your WhatsApp with the country code (like +966 5x xxx xxxx) and your email — our manager will take it from there.(④保住 with the country code 锚) |
| L185-188 zh 话术池(4 条全换) | ① 留个WhatsApp和邮箱呗?客户经理 WhatsApp 联系你,正式报价单 PDF 发你邮箱。 ② 我让负责[国家]市场的同事对接你,留个WhatsApp和邮箱? ③ 客户经理按你的项目出完整报价单,PDF 发你邮箱——WhatsApp 和邮箱都留一下吧。 ④ 你给我个联系方式(WhatsApp 和邮箱),我让客户经理按你的项目给你出方案。 |
| L190 节标题 | ### 双渠道与国家代码(双要) |
| L192 原则行 | - **双渠道一次要齐**:首次索要就把 WhatsApp 和邮箱一起要(天然理由=客户经理 WhatsApp 联系你 + 正式报价单 PDF 发你邮箱);客户只给一个时,另一渠道**轻补一次**(邮箱按系统轻补指引逐字句;号码礼貌补问一句),不成就不再提,绝不纠缠(转化率优先):(其下两条逐字话术行 L193-194 原文不动) |
| L214 | Drop your WhatsApp and email — our manager will reach out to you directly with the full quote!(与 wall no_contact.app 一字不差) |
| L216 互留句 | Drop your WhatsApp and email too — and our manager will send the full quotation! |
| L221 | 缺失渠道可轻补一次 → 缺失渠道(号码或邮箱)可再礼貌提一次,仅一次不纠缠;同段确认式例句 "Our manager will send the full quotation to your WhatsApp." → "Our manager will send you the full quotation." |
| L173、L227 括注 | (缺失渠道轻补一次、号码缺国家代码时…) → (缺失渠道——号码或邮箱——可再礼貌提一次、号码缺国家代码时…)(两处同构,锚串 回显补全后的完整号码 保留) |
- Step 3: 改 company_facts.md L30
留个 WhatsApp,他直接联系你 → 留个 WhatsApp 和邮箱,他直接联系你(仅插入 和邮箱,其余标点不动)
- Step 4: 跑 md 侧锚 + 提示词静态测试
Run: PYTHONUTF8=1 .venv/Scripts/python -m pytest tests/test_dual_ask_static.py tests/test_system_prompt_dual_contact.py tests/test_prompt_static.py -q
Expected: 全绿(既有锚全部存活)。红则按锚串自检表逐项排查,缺哪个锚补哪个。
- Step 5: Commit
git add seed_profiles/jiancai/prompts/system.md seed_profiles/jiancai/prompts/company_facts.md tests/test_dual_ask_static.py
git commit -m "feat(prompts): 建材 system.md/company_facts.md 双要改写——话术池/原则节/App反向留资/互留句/提醒配合全量对齐"
Task 3: 全量回归 + 仓库洁净验收
- Step 1: 根目录全量
Run: PYTHONUTF8=1 .venv/Scripts/python -m pytest -q
Expected: 现状 collect 819(CLAUDE.md 的 632 基线已被 site-import 等后续工作推高,计划审查实测),改后预期 826 collected(≈824 passed + 2 skipped,+7 = 新增静态锚);若数目对不上,验收标准退为两条:根目录与 tests/ 子目录双跑数字一致 + 相对改前基线差值恰为 +7(执行 Task 1 前先跑一次全量记录改前基线)
- Step 2: tests/ 子目录全量(双跑一致性)
Run: cd tests && PYTHONUTF8=1 ../.venv/Scripts/python -m pytest -q(跑完回到根目录)
Expected: 与 Step 1 数字一致
- Step 3: 洁净验收
Run: git status --short
Expected: 空(无临时文件、无漂移)。有残留即删。
Task 4: 生产整体对齐出厂 + 三路径验证 + 清理
生产信息:admin API https://chat.ai.hnxinyouhe.com/api/platform;聊天域名 chat2.agent.hnxinyouhe.com(Host 路由 jiancai);凭据执行时向用户取(不写入任何文件)。本次不涉及镜像重建/SSH——配置经 admin API 直推租户配置组。
- Step 1: 登录 + 拉取生产当前配置
TOKEN=$(curl -s -X POST https://chat.ai.hnxinyouhe.com/api/platform/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"<向用户取>","password":"<向用户取>"}' | python -c "import sys,json;print(json.load(sys.stdin)['token'])")
curl -s -H "Authorization: Bearer $TOKEN" https://chat.ai.hnxinyouhe.com/api/platform/tenants/jiancai/config/prompts > /tmp/prod_prompts.json
curl -s -H "Authorization: Bearer $TOKEN" https://chat.ai.hnxinyouhe.com/api/platform/tenants/jiancai/config/wall > /tmp/prod_wall.json
(登录 409 = must_change_password → 停下找用户;/tmp/ 为临时文件,任务收尾删除)
- Step 2: 漂移审计(PUT 前置闸)
对比 /tmp/prod_prompts.json、/tmp/prod_wall.json 与仓库出厂(seed_profiles/jiancai/ 改后版本):
-
预期漂移 = 出厂侧演进未推生产(cd3b58a 翻译规则等)+ 本次双要改动
-
出现预期外差异(生产侧独有定制)→ 停下报告用户,不得盲目覆盖
-
Step 3: 验证守卫(按用户全局纪律:测试绝不打扰线上正式收件/推送)
PUT 临时配置:email 组 notify_emails → ["clany@hnxinyouhe.com"](测试收件箱);push 组 enabled → false。记录两组当前版本号(验证后恢复用)。
- Step 4: 整组 PUT 出厂最新版
以 Step 1 GET 响应的键结构为准,prompts 组四件(system/extractor/evaluator/company_facts)与 wall 组整组替换为仓库出厂内容后 PUT(读-改-写;PUT = 整组替换语义)。PUT 后 GET /tenants/jiancai/config/versions 确认两组版本各 +1。
- Step 5: 三路径真实验证(REST,与 WS 同 code path)
SID=$(curl -s -X POST https://chat2.agent.hnxinyouhe.com/api/chat/session \
-H 'Content-Type: application/json' -d '{"context":{}}' | python -c "import sys,json;print(json.load(sys.stdin)['session_id'])")
curl -s -X POST https://chat2.agent.hnxinyouhe.com/api/chat/message \
-H 'Content-Type: application/json' -d "{\"session_id\":\"$SID\",\"content\":\"...\"}"
-
路径 a 双要首问:先
How much for DN110 PE100 pipe?(预期先问需求一轮)→For water supply project in Saudi Arabia→ 预期回复同时索要 WhatsApp 和 邮箱 -
路径 b 号码-only:新会话走一轮需求后发裸号
0501234567→ 预期 CC 回显确认(完整号)→yes→ 下一轮预期邮箱轻补(email address would help too) -
路径 c 邮箱-only:新会话走一轮需求后
My email is test@example.com, please send the quote→ 预期礼貌补问号码一次;回复拒绝(I'd rather not)→ 预期不再追要、正常继续 -
三路径回复存临时文件人工核对后删除;限流 30 条/小时/租户/IP,消息总量控制在其内
-
Step 6: 恢复守卫 + 清理
-
PUT 恢复
email/push两组原值(对照 Step 3 记录),GET 确认内容一致 -
删除验证会话:
GET /tenants/jiancai/conversations?limit=50找当日 app 来源测试会话 →DELETE /tenants/jiancai/conversations/{conv_id}逐个删 -
删除全部临时文件(
/tmp/prod_*.json、回复存档等) -
Step 7: 回滚预案(只在异常时执行)
POST /tenants/jiancai/config/rollback(body 带回 PUT 前版本号,prompts/wall 两组各回退),恢复守卫同 Step 6。
- Step 8: 观察注记
向用户报告:留资率、双渠道齐收率、breach_rate 纳入阶段④日检观察项;若留资率异常下滑,回滚预案同 Step 7(配置版本秒级回退)。
验收标准(对齐 spec §7)
- 全量 pytest 绿(新增 7 静态锚计入,双跑数字一致)
- 引擎代码 diff 为零;auto 租户与生成器模板 diff 为零
- 生产 jiancai prompts/wall 版本各 +1 = 出厂最新版;三路径真实行为符合预期
- 验证会话与临时文件清零;守卫配置恢复原值