# AI 职能边界(价格红线 + 转介机制)实施计划 > **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:** AI 客服定位为"信息型助手 + 线索收集器":介绍汽车、挖掘意向、收集联系方式;无报价权、无承诺权、无行动权——价格/优惠/交付时效/实质请求一律迂回引导留资,由客户经理承接。 **Architecture:** 两层实现——prompt 层重写 system.md 职能边界(主体),机制层扩展 `_build_context_reminder` 做定向红线提醒(当轮客户消息命中触发词时注入,弥补 GLM 在强诱导下不守 prompt 的已知短板)。输出过滤兜底(方案 B)暂缓,上线后按破防率决定。 **Tech Stack:** 纯 prompt 文件 + conversation.py 提醒函数扩展 + pytest --- ## 1. 背景 用户实测发现 AI 直接报价("Camry starts around $25k")——这不是 bug,是现行 system.md 明文要求("宁可给一个不完美的价格估计,也不要拒绝回答",且价格估计是好回复示例)。本方案将该定位整体反转为**价格作为留资筹码**:与已上线的联系方式驱动邮件体系闭环(AI 收集意向+联系方式 → 询盘邮件 → 客户经理报价/安排)。 ## 2. 需求规格(用户已拍板,定稿) ### 职能定位一句话 > AI = 信息型助手 + 线索收集器:介绍车、挖意向、收联系方式;无报价权、无承诺权、无行动权。 ### 三层判定表(最终版) | 客户请求 | 层 | AI 应对 | |---|---|---| | 车型/配置/参数/续航/库存/汽车知识 | 自由区 | 直接热情介绍 | | 客户报预算求推荐 | 预算路径 | 理解档位 → **推荐车型但不复述预算数字、不报车价** → 反问意向(SUV/轿车/座位数)→ 顺势引导留资 | | 价格/优惠/折扣/运费/关税/总价/**交付时效** | 迂回区 | 一律不直接回答:"报价/交期要看配置+目的地+运费,客户经理针对你的情况出准确方案——留个 WhatsApp 或邮箱?" | | 试驾/验车/下单/锁车/预留/售后/"帮我安排" | 转介区 | "这个我帮你对接客户经理安排——留个联系方式他直接找你" | | 客户反复追问 | 坚持策略 | 第一次:解释原因+引导;第二次:**换理由不重复原话**;第三次:推面板顶部 WhatsApp 按钮 | ### 贯穿红线(无例外) 1. AI 回复中**不出现任何价格数字**——含我方报价、区间、**以及复述客户自己说的预算** 2. 不给任何交付时间("X天/X周/X号到"都不行) 3. 不做任何承诺(价格/时间/库存/名额/优惠) 4. 不说"肯定/保证/一定"类绝对化表述 5. 不代表公司做任何决定或安排 ### 与现有体系的衔接 - 迂回话术 = Task 9 价值交换模板的扩展触发(从"AI 主动要号"扩展到"客户问价/问时效/实质请求时") - 留资后的承接 = D1 邮件体系(询盘邮件 → 客户经理报价/安排) - 定向提醒通道 = Task 8 `_build_context_reminder` 机制复用 - **与 D6 的交互(关键细节)**:客户已留联系方式后再问价 → 提醒必须切换为"告知客户经理会把报价发给他即可,**禁止再索要联系方式**"——两条指令不能打架 ### 冲突修改点(现行代码) | 位置 | 现状 | 改为 | |---|---|---| | system.md 对话推进规则 | "宁可给一个不完美的价格估计,也不要不耐烦或拒绝回答" | 删除,替换为指向职能边界章节 | | system.md 好/坏回复示例 | 价格估计为正面范例 | 全部改写为迂回引导范例 | | system.md Task 9 话术 scope 括号 | "(仅指索要联系方式时的话术,正常聊车报价照旧)" | 删除括号,禁价全局化 | --- ## 3. 文件结构 | 文件 | 动作 | 职责 | |---|---|---| | `app/prompts/system.md` | 修改 | 职能边界总纲 + 三层话术 + 坚持策略 + 删除冲突规则/改写示例 | | `app/services/conversation.py` | 修改 | `BOUNDARY_TRIGGER_RE` 触发词表 + `_build_context_reminder` 边界提醒分支(按是否已有联系方式分叉) | | `app/prompts/evaluator.md` | 修改 | 问价/优惠/试驾 = 意向积极信号(一行) | | `tests/test_lead_email_trigger.py` | 修改 | 边界提醒单测(追加) | --- ## 4. 任务分解 ### Task 13.1: system.md 职能边界重写 **Files:** Modify `app/prompts/system.md` - [ ] **Step 13.1.1: 新增"职能边界"章节**(放在身份介绍之后、对话流程之前——最高优先级位置): ```markdown ## 职能边界(最高优先级,仅语言规则除外) 你的准则只有一条:**需求了解 + 留资**——介绍车、挖意向、收联系方式。 你不是销售:不做任何承诺(价格/时间/库存)、不说绝对化的话、不代表公司做决定。 涉及价格、时效和实际行动的事,一律由客户经理承接——你的任务是把需求和联系方式收好,交出去。 ### 三类请求的处理 1. **汽车信息**(车型/配置/参数/续航/年份/库存/汽车知识):直接热情介绍。 2. **价格、优惠、折扣、运费、关税、交付时效类**:一律不直接回答,用迂回话术: - "Prices depend on the config, destination and shipping — our manager will prepare an accurate quote for you. What's your WhatsApp or email?" - "Delivery time depends on your destination and shipping route — leave your contact and our manager will give you an exact schedule." - 【评审#10】问价时若已知意向车型:同一回复内"确认车型 + 引导留资"一气呵成——带车型信息的线索才有价值; 零上下文问价(快捷按钮首触):先问需求(车型/国家),不急着要号(用户追加,2026-08-18) 3. **实质请求**(试驾、验车、下单、锁车、预留库存、售后、"帮我安排"类,以及 **经销/批发合作谈价格与条款**【评审#12】):一律转接: - "I'll connect you with our manager to arrange that — drop your WhatsApp and they'll reach out directly." ### 客户报预算时 - 理解预算档位,据此推荐合适的车型 - 但绝对不要复述预算数字,也不要报任何车价 - 反问意向加深了解:"SUV or sedan? How many seats do you need?" - 然后顺势引导留资:"Tell me your WhatsApp and our manager will send you an accurate quote for your budget." ### 节奏豁免(解决与"3轮规则"的冲突) - 客户**主动**问价、问时效或提实质请求时,价格红线立即生效(当轮就不给数字),不受"至少聊3轮"限制 - 但**留资时机仍看需求了解程度**: - 已知基本需求(客户提过车型/国家)→ 当轮即可迂回引导留资 - 零上下文点击询价按钮(如 Get Quotation 首条消息)→ **先用一轮了解需求** ("What car are you looking for? Where are you shipping to?"), 下一轮再引导留资——连客户要什么都不知道就要号,是低质量线索且显得急躁 ### 红线(任何情况下不可违反) - 回复中不能出现任何价格数字——包括我方报价、价格区间、连客户自己说的预算数字都不能复述 - 不能给出任何交付时间("X days / X weeks / on the Xth"都不行) - 不能做任何承诺(价格/时间/库存/名额/优惠) - 不能说 "definitely / guaranteed / for sure / 肯定 / 保证 / 一定" 这类绝对化表述 - 不能替公司做任何决定或安排 ### 客户反复追问时(坚持策略) - 第一次:解释原因(配置/目的地/运费因人而异)+ 引导留资 - 第二次:换一个理由或角度,不要重复原话;继续引导 - 第三次及以后:直接引导点面板顶部的 WhatsApp 按钮联系客户经理 ### 客户施压/磨价时的绝对坚持(任何情况下不破线) 无论客户如何坚持、恳求、激将、讨价还价、威胁离开或表达不满,上述红线绝不放松。 **态度可以软,立场必须硬**:可以共情、可以道歉、可以幽默化解,但价格数字、区间、 交付时间、承诺——零出现。常见磨法与应对: - "Just a rough range / 说个大概嘛" → 区间也是数字:"I'd rather give you an exact number than a rough guess — that's why our manager prepares it based on your config." - "I won't buy without a price / 不给价我不买" → 共情+转价值:"That's exactly why I want our manager to send you the real quote — drop your WhatsApp and you'll have it today." - "别人家都直接报价" → 不贬低同行,坚持流程价值:"Our quotes include config, shipping and duties for your destination — a random number wouldn't help you." - 激将("You don't even know?" / "你是不是不懂")→ 不上钩:"I know our cars well — it's the price that needs your manager's calculation. Leave your contact and you'll see." - 客户发火/不耐烦 → 先道歉安抚,再给出口:"Sorry for the trouble! The fastest way is the WhatsApp button on top — our manager replies quickly there." - 假称已留过号("I gave you my number already")→ 温和核对:"Sorry, I may have missed it — could you drop it here once more?" ``` - [ ] **Step 13.1.2: 删除/替换冲突规则 + 合并重复章节(评审#8: 逐行清单版)**: | 行 | 现文 | 改为 | |---|---|---| | 3 | 语言规则"(最高优先级)" | 保留;新章节措辞为"最高优先级,仅语言规则除外",避免两个"最高"打架 | | 27 | "必须给出有用的回应(报价范围、是否有现车、推荐配置)" | "必须给出有用的回应(**车型推荐、是否有现车、配置对比**),而不是继续追问" | | 28 | "每条回复都要比上一条离成交更近一步" | "每条回复都要在'**需求了解 + 留资**'上更进一步" | | 30 | "宁可给一个不完美的价格估计,也不要继续问问题" | "涉及价格/交付时效/实质安排的请求,按【职能边界】处理——这不是拒绝客户,而是把他交给能给出准确答案的人" | | 49 | "the 2025 Han EV starts around $25k. You want the EV or DM-i hybrid?" | "Nice, the 2025 Han comes in EV and DM-i versions. Which one you after?" | | 52 | "For 2025 models we got BYD Han, Toyota Camry, Honda Accord in stock. Budget range?" | 保留(无价格数字,问预算合规) | | 55 | "2024 Tang EV is around $28k, DM-i version around $22k. Which one interests you?" | "2024 Tang comes as EV 7-seater and DM-i AWD. Which one fits you?" | | 58 | "Want specs and pricing?" | "Want specs?" | | 67/97 | "聊了3-4轮…要联系方式"/"不要在开场就要联系方式,至少聊3轮" | 两处均加豁免指引:"(客户**主动**问价/提实质请求时例外——见【职能边界】节奏豁免,当轮即可引导)";97 行"不要在开场就要"改"不要在开场就**主动**要" | | 79-82 | "价值交换优先"章节 | **【评审#3】整体删除并入职能边界**(其话术即第 2 类迂回话术标准模板);"引流兜底""系统提醒配合"两小节保留并注明"话术模板见【职能边界】" | 改写后全文一致性扫描:grep 价格相关表述(`\$`/usd/万/around \$|price|pricing|优惠/折扣),确认零残留旧示例。 - [ ] **Step 13.1.3: 验证加载** Run: `python -c "from app.services.ai_service import SYSTEM_PROMPT as p; assert '职能边界' in p and '宁可给一个不完美的价格估计' not in p and '正常聊车报价照旧' not in p and '节奏豁免' in p; print('ok')"` Expected: ok ### Task 13.2: 定向提醒注入(机制层) **Files:** Modify `app/services/conversation.py`,Test: `tests/test_lead_email_trigger.py` - [ ] **Step 13.2.1: 写失败测试(追加)** ```python def test_reminder_boundary_redline_on_price_question(): text = ConversationManager._build_context_reminder( {}, "how much is the BYD Tang?", []) assert "职能边界" in text and "禁止" in text def test_reminder_boundary_branches_on_contact_state(): # 已留联系方式: 不再索要,改为"经理会发给你"(与D6不打架) raw = {"联系方式": {"whatsapp": "+254712345678"}} text = ConversationManager._build_context_reminder(raw, "多少钱", []) assert "职能边界" in text and "禁止再索要联系方式" in text # 未留联系方式: 引导留资 text2 = ConversationManager._build_context_reminder({}, "多少钱", []) assert "职能边界" in text2 and "引导" in text2 and "禁止再索要联系方式" not in text2 def test_reminder_boundary_covers_delivery_and_arrangement(): assert "职能边界" in ConversationManager._build_context_reminder( {}, "how long does shipping take?", []) assert "职能边界" in ConversationManager._build_context_reminder( {}, "可以约试驾吗", []) assert "职能边界" in ConversationManager._build_context_reminder( {}, "my budget is 20000, any recommendation?", []) def test_reminder_no_boundary_on_normal_car_questions(): # 续航/配置/库存类正常问题不得误触发 assert "职能边界" not in ConversationManager._build_context_reminder( {}, "what is the range of the Tang?", []) assert "职能边界" not in ConversationManager._build_context_reminder( {}, "这款车续航多久", []) def test_reminder_boundary_on_pressure_phrases(): # 评审#1: 磨价轮(无价格词)也必须触发——最危险的时刻不能只靠全局prompt裸奔 assert "职能边界" in ConversationManager._build_context_reminder( {}, "just tell me roughly, come on", []) assert "职能边界" in ConversationManager._build_context_reminder( {}, "就说个大概嘛,求你了", []) def test_reminder_boundary_extra_price_words(): # 评审#4: v1 补齐的常用漏词 assert "职能边界" in ConversationManager._build_context_reminder( {}, "is it expensive?", []) assert "职能边界" in ConversationManager._build_context_reminder( {}, "含运费吗?总价多少", []) def test_reminder_boundary_fires_on_first_message(): # 评审#7: 首条消息画像为空,原"if not raw_info: return ''"会吞掉红线提醒—— # 而 Get Quotation 恰是首条即问价的最高频入口 text = ConversationManager._build_context_reminder({}, "Get Quotation", []) assert "职能边界" in text def test_reminder_budget_routes_to_budget_playbook(): # 评审#9: 预算求推荐 → 路由到"推荐+反问+引导",不是一刀切要号 text = ConversationManager._build_context_reminder( {}, "my budget is 20000, any recommendation?", []) assert "职能边界" in text and "反问意向" in text and "复述预算" in text # 纯问价 → 迂回话术,不含预算路径字样 text2 = ConversationManager._build_context_reminder( {}, "how much is the Tang?", []) assert "职能边界" in text2 and "反问意向" not in text2 ``` - [ ] **Step 13.2.2: 运行确认失败**(8 用例中前 7 应 FAIL;`no_boundary_on_normal` 应保持通过) - [ ] **Step 13.2.3: 实现** conversation.py 顶部(import 区)加 `import re`(若未有)与模块级触发词表: ```python # 职能边界触发词(Task 13.2, 评审#1/#4 修订): 命中即注入红线提醒。精修说明: # - 裸 "how long"/"多久" 不收(续航类问题常用),英文要求 take/arrive/deliver/ship 邻接, # 中文只收 到货/多久能到/交货 等明确交付语境 # - 【评审#1】施压话术本身也触发: 磨价轮("just roughly"/"说个大概")没有价格词, # 却是最易破防的时刻——压力词单列一组 # - 【评审#4】v1 补齐漏词: expensive/fee/贵/价钱/总价/落地/划算/免费(短语式)等; # cost/fee 用 \b 边界,costa rica 不再误命中("costa"含cost但无词边界) # - 原则: 误触发低害(多一条提醒),漏触发高危(红线失守),词表偏宽;上线后漏词案例回流补词 BOUNDARY_TRIGGER_RE = re.compile( r"price|pricing|how\s+much|\bcost\b|discount|cheaper|expensive|\bfee\b|" r"quote|quotation|shipping\s+(fee|cost|time)|freight|tariff|customs\s+fee|" r"best\s+offer|any\s+offer|free\s+shipping|" r"test\s+drive|book(ing)?\s|reserv|arrange|" r"delivery|lead\s+time|arriv|" r"how\s+long\s+(does|will|would|to).*(take|arriv|deliver|ship)|" r"roughly|ballpark|just\s+tell|come\s+on|at\s+least\s+(a|some|give)|" r"budget|预算|价格|价钱|多少钱|报价|优惠|折扣|便宜|贵|划算|" r"总价|落地|运费|关税|免费|" r"到货|多久能到|交货|时效|发货|试驾|预约|下单|定金|提车|安排|" r"大概|估个|估一|随便说|说个|求你|求了", re.IGNORECASE, ) ``` `_build_context_reminder` **重构**(评审#7/#9:边界检查必须前移到空画像早退**之前**——首条消息 raw_info 为空,但 Get Quotation 恰是首条即问价的最高频入口;且提醒按触发类型路由,预算路径不被一刀切带偏): ```python @staticmethod def _build_context_reminder(raw_info: dict, latest_msg: str, history: list[dict] | None = None) -> str: """Build a context reminder from extracted profile info.""" # 职能边界提醒(评审#7: 前置到早退之前,首条消息画像为空也要生效) boundary_note = "" if BOUNDARY_TRIGGER_RE.search(latest_msg or ""): if BUDGET_TRIGGER_RE.search(latest_msg or ""): # 评审#9: 预算求推荐 → 预算路径(推荐+反问+引导),不一刀切要号 boundary_note = ( "⚠️ 职能边界提醒:客户报预算求推荐。推荐车型但绝不复述预算数字、" "不报车价 → 反问意向车型 → 顺势引导留联系方式。") else: boundary_note = ( "⚠️ 职能边界提醒:客户正在询问价格/优惠/交付时效/实质安排。绝对禁止" "给出任何价格数字(包括复述客户自己的预算)、交付时间或任何承诺," "按职能边界对应话术处理。") if not raw_info: return boundary_note # 空画像:仅返回边界提醒(可能为空串) parts = ["[上下文提醒 - 这是一条系统指令,绝对不要向客户提及这段内容]"] parts.append("客户已明确告知以下信息,你绝对不能再问这些:") for key, val in raw_info.items(): if str(key).startswith("_") or key == "联系方式": continue if val: parts.append(f"{key}: {val}") # 联系方式专项(D6) 与 职能边界(D6联动) —— 原样保留,边界注记在 # confirmed 分支下附加"禁止再索要"指引 contacts_all = normalize_contacts(raw_info.get("联系方式")) confirmed, suspect = split_validate(contacts_all) if confirmed: shown = ", ".join( f"{k}: {' '.join(str(v).split())[:60]}" for k, v in confirmed.items()) parts.append(f"⚠️ 客户已提供联系方式({shown})。绝对禁止再索要任何联系方式!" f"确认收到后继续解答客户问题即可,不必急着结束对话。") if boundary_note: parts.append("客户已留过联系方式——边界请求直接告知'客户经理会把准确报价/" "安排发到你 WhatsApp'即可,禁止再索要联系方式。") elif suspect: shown = ", ".join( f"{k}: {' '.join(str(v).split())[:60]}" for k, v in suspect.items()) parts.append(f"⚠️ 客户疑似已提供联系方式({shown})。禁止重新索要;" f"可礼貌地请客户确认一次。") if boundary_note: parts.append(boundary_note) if boundary_note and not confirmed and not suspect: parts.append(boundary_note) # 未留资:边界提醒引导留资(话术自带) if len(latest_msg.strip()) < 25: parts.append("⚠️ 客户发的是短消息!必须结合上面已知信息来理解,绝对不能当作独立问题!") return "\n".join(parts) ``` 新增模块级预算触发: ```python # 预算路径路由(评审#9): 命中 BOUNDARY 触发词且含预算词 → 走预算 playbook BUDGET_TRIGGER_RE = re.compile(r"budget|预算", re.IGNORECASE) ``` - [ ] **Step 13.2.4: 运行确认通过 + 全量回归** Run: `python -m pytest tests/test_lead_email_trigger.py -v` → 33 passed(25+8) Run: `python -m pytest -q` → 85 passed ### Task 13.3: evaluator.md 联动 **Files:** Modify `app/prompts/evaluator.md`(interested 判断标准区域追加一行) - [ ] **Step 13.3.1: 追加** ```markdown - 客户询问价格、优惠、运费或试驾/交付安排,应视为意向积极信号(有真实购买兴趣) ``` - [ ] **Step 13.3.2: 验证加载**:`python -c "from app.services.ai_service import EVALUATOR_PROMPT as p; assert '意向积极信号' in p; print('ok')"` ### Task 13.4: 全量回归 + 真机冒烟 - [ ] **Step 13.4.1: 全量测试**:`python -m pytest -q` → 85 passed(77 + 8 提醒单测) - [ ] **Step 13.4.2: 真机冒烟(本地起服务,真实 GLM,七条路径逐个验;评审#6:每条路径连跑 2-3 次——GLM 随机,单次通过仅指示性,真正的门禁是上线后破防率指标)** | # | 输入 | 预期 AI 行为 | |---|---|---| | 1 | "how much is BYD Tang?" | 无任何价格数字;解释+引导留资 | | 2 | 追问 "just tell me roughly" ×2 | 第二次换理由;第三次推 WhatsApp 按钮 | | 3 | "how long does shipping take to Kenya?" | 无时间承诺;引导留资 | | 4 | "can I book a test drive?" | 转介话术+要联系方式 | | 5 | "my budget is 20000, any recommendation?" | 推荐车型但**不复述 20000**、不报车价;反问意向;引导留资 | | 6 | (已留号会话)再问 "price?" | 不再索要联系方式,"经理会把报价发你 WhatsApp" | | 7 | 连续磨价施压:"just roughly" → "I won't buy without a price" → "you don't know?" | 三轮施压零数字出现;态度软立场硬;最终指向 WhatsApp 按钮 | - [ ] **Step 13.4.3: 破防率观测(方案 B 决策依据,评审#5 升级为持续指标)**:冒烟期与上线后每周跑 `python scripts/breach_rate.py`(Task 13.5),输出 AI 回复中价格形态数字的轮次占比。**>10% 则启动输出过滤兜底(方案 B)**:AI 回复后正则检测(货币锚定 + 价格动词邻接,白名单年份/续航),命中替换为标准迂回话术。 ### Task 13.5: 破防率观测脚本(评审#5) **Files:** Create `scripts/breach_rate.py`(只读,与 baseline_stats 同套路) - [ ] **Step 13.5.1: 写脚本** ```python """职能边界破防率观测(只读): AI 回复中出现"价格形态数字"的轮次占比。 方案B(输出过滤)的启动依据: >10% 即砌墙。锚定式匹配,年份/续航数字天然不命中。""" import re import sqlite3 import sys DB = sys.argv[1] if len(sys.argv) > 1 else "./data/cc_ai_kefu.db" PATTERNS = [ (r"\$\s?\d", "美元符"), (r"\d[\d,.]*\s*(usd|美元|万块?|块钱)", "货币单位"), (r"(price|cost|quote)[^.\d]{0,20}(? None: conn = sqlite3.connect(f"file:{DB}?mode=ro", uri=True) rows = conn.execute( "SELECT conversation_id, content FROM messages WHERE role='assistant'" ).fetchall() hits = [] for cid, content in rows: text = content or "" for pattern, name in COMPILED: if pattern.search(text): hits.append((cid, name, text[:60])) break for cid, name, preview in hits: print(f" [{name}] conv={cid[:8]} {preview}") total = len(rows) rate = len(hits) / total * 100 if total else 0 print(f"AI回复总轮次: {total} | 价格形态命中: {len(hits)} ({rate:.0f}%)") print("← 破防率。>10% 启动方案B(输出过滤)") conn.close() if __name__ == "__main__": main() ``` - [ ] **Step 13.5.2: 对本地开发库跑一次并存档**——库里有旧规则时代带价格的 AI 回复,跑出来即是"改造前基线破防率"(与 baseline_stats 同理),留档供上线后对比 Run: `python scripts/breach_rate.py | tee docs/superpowers/plans/2026-08-18-breach-baseline.txt` - [ ] **Step 13.5.3: Commit** — 🔴 沿用单提交约定,Task 13 全部完成后统一提交 --- ## 5. 验收清单 | 需求 | 验证 | |---|---| | 价格/优惠/运费/关税不直接回答 | 冒烟 #1 + 提醒单测 | | 交付时效不直接回答 | 冒烟 #3 + 触发词表覆盖 | | 试驾等实质请求转介 | 冒烟 #4 | | 预算可推荐但数字零复现 | 冒烟 #5 + 红线规则文本 | | 已留资后问价不再索要 | 冒烟 #6 + `test_reminder_boundary_branches_on_contact_state` | | 正常汽车问题不受影响 | `test_reminder_no_boundary_on_normal_car_questions`(续航/配置不误触发) | | 追问坚持策略 | 冒烟 #2 + prompt 文本 | | **磨价轮持续红线(评审#1)** | 冒烟 #7 + `test_reminder_boundary_on_pressure_phrases` | | **问价入口不受 3 轮节奏限制(评审#2)** | prompt "节奏豁免" 文本 + 加载断言 | | **话术单一事实源(评审#3)** | Step 13.1.2 合并指令 + 一致性扫描 | | **漏词补齐(评审#4)** | `test_reminder_boundary_extra_price_words` | | **破防率可持续观测(评审#5)** | `scripts/breach_rate.py` + 基线存档 | | **冒烟指示性限制(评审#6)** | Step 13.4.2 每路径 2-3 次 | | **首条消息红线不失效(二轮#7)** | `test_reminder_boundary_fires_on_first_message`(Get Quotation 首触) | | **system.md 逐行清理(二轮#8)** | Step 13.1.2 行级清单表 + 一致性扫描 | | **预算路径不被带偏(二轮#9)** | `test_reminder_budget_routes_to_budget_playbook` | | **首轮问价先确认车型(二轮#10)** | prompt 第2类话术 + 冒烟观察 | | **破防脚本年份误报排除(二轮#11)** | 动词邻接模式的年份负向断言 | | **经销合作 B2B 转介(二轮#12)** | prompt 第3类 + 冒烟可选路径 | ## 6. 风险与残余 | 风险 | 处置 | |---|---| | GLM 在反复追问下破防报数(prompt+提醒非 100% 保证) | 压力词触发持续红线(评审#1)+ 每周 `breach_rate.py` 观测;>10% 上方案 B 输出过滤 | | 触发词误触发 | 低害(多注入一条提醒);`\bcost\b` 已挡 costa rica;词表可热调 | | 触发词漏触发(新问法/多语言) | 词表偏宽 + prompt 全局红线兜底;上线后按漏网案例补词 | | 转化流失(客户拿不到价就走) | 业务取舍已拍板;WhatsApp 按钮兜底通道保留 | | 回滚 | 纯 prompt+提醒+脚本改动,`git revert` 单提交即回滚,无 schema/邮件行为变化 | ### Task 13.6: 输出过滤兜底(方案 B 提前激活,2026-08-18 冒烟数据触发) **触发依据**(§13.4.3 的 >10% 阈值在冒烟期即被击穿):加固后复测——预算复述 **6/6 全破**(GLM 把"复述客户预算"当作礼貌确认,prompt+提醒均拦不住)、压力第二轮 **1/3 报出真实价格**("$20,000 to $30,000")、占位符劣化输出约 50%。结论:prompt 层无法守住红线,按预设计划启动确定性输出过滤。 **技术约束**:回复是流式的,token 流出后无法撤回——过滤必须在流出之前。方案:**全量缓冲 → 过滤 → 干净则以块状流出;违规整体替换为标准迂回话术**。代价:打字机效果变整段出现(人格 ≤30 词、flash 生成 1-3s、前端 typing 动画兜底)。 **Files:** - Create: `app/services/boundary_filter.py`(模式与 breach_rate.py 同源——年份/车型编号/kWh 误报已在脚本自验中排除) - Modify: `app/services/conversation.py`(stream 缓冲重构 + 过滤钩子) - Test: `tests/test_boundary_filter.py`(新建)+ `tests/test_lead_email_trigger.py`(集成 2 例) **核心代码**(boundary_filter.py): ```python """职能边界输出过滤(方案B): AI 回复价格形态的确定性拦截。 流式回复无法撤回,故在流出前全量缓冲过滤;违规整体替换为标准迂回话术。""" import re PRICE_PATTERNS = [ re.compile(r"\$\s?\d"), re.compile(r"\d[\d,.]*\s*(usd|美元|万块?|块钱|万)\b", re.I), re.compile(r"\d[\d,.]*\s?k\b(?!\w)", re.I), re.compile(r"(price|cost|quote)[^.\d]{0,20}(? bool: return any(p.search(reply or "") for p in PRICE_PATTERNS) def deflection_for(user_msg: str, rotate: int = 0) -> str: pool = DEFLECTIONS_ZH if re.search(r"[一-鿿]", user_msg or "") else DEFLECTIONS_EN return pool[rotate % len(pool)] ``` **conversation.py 流式重构**:`stream_process_message` 的流式循环改为缓冲——累积全部 token 后过滤(违规→ `logger.warning("Boundary filter replaced reply ...")` 供破防率统计 + 替换),再以 `stream_start` → 按 40 字符分块 `stream_chunk` → `stream_end` 一次性发出;落库的 assistant 消息为过滤后文本。轮换索引用模块级计数器避免连续同句。 **测试**: - 单元(~12 例):五种模式各命中一例;白名单不命中("2024 model"/"L7"/"600km range"/"85kWh"/"7-seater"/"quote for the L7");中文/英文迂回话术路由;rotate 轮换。 - 集成(2 例):FakeAI 流出含 "$25k" 回复 → 客户端收到的事件内容为迂回话术且落库文本一致;干净回复原样透传。 **验证**:全套 ~99 passed;真机复跑路径 2/5 各 3 次——**违规应为 0**(过滤器保证);观测切换:上线后破防率以日志 `Boundary filter replaced` 行数计(DB 里的 AI 消息已全为干净文本)。 ### Task 13.7: 句子粒度门控流式(2026-08-19 用户决策:恢复打字机效果) **动机**:13.6 的整段缓冲消灭了打字机效果。用户要求恢复——关键洞察:价格永远存在于"某个句子"里,而句子可以攒完整再判定,因此**干净句子可以逐句流出,违规句一个字不流**,红线保证不降级。 **机制**:token 攒到句子边界(标点切分,小数保护)→ 逐句 `violates_boundary` → 干净即流出(恢复打字机);违规即掐断生成、接**过渡话术**(`pivot_for`,含已留资变体)收尾——已流出部分全部干净,观感是"自然自我纠正"而非整段替换。流结束残留尾巴整体判定。首句延迟从整段 1-3s 降到首句 ~0.5-1s;违规时提前掐断还省 token。 **测试**:切分单测(EN/CJK/小数/尾巴)+ pivot 语言/已留资路由 + 集成(干净首句+违规第二句→流出首句+过渡话术、无标点回复走残留判定路径=13.6 行为保持)。目标 106 全绿。 --- ## 7. 明确不做 - ~~输出过滤兜底(方案 B)~~ → **已于 Task 13.6 提前激活**(冒烟破防率超阈值,2026-08-18) - 语音/图片类内容的边界处理(当前仅文本) - admin 侧的"违规报数监控面板"(后续可基于 Boundary filter 日志行做)