Files
ai-kefu-admin/docs/superpowers/plans/2026-11-02-jiancai-dual-contact-ask.md
T

23 KiB
Raw Blame History

建材(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! (wall no_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 替换;:693 assert "缺失渠道可按轻补指引提一次" 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)

  1. 全量 pytest 绿(新增 7 静态锚计入,双跑数字一致)
  2. 引擎代码 diff 为零;auto 租户与生成器模板 diff 为零
  3. 生产 jiancai prompts/wall 版本各 +1 = 出厂最新版;三路径真实行为符合预期
  4. 验证会话与临时文件清零;守卫配置恢复原值