feat: 再索要墙渠道精确化——索要缺失渠道放行(双要前置)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-08 11:24:45 +08:00
parent 7c4f8eb4da
commit 1fc87fbe85
4 changed files with 89 additions and 12 deletions
+26 -4
View File
@@ -796,20 +796,24 @@ def test_no_contact_wall_already_contact_no_reask(async_db, monkeypatch):
# ---- Task 5b: 再索要墙(已留资时输出侧确定性拦截索要句) ----
def test_reask_wall_drops_ask_sentence_when_contact(async_db, monkeypatch):
# 生产日志复现: 已留资 + GLM 仍在回复中夹索要句 → 该句静默丢弃,前后句照常
# 渠道精确化(2026-10-08): 客户留的是 phone,故问 phone 的索要句才丢;
# 问 WhatsApp(缺失渠道)的索要句放行流出——同轮钉死两个方向
class ReAskAI(FakeAI):
async def stream_chat(self, messages, context=""):
for token in ("Great choice! ", "Got your WhatsApp? ",
for token in ("Great choice! ", "Got your phone number? ",
"Got your WhatsApp? ",
"Manager will send the details."):
yield token
(manager, conv), _ = _setup(async_db, monkeypatch,
ai=ReAskAI(extract={}),
source=ConversationSource.standalone)
asyncio.run(_send(manager, conv.session_id, "00963944526529")) # 留资
asyncio.run(_send(manager, conv.session_id, "00963944526529")) # 留资(phone)
asyncio.run(_send(manager, conv.session_id, "i want 2model"))
msgs = asyncio.run(manager._load_history(conv.id))
tail = [m["content"] for m in msgs if m["role"] == "assistant"][-1]
assert "Got your WhatsApp" not in tail # 索要句被丢
assert "Got your phone number" not in tail # 索要已在手渠道 → 丢
assert "Got your WhatsApp" in tail # 索要缺失渠道 → 放行流出
assert "Great choice" in tail # 前句保留
assert "send the details" in tail # 后句保留
asyncio.run(manager.db.close())
@@ -838,11 +842,13 @@ def test_reask_wall_closed_without_contact(async_db, monkeypatch):
def test_reask_wall_all_dropped_falls_back_to_closing(async_db, monkeypatch):
# 整条回复只有一句索要 → 全被丢 → 兜底流出"经理主动联系"句,不能空回复
# 渠道精确化(2026-10-08): 索要**已在手**渠道才全丢兜底——客户留的是
# phone,语料改为问 phone;问缺失渠道(WhatsApp)放行流出的方向见第二段
from app.services.boundary_filter import NO_CONTACT_ALREADY
class OnlyAskAI(FakeAI):
async def stream_chat(self, messages, context=""):
for token in ("Got your WhatsApp? ",):
for token in ("Got your phone number? ",):
yield token
(manager, conv), _ = _setup(async_db, monkeypatch,
@@ -854,6 +860,22 @@ def test_reask_wall_all_dropped_falls_back_to_closing(async_db, monkeypatch):
assert tails[-1] == NO_CONTACT_ALREADY
asyncio.run(manager.db.close())
# 同一场景反向钉死: 有 phone 在手问 WhatsApp(缺失渠道) → 放行并流出,
# 不触发兜底(Task 3 邮箱轻补句依赖此放行,否则会被墙吞成兜底句)
class OnlyAskMissingAI(FakeAI):
async def stream_chat(self, messages, context=""):
for token in ("Got your WhatsApp? ",):
yield token
(manager2, conv2), _ = _setup(async_db, monkeypatch,
ai=OnlyAskMissingAI(extract={}),
source=ConversationSource.standalone)
asyncio.run(_send(manager2, conv2.session_id, "00963944526529"))
msgs2 = asyncio.run(manager2._load_history(conv2.id))
tails2 = [m["content"] for m in msgs2 if m["role"] == "assistant"]
assert "Got your WhatsApp?" in tails2[-1] # 放行流出,非兜底句
asyncio.run(manager2.db.close())
# ---- Task 9: 端到端回归(用户报告场景复现) ----
def test_e2e_no_reasking_after_contact_provided(async_db, monkeypatch):