feat: CC强制墙——触发轮整段缓冲+流末country code判定+模板句整段替换

场景①三轮提示词修复 0/8 后的需求方决策(增补设计 §2): GLM 对反惯性的
CC 重发追问结构性无视,升级为输出侧确定性强制。

- 触发: 本轮 completion_marks == ["_cc_asked"](裸号在场且未问过)
- 流式无法撤回 → 触发轮跳过逐句流出,整段缓冲(缓冲期间价格/零号码/
  再索要三墙照常逐句检查,优先级不变);流末统一判定
- 完整回复含 "country code"(中文轮认"国家代码",与注入的逐字追问句
  同词)→ 零干预原样流出;不含 → 整段替换为 CC 模板句正文,logger.warning
  留痕
- 模板句与 _CC_NOTE_* 同源化: _CC_ASK_* 抽出为共享正文,_CC_WALL_EN
  加 "Got it — " 衔接头(_CC_WALL_ZH 与正文同句)——注入/替换两处
  逐字锚点不漂移
- _cc_asked 计数照常流前落库,一次性消耗不受替换影响
- 测试 5 项: 替换(EN/ZH)+计数落库 / 含 country code 零干预(EN/ZH) /
  非触发轮三形态(带前缀号/已耗/双渠道齐)原样流出

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-08 13:34:34 +08:00
parent 2e08ab22b6
commit 5f124e691b
2 changed files with 176 additions and 5 deletions
+48 -5
View File
@@ -98,15 +98,24 @@ def _lang_note_for(msg: str) -> str:
# Task 3(spec §4.1-2/§4.4, 2026-10-08): 缺失渠道轻补逐字模板——与 system.md
# 联系方式引导策略节同源。注入的是"缺失渠道"(客户没给过的),与再索要墙的
# 渠道精确化(Task 1)配套: 墙只拦已在手渠道的索要,这些话术恰好问缺失渠道。
# CC 强制墙(2026-10-08 增补设计 §2)复用同源正文: _CC_ASK_* 是注入指引里
# "请逐字使用这句"的正文;_CC_WALL_* 是墙的整段替换句(EN 加 "Got it — "
# 衔接头,设计 §2 定稿文案;ZH 与正文同句)——两处逐字锚点不漂移。
_CC_ASK_EN = (
"Could you send the full number with the country code, "
"like +966 5x xxx xxxx? Just so our manager can reach you directly.")
_CC_ASK_ZH = (
"麻烦重发一下带国家代码的完整号码呗(比如 +966 5x xxx xxxx)?"
"客户经理直接加你。")
_CC_WALL_EN = "Got it — " + _CC_ASK_EN
_CC_WALL_ZH = _CC_ASK_ZH
_CC_NOTE_EN = (
"⚠️ 轻补指引(系统指令,绝对不要向客户提及这段说明):客户留下的号码不含"
"国家代码,客户经理无法直接回拨。请逐字使用这句:\"Could you send the "
"full number with the country code, like +966 5x xxx xxxx? Just so our "
"manager can reach you directly.\" 仅此一次,客户不给就不再提。")
"国家代码,客户经理无法直接回拨。请逐字使用这句:\"" + _CC_ASK_EN + "\" "
"仅此一次,客户不给就不再提。")
_CC_NOTE_ZH = (
"⚠️ 轻补指引(系统指令,绝对不要向客户提及这段说明):客户留下的号码不含"
"国家代码,客户经理无法直接回拨。请逐字使用这句:\"麻烦重发一下带国家代码"
"的完整号码呗(比如 +966 5x xxx xxxx)?客户经理直接加你。\" "
"国家代码,客户经理无法直接回拨。请逐字使用这句:\"" + _CC_ASK_ZH + "\" "
"仅此一次,客户不给就不再提。")
_EMAIL_NOTE_EN = (
"⚠️ 轻补指引(系统指令,绝对不要向客户提及这段说明):客户已留号码但还没"
@@ -285,6 +294,11 @@ class ConversationManager:
# (confirmed/suspect 已在 Task 3 写回处算好,此处复用)
has_contact = bool(_confirmed_now)
_confirmed_channels = set(_confirmed_now)
# CC 强制墙(2026-10-08 增补设计 §2): 本轮注入的是国家代码追问时,
# 输出侧确定性强制。流式无法撤回 → 触发轮不逐句流出,整段缓冲到
# 流末统一判 "country code" 在场与否(缓冲期间价格/零号码/再索要
# 三墙照常逐句检查,优先级不变)。提示词层全部保留,墙只是兜底。
cc_wall = completion_marks == ["_cc_asked"]
yield {"type": "stream_start"}
emitted = ""
@@ -292,6 +306,7 @@ class ConversationManager:
violated = False
dropped_ask = False # Task 5b: 本轮是否有索要句被再索要墙丢弃
dirty = ""
cc_buffer: list[str] = [] # CC 触发轮: 已过墙句子的延迟流出缓冲
# 15.3: 双墙裁决——价格墙检查在前: 同句双违规时价格路径话术优先
# (职能边界话术更具体,且自带留资引导);仅纯号码/邮箱违规时
# violation=="no_contact",用零号码墙替换句
@@ -318,6 +333,9 @@ class ConversationManager:
conv.id, sentence[:60])
dropped_ask = True
continue
if cc_wall:
cc_buffer.append(sentence) # 墙照检,流出延后到流末
continue
for i in range(0, len(sentence), 40):
yield {"type": "stream_chunk", "content": sentence[i:i + 40]}
emitted += sentence
@@ -337,6 +355,8 @@ class ConversationManager:
logger.warning("Re-ask wall dropped tail (conv=%s): %r",
conv.id, pending[:60])
dropped_ask = True
elif cc_wall:
cc_buffer.append(pending)
else:
for i in range(0, len(pending), 40):
yield {"type": "stream_chunk", "content": pending[i:i + 40]}
@@ -361,6 +381,29 @@ class ConversationManager:
for i in range(0, len(closing), 40):
yield {"type": "stream_chunk", "content": closing[i:i + 40]}
emitted += closing
# CC 强制墙裁决(2026-10-08 增补设计 §2): 触发轮缓冲全文在此判定——
# 含 "country code"(中文轮认 "国家代码",与 _CC_NOTE_ZH 注入的逐字
# 追问句同词)→ GLM 自发服从,缓冲原样流出,零干预;不含 → 整段替换
# 为 CC 模板句正文。violated(价格/零号码墙)优先于本墙——沿用双墙
# 裁决"更具体话术优先"惯例;_cc_asked 已在流前落库,一次性消耗不受
# 替换影响。缓冲轮全部实义句被再索要墙丢弃时 cc_buffer 为空 → 同样
# 走替换(模板句即本轮唯一实义输出,天然不触发下方兜底)。
if cc_wall and not violated:
cc_full = "".join(cc_buffer)
if "country code" in cc_full.lower() or "国家代码" in cc_full:
for i in range(0, len(cc_full), 40):
yield {"type": "stream_chunk", "content": cc_full[i:i + 40]}
emitted = cc_full
else:
logger.warning(
"CC enforcement wall replaced reply (conv=%s): %r",
conv.id, cc_full[:60])
cc_template = (_CC_WALL_ZH if _CJK_RE.search(content or "")
else _CC_WALL_EN)
for i in range(0, len(cc_template), 40):
yield {"type": "stream_chunk",
"content": cc_template[i:i + 40]}
emitted = cc_template
# Task 5b 评审修正: 整条回复只有索要句(全被丢)→ 兜底流出"经理主动
# 联系"句,绝不空回复。与上方墙收尾互斥——violated 路径已自带收尾,
# 此处仅在无任何实义内容流出时接管;已流出的纯空白前缀一并作废,