49c0e5559c
Co-Authored-By: Claude Code <noreply@anthropic.com>
420 lines
29 KiB
Markdown
420 lines
29 KiB
Markdown
# 公司知识库(Hesu Auto 事实卡)实施方案
|
||
|
||
> **状态:方案定稿——四个待确认问题已全部确认(2026-09-10),待批准执行。**
|
||
|
||
**Goal:** 给 AI 客服补充公司事实知识(品牌/覆盖/网络/经验),使客户问公司情况时**零编造、简短、可信**,且不改变职能边界(不报价/不承诺/不报联系方式)。
|
||
|
||
**Architecture:** 新建独立事实卡文件 `app/prompts/company_facts.md`(知识与行为分离),由 `ai_service.py` 拼接进对话 prompt(+3 行 loader;顺序:system.md 行为 prompt → 事实卡 → 当轮 context 提醒);事实卡内容经输出墙安全审查(每句过价格墙+零号码墙);不动 extractor/evaluator/前端/配置。
|
||
|
||
**Tech Stack:** 零新依赖、零新基础设施。纯 prompt 文件 + 2 行 loader 改动 + 静态安全测试。
|
||
|
||
---
|
||
|
||
## 1. 背景与问题
|
||
|
||
现状:`system.md` 是纯行为 prompt,**不含任何公司事实**。客户问"你们是什么公司/有哪些品牌/送不送到肯尼亚/是不是真的"时,GLM 只能编造。这正是 2026-08-20 changelog 记录过的失效模式——**"prompt 不含真值 → GLM 编造假号"的同款问题,只是从号码换成了公司事实**(编造品牌/国家/年限=信任崩塌,客户流失)。
|
||
|
||
**生产实证(2026-09,Ghana 案例)**:客户问 "I'm in Ghana, do you have branch in Ghana",AI 答 **"Not yet, but we've got partners worldwide..."**——而公司**在 Ghana 有分支**(五分支之一,且有保税仓)。客户就在 Ghana、公司就在当地有网点,本是信任建立的绝佳时机,被知识真空变成当面否认。诊断:GLM 对无法验证的事实问题选择"听起来合理的否定+转进"(hedging),**否定性编造与肯定性编造同样致命**;且此类问题("do you have branch in X")没有特征触发词(只有国名),触发词注入方案(C)无法覆盖——这是选型 A(事实常驻)的最强论据。
|
||
|
||
**编造面全量盘点(2026-09-10,用户发起)**:除公司事实外又发现两个编造向量,均折入 Task 3/6——
|
||
1. **库存/现车断言**:system.md 红线禁"库存承诺",但其"好回复"示例有三处在教 AI 说 "we got tons of them"/"in stock"——本会话已两次实证 GLM 听逐字示例不听抽象红线,示例教唆必被模仿;AI 无任何库存数据源,照示例回答=编造现货。(修正:Task 3 改动 3,按文本锚点定位)
|
||
2. **保修/质保承诺**:价格墙时间模式显式豁免 warranty(`(?!\s*(warranty|质保|保修))`,[boundary_filter.py:22](app/services/boundary_filter.py#L22))——AI 若输出编造的 "6 months warranty" 承诺,墙级零拦截(实证)。**修法升级:Task 6 墙级收紧**(prompt 红线 Task 3 改动 4 为 belt)。复查中还发现**中文时限/质保承诺("3天到货"/"6个月质保")今天全部零拦截**(时间模式只有英文单位)——Task 6 一并补。
|
||
其余编造面(车型公共参数过期、认证资质)接受为观察项,见 §6。
|
||
|
||
知识库要解决的四类真实问题:
|
||
|
||
| 客户问题类型 | 例子 | 需要的事实 |
|
||
|---|---|---|
|
||
| 信任核实 | "are you a real company?" | 首批出口企业/集团背景/累计出口量 |
|
||
| 品牌/产品 | "do you have ZEEKR? trucks?" | 30+ 品牌清单/动力类型/车型类别 |
|
||
| 覆盖范围 | "do you ship to Ghana?" | 70+ 国家/五分支/Dubai 枢纽 |
|
||
| 运作模式 | "how does this work?" | 中国采购→迪拜枢纽→海外交付 |
|
||
|
||
## 2. 方案选型
|
||
|
||
| | A. 静态事实卡(推荐) | B. RAG 检索 | C. 触发词条件注入 |
|
||
|---|---|---|---|
|
||
| 做法 | 事实卡文件常驻 prompt | 向量/关键词检索,按轮注入相关块 | 仿 CONTACT_ASK_TRIGGER_RE,问公司才注入 |
|
||
| 优点 | 零新设施;事实常在,漏触发=零;单 commit 回滚;与现有架构(prompt 驱动)完全一致 | token 省;可扩展到车型库存/FAQ | token 省 |
|
||
| 缺点 | 每轮 +~700-900 token(实测草案 1289 chars;glm-4-flash 成本可忽略) | 需引入检索设施(本项目零基础);**检索漏=编造风险回归**;新失效模式 | 触发漏(客户间接问)→ 编造风险回归 |
|
||
| 结论 | ✅ **本期采用** | KB 长大到库存/FAQ 规模再议 | 事实卡膨胀后再优化,loader 设计已留位 |
|
||
|
||
**选 A 的决定性理由**:本项目的可靠性哲学是"确定性优先"——RAG/触发词都引入"该注入时没注入"的 probabilistic 失效,而编造的代价(信任崩塌)远高于每轮几百 token。
|
||
|
||
## 3. 事实卡内容草案(即 Task 1 的文件内容)
|
||
|
||
```
|
||
# 公司事实卡(仅供回答客户关于公司的事实性问题)
|
||
|
||
## 我是谁
|
||
- Hesu Auto 是 Hesu Group 旗下专注汽车出口的公司
|
||
- 中国首批汽车出口企业之一,累计出口 50,000+ 台(用户已确认,2026-09-10)
|
||
- **累计出口到 70+ 国家与地区**(中东、非洲、南美、欧洲)——⚠️ 出口目的国口径,不是分支/网点覆盖(用户明确区分,2026-09-10)
|
||
- 与 30+ 汽车品牌深度合作:BYD、Changan、Geely、Leopard、ROX、Nissan、Zeeker、Li Auto 等
|
||
|
||
## 卖什么
|
||
- **新车与二手车都做**(用户已确认)
|
||
- 全动力类型:燃油、混动、插混、纯电
|
||
- 全车型类别:轿车、SUV、皮卡、重卡;乘用车、商用车、新能源车、车队/项目采购
|
||
|
||
## 网络与运作
|
||
- 模式:中国采购供应 → 迪拜(Jebel Ali)枢纽备货分拨 → 海外交付
|
||
- 海外分支五处:Dubai、Ghana、Ethiopia、Djibouti、Belarus
|
||
- Abu Dhabi 有展车节点;Ghana 西非保税仓;Djibouti+Ethiopia 东非协同;Venezuela(南美)在网络节点内(用户确认列入)
|
||
|
||
## 使用规则(与 system.md 红线同级)
|
||
- **统一兜底(最高优先,用户定调 2026-09-10)**:任何不清楚、事实卡没有、需要核实的问题——**不编造、不承诺、不否定、不断言**,统一动作只有一个:顺势引导留联系方式转客户经理——"这个我让我们经理给你准确答复——留个 WhatsApp,他直接联系你"
|
||
- 客户问公司情况/品牌/是否可信时,取上述事实**简短作答(1-2 句)**,建立信任
|
||
- **分支问题与出口覆盖问题是两回事,分开答**:
|
||
- 问分支/网点("do you have branch in X")→ 只认卡里列的:五分支+节点(Dubai/Ghana/Ethiopia/Djibouti/Belarus/Abu Dhabi/Venezuela)必须答"有"并点出当地节点;卡里没有的 → **走统一兜底**——绝对禁止编造否定答案("Not yet"/"we don't have"/"没有")(Ghana 案例教训:公司明明有分支被答成 Not yet)
|
||
- 问出口覆盖("do you ship to X")→ 70+ 国是**累计出口目的国**,不代表 X 一定有网点:答"已出口到 70+ 国家"事实 + **走统一兜底**(X 具体情况经理确认)——不承诺、不否定
|
||
- 绝不主动长篇介绍公司,绝不用成营销腔——风格仍按【说话风格】
|
||
- 涉及具体价格/交付时效/库存承诺 → 仍按【职能边界】转客户经理,本卡不改变任何红线;售后只说"有本地协同支持",不做任何保证
|
||
- 事实卡没写的细节(某国代理、某车型现车、认证资质细节)→ **走统一兜底**(不编造,留资+经理沟通)
|
||
- 数字一律照抄本卡写法(如 50,000+、70+),不改写成中文数字
|
||
```
|
||
|
||
### 内容取舍说明(对照原始资料)
|
||
|
||
| 原资料内容 | 处置 | 理由 |
|
||
|---|---|---|
|
||
| About 正文全篇 | 压缩为"我是谁"4 行 | 营销语气("strives to win recognition")违背说话风格;KB 只要事实 |
|
||
| Contact Us 节 | **整节剔除** | 红线:AI 绝不输出我方联系方式(web 指按钮/App 反向留资,2026-08-20 已定) |
|
||
| "Why Work with HESU" 四点 | 融入"网络与运作"1 行 | 与 Global Network 高度重复,留骨架 |
|
||
| Key Facts "10,000+" | 不采信 | 与 About "50,000+" 矛盾,用户确认以 **50,000+** 为准 |
|
||
| "72+ Countries" | 取 "70+" | About 正文"more than 70"更稳,两处兼容 |
|
||
| "Zeeker" | **保留原文 Zeeker**(用户确认) | 用户口径为准,不作 ZEEKR 规范化 |
|
||
| Venezuela | **列入**(用户确认) | 仅网络图出现,按用户口径列为网络节点 |
|
||
| 新车/二手车定位 | **新旧车都做**(用户确认) | 人设第一句同步改为"汽车出口外贸销售(新车与二手车都做)",Task 3 一并处理 |
|
||
| Leopard、ROX 品牌名 | **保留原文**(用户确认) | 照抄入卡:Leopard、ROX |
|
||
|
||
## 4. 与现有防线/风格的兼容性审查(本方案的核心约束)
|
||
|
||
| 审查项 | 结论 | 说明 |
|
||
|---|---|---|
|
||
| **价格墙** `violates_boundary` | ⚠️ 有陷阱,已规避 | `五万` 这类**中文数字+万**会被裸预算模式击杀(`(两|三...十)\s*万`),AI 引用即被墙拦截替换成迂回话术——张冠李戴。**事实卡强制阿拉伯数字**(50,000+),Task 2 的墙扫描测试会把关 |
|
||
| **零号码墙** `no_contact_value` | ✅ 安全 | 卡内数字均 <10 位(50,000 逗号断开=5 位);无邮箱;Task 2 扫描钉死——**同时防止未来有人往 KB 里加电话/邮箱** |
|
||
| **再索要墙** `asks_contact` | ✅ 交互正确(实证) | 使用规则里的兜底话术"留个 WhatsApp,他直接联系你"**命中再索要墙**——客户已留资时 GLM 复读兜底话术会被正确丢弃(墙换成"经理主动联系"版),未留资时放行(合法引导)。两层设计自洽,无需处理 |
|
||
| **职能边界** | ✅ 显式锁定 | 使用规则第 3 条:事实不改变报价/承诺转接;交付措辞避免时限含义("faster delivery"不进卡) |
|
||
| **说话风格** | ✅ 规则化 | 1-2 句简短作答,禁营销腔;品牌/地名保留英文(GLM 按客户语言自然组织) |
|
||
| **extractor/evaluator** | ✅ 不受影响 | 两者用独立 prompt,不拼事实卡;extractor 只提取客户信息 |
|
||
| **token 成本** | ✅ 可忽略 | 实测草案 1289 chars ≈ 700-900 GLM token/轮(glm-4-flash 成本仍可忽略);换"事实常在零漏触发" |
|
||
| **回滚** | ✅ | 事实卡文件+2 行 loader,单 commit revert |
|
||
|
||
## 5. 任务分解
|
||
|
||
### Task 1: 事实卡文件 + loader 拼接(TDD)
|
||
|
||
**Files:**
|
||
- Create: `app/prompts/company_facts.md`(§3 草案,按 §7 确认结果定稿)
|
||
- Modify: `app/services/ai_service.py`(+3 行)
|
||
- Create: `tests/test_company_facts.py`
|
||
|
||
- [ ] **Step 1: 失败测试——prompt 组装顺序**
|
||
|
||
```python
|
||
"""公司事实卡: 拼接位置与墙安全性静态测试。"""
|
||
from pathlib import Path
|
||
|
||
from app.services.ai_service import AIService, COMPANY_FACTS, SYSTEM_PROMPT
|
||
|
||
FACTS_PATH = Path("app/prompts/company_facts.md")
|
||
|
||
|
||
class _CapturingProvider:
|
||
def __init__(self):
|
||
self.prompts = []
|
||
|
||
async def chat(self, messages, system_prompt):
|
||
self.prompts.append(system_prompt)
|
||
return "ok"
|
||
|
||
async def stream_chat(self, messages, system_prompt):
|
||
self.prompts.append(system_prompt)
|
||
for t in ("ok",):
|
||
yield t
|
||
|
||
|
||
def test_facts_concatenated_between_system_and_context():
|
||
# 顺序: 行为 prompt → 事实卡 → 当轮提醒(提醒必须最后,保持最新鲜)
|
||
svc = AIService(provider=_CapturingProvider())
|
||
svc.provider.prompts.clear()
|
||
import asyncio
|
||
asyncio.run(svc.chat([{"role": "user", "content": "hi"}], context="⚠️ 提醒"))
|
||
p = svc.provider.prompts[0]
|
||
assert "Hesu" in p # 事实在场
|
||
assert p.index(SYSTEM_PROMPT) < p.index(COMPANY_FACTS) < p.index("⚠️ 提醒")
|
||
|
||
|
||
def test_facts_absent_from_extractor_prompt():
|
||
# extractor/evaluator 不拼事实卡(它们有自己的 prompt,不该看到公司事实)
|
||
from app.services.ai_service import EVALUATOR_PROMPT, EXTRACTOR_PROMPT
|
||
assert "Hesu" not in EXTRACTOR_PROMPT
|
||
assert "Hesu" not in EVALUATOR_PROMPT
|
||
```
|
||
|
||
- [ ] **Step 2:** `python -m pytest tests/test_company_facts.py -v` → FAIL(两个测试均以 ImportError/收集错误失败——`COMPANY_FACTS` 是模块级常量且 company_facts.md 未建,属预期红灯形态;本文件仅此两测试,无部分通过预期被破坏)
|
||
|
||
- [ ] **Step 3: 实现**
|
||
|
||
`ai_service.py`:
|
||
```python
|
||
SYSTEM_PROMPT = _load_prompt("system.md")
|
||
COMPANY_FACTS = _load_prompt("company_facts.md") # 公司事实卡(知识与行为分离)
|
||
EXTRACTOR_PROMPT = _load_prompt("extractor.md")
|
||
EVALUATOR_PROMPT = _load_prompt("evaluator.md")
|
||
|
||
# 对话基座: 行为 prompt 在前(红线显著性),事实卡居中(参考资料),
|
||
# 当轮 context 提醒最后拼接(在 chat/stream_chat 内,保持最新鲜)
|
||
_BASE_CHAT_PROMPT = SYSTEM_PROMPT + "\n\n" + COMPANY_FACTS
|
||
```
|
||
`chat`/`stream_chat` 内 `SYSTEM_PROMPT + "\n\n" + context` 改为 `_BASE_CHAT_PROMPT + ("\n\n" + context if context else "")`。
|
||
|
||
创建 `app/prompts/company_facts.md`(§3 定稿内容)。
|
||
|
||
- [ ] **Step 4:** 测试通过;全量回归 `python -m pytest tests/ -q` 全绿(基线 160)
|
||
|
||
- [ ] **Step 5: 提交** `feat(kb): 公司事实卡入 prompt——Hesu 品牌/覆盖/网络零编造`
|
||
|
||
### Task 2: 事实卡墙安全静态测试(TDD)
|
||
|
||
- [ ] **Step 1: 失败测试**(追加到 `tests/test_company_facts.py`)
|
||
|
||
```python
|
||
from app.services.boundary_filter import (
|
||
no_contact_value, split_sentences, violates_boundary,
|
||
)
|
||
|
||
|
||
def _fact_sentences():
|
||
# 注: 事实卡是无句读的 markdown 列表,多行会合并成大句块——无害,
|
||
# 检测是 search 式,违规在任何块内都能命中(§3 草案独立评审实测 1289 chars
|
||
# 过两墙全清——长度数字随草案修订变化,以实施时实测为准)
|
||
complete, tail = split_sentences(COMPANY_FACTS)
|
||
return complete + ([tail] if tail else [])
|
||
|
||
|
||
def test_facts_pass_both_output_walls():
|
||
# AI 原样引用事实也必须安全: 每句过价格墙+零号码墙
|
||
# (陷阱实证: "五万"会被裸预算模式击杀、"累计出口五万台"→整句被替换成要号话术)
|
||
for s in _fact_sentences():
|
||
assert not violates_boundary(s), s[:60]
|
||
assert not no_contact_value(s), s[:60]
|
||
|
||
|
||
def test_facts_length_capped():
|
||
# 防营销手册化: 事实卡是卡片不是 brochure——超限=该精简或走检索方案
|
||
assert len(COMPANY_FACTS) < 2500
|
||
|
||
|
||
def test_facts_no_contact_channel():
|
||
# 我方联系方式绝不入卡(红线: web 指按钮/App 反向留资)
|
||
# 只查确定性标记: 邮箱@与 wa.me 链接("WhatsApp"品牌词是合法内容,不作标记)
|
||
assert "@" not in COMPANY_FACTS
|
||
assert "wa.me" not in COMPANY_FACTS.lower()
|
||
```
|
||
|
||
- [ ] **Step 2:** FAIL(文件未建/或内容违规)→ **Step 3:** 按 §3 定稿 → **Step 4:** 全绿
|
||
|
||
- [ ] **Step 5: 提交** `test(kb): 事实卡墙安全静态网——中文数字陷阱/联系方式/长度上限`
|
||
|
||
### Task 3: system.md 集成(人设定调 + 事实作答口径 + 编造面封堵)
|
||
|
||
- [ ] **改动 1——人设第一句**(业务定位用户已确认"新旧车都做")。⚠️ 该句是 system.md L1 的**前缀**(同行后续为"你说话像在 WhatsApp…"风格句)——做**子串替换**,不要整行替换删除风格句:
|
||
|
||
原(子串):
|
||
```
|
||
你是一位二手车外贸销售
|
||
```
|
||
改为:
|
||
```
|
||
你是一位汽车出口外贸销售(新车与二手车都做)
|
||
```
|
||
|
||
- [ ] **改动 2**——在【职能边界】"三类请求的处理"第 1 类(汽车信息)后追加一行:
|
||
|
||
```
|
||
- 公司情况(品牌/覆盖国家/出口经验/运作模式)按系统提供的事实卡简短作答建立信任——但不因此报价、承诺或跳过留资引导
|
||
```
|
||
|
||
- [ ] **改动 3——库存断言示例修正(三处,按文本锚点定位,勿信行号)**:示例在教学"现货断言",而 AI 无库存数据源(编造向量,见 §1 盘点)。保留各示例的原本教学意图(围绕客户说的车型/年份),只把现货断言改为"可采购调配"。⚠️ 行号会随文件改动漂移(方案撰写时实测在 L105/L111/L117,上轮修复后已漂移过一次)——实施时以 `grep -n "in stock\|tons of them" app/prompts/system.md` 定位,以下原文为准:
|
||
|
||
| 原文(逐字) | 改为 |
|
||
|---|---|
|
||
| "Corolla is a solid pick, we got tons of them. Any preference on year?" | "Corolla is a solid pick, we can source it easily. Any preference on year?" |
|
||
| "For 2025 models we got BYD Han, Toyota Camry, Honda Accord in stock. Budget range?" | "For 2025 models we can source BYD Han, Toyota Camry, Honda Accord. Budget range?" |
|
||
| "We got the 2024 Tang EV 7-seater and the DM-i AWD in stock. Want specs?" | "The 2024 Tang comes as the EV 7-seater and DM-i AWD — we can source either. Want specs?" |
|
||
|
||
⚠️ 差示例(L104 附近,"我们在非洲和东南亚市场有大量现车")是反面教材,**不要**顺手修改——它被标为"差"正是因为客服腔,保留其反例作用。
|
||
|
||
- [ ] **改动 4——红线区补三条**(价格墙对 warranty 形态白名单放行,保修承诺只能靠 prompt 封;Task 6 墙收紧为确定性兜底):
|
||
|
||
```
|
||
- 一切不清楚、没把握的问题(卡外国家/库存/保修/认证/任何事实细节)——不编造、不承诺、不否定、不断言,引导客户留联系方式,由客户经理沟通
|
||
- 不对库存/现车做断言——有没有现车、什么时候有,由客户经理确认;你只能说"可以采购调配"
|
||
- 不报任何保修/质保期限("X months warranty"这类数字承诺)——一律"客户经理会给你完整的保修方案"
|
||
```
|
||
|
||
- [ ] 回归 `python -m pytest tests/ -q` 全绿(prompt 改动无单测,靠既有安全网;注意 test_boundary_filter.py 的 `test_whitelist_clean` 用例含 "we got tons of them" 作为墙输入——那是测试自有字符串,不依赖 system.md,不受影响)
|
||
- [ ] **提交** `feat(prompt): 人设定调+事实作答口径+库存/保修编造面封堵`
|
||
|
||
### Task 4: 改动文档 + 冒烟清单
|
||
|
||
- [ ] 新建 `docs/2026-09-10-company-knowledge-base-changelog.md`(体例同前两份:背景/选型/文件清单/已知限制/部署回滚/冒烟结果)
|
||
- [ ] **真机 GLM 冒烟(部署前置项,单测覆盖不到 GLM 引用事实的方式)**:
|
||
|
||
| 场景 | 期望 |
|
||
|---|---|
|
||
| **"I'm in Ghana, do you have branch in Ghana?"(生产案例复现)** | **必须有分支**——"Yes, we've got a Ghana branch with bonded storage" + 顺势问需求;绝不再说 Not yet |
|
||
| "are you a real company?" | 1-2 句事实作答(首批出口企业/出口量/70+ 国),零编造零营销腔 |
|
||
| "do you have Zeeker? pickup trucks?" | 按卡答(品牌清单;皮卡重卡都有),不报库存承诺 |
|
||
| "do you ship to Kenya?"(卡内未列的国家) | **区分口径**:答"已出口到 70+ 国家,肯尼亚具体我让经理确认"——不承诺有网点、不说 Not yet |
|
||
| 中文问"你们公司在哪/怎么运作" | 中文简短作答,数字用出口量/70+ 形式(不能被价格墙拦) |
|
||
| "how much for Song L in Ghana?" | **公司事实不得软化价格红线**——仍迂回+要联系方式 |
|
||
| App 会话问公司情况 | 同样作答,零联系方式输出 |
|
||
| "are your cars new or used?" | 按确认口径作答:"新旧车都做"(事实卡+人设已定调) |
|
||
| "do you have Song L in stock right now?" | **不断言现货**——"可以采购调配"+顺势留资("留个 WhatsApp 经理告诉你现车情况")(改动 3 验证) |
|
||
| "is there warranty? how long?" | **不报期限数字**——"客户经理会给你完整保修方案"+留资引导(改动 4 + Task 6 墙级兜底:即使 GLM 硬说 "6 months warranty" 也会被价格墙替换) |
|
||
| "do you have office in Tokyo?"(卡外冷门事实) | **统一兜底**:不否定不肯定——"出口 70+ 国,东京这边我让经理确认,留个 WhatsApp" |
|
||
| 中文问"多久能到 Ghana?"/"质保多久?" | 不给时限/期限数字(中文单位形态 Task 6 已墙级兜底:"3天到货"/"6个月质保"会被替换) |
|
||
|
||
- [ ] **提交** `docs: 公司知识库——改动文档`
|
||
|
||
### Task 5: 再索要墙补裸碎片模式(Ghana 案例顺带发现,已实证)
|
||
|
||
**原理:** Ghana 案例的 AI 回复结尾 "Your number, please?" 经实证**穿透现有 REASK_PATTERNS**(只拦得住 "email or WhatsApp?" 组合形态;"Your number, please?"/"Your WhatsApp?" 裸碎片全漏)。已留资场景下这类碎片就是再索要。收紧版模式(动词后顾排除跟进确认句)。
|
||
|
||
⚠️ **既有测试同步更新(独立评审实证抓出,2026-09-10)**:新模式会命中 `test_asks_contact_clean_phrasings_pass` 里的 "Could you confirm your number?"(该断言编码 suspect 礼貌确认旧政策)。行为本身正确——`asks_contact` 仅在 confirmed(has_contact=True)时启用,此时再问"确认号码"就是冗余再索要;suspect 流(has_contact=False)闸门关着不受影响。按 Task 6 处理 test_warranty_clean 的同款方式:把该句从 clean 清单移入命中清单,注明政策依据与日期。**实施时必须对既有 asks_contact 全部测试句重跑核对**(首版"5 命中/5 放行"只验了自拟句集,漏了既有套件——教训)。
|
||
|
||
**Files:**
|
||
- Modify: `app/services/boundary_filter.py`(REASK_PATTERNS 追加一条)
|
||
- Test: `tests/test_boundary_filter.py`
|
||
|
||
- [ ] **Step 1: 失败测试 + 既有政策测试更新**(追加到 asks_contact 测试组;同步把 "Could you confirm your number?" 从 clean 测试移入命中测试,注释注明政策变更)
|
||
|
||
```python
|
||
def test_asks_contact_bare_fragment_asks():
|
||
# Ghana 生产案例: "Your number, please?" 曾穿透(5b 首版只覆盖动词/疑问词形态)
|
||
assert asks_contact("Your number, please?")
|
||
assert asks_contact("Your WhatsApp?")
|
||
assert asks_contact("So... your number?")
|
||
assert asks_contact("And your contact?")
|
||
# 政策变更(2026-09-10): confirmed 语境下"确认号码"也是冗余再索要
|
||
# (suspect 流闸门关着不受影响)——自 clean 清单移入
|
||
assert asks_contact("Could you confirm your number?")
|
||
|
||
|
||
def test_asks_contact_fragment_followup_still_pass():
|
||
# 动词后顾排除: 跟进确认(非索要)放行
|
||
assert not asks_contact("Did the manager reach your WhatsApp?")
|
||
assert not asks_contact("Got your number, thanks!")
|
||
assert not asks_contact("Our manager will send it to your WhatsApp shortly.")
|
||
```
|
||
|
||
- [ ] **Step 2:** FAIL → **Step 3: REASK_PATTERNS 追加**(模式已实证 5/5):
|
||
|
||
```python
|
||
# 裸碎片问句: "Your number, please?"/"Your WhatsApp?"(Ghana 案例穿透补)
|
||
# 后顾排除跟进确认句("reach/got/sent/did your X?" 是问进度不是要号)
|
||
re.compile(r"(?<!reach )(?<!reached )(?<!got )(?<!sent )(?<!did )"
|
||
r"your\s+(?:whatsapp|number|phone|e-?mail|contact)\b[^.!?]{0,20}[??]", re.I),
|
||
```
|
||
|
||
- [ ] **Step 4:** 全量回归 `python -m pytest tests/ -q` 全绿
|
||
- [ ] **Step 5: 提交** `fix(contact): 再索要墙补裸碎片形态——Your number, please?(Ghana案例)`
|
||
|
||
### Task 6: 时限/保修承诺墙级收紧(编造承诺从 prompt 层升为墙级确定性)
|
||
|
||
**原理(2026-09-10 实证):** 政策依据=用户定调"不清楚的不承诺":保修期限/交付时限是公司政策事实,AI 无真值,输出即编造。三处墙缺口(均已实证):
|
||
|
||
1. 价格墙时间模式的 warranty 豁免使 "we offer 6 months warranty" **确定性放行**(现墙 False)——直接去掉豁免即可(day/week/month 单位收紧后命中)。
|
||
2. ⚠️ **首版计划的测试 bug(实证抓出)**:`8 years warranty` 的 year 不在 `(day|week|month)` 内,去豁免后**仍放行**——且不能把 year 加进时间模式("under 5 years old" 二手车龄聊天会误杀)。修法:新增**定向模式** `\d+\s*years?\b[^.!?]{0,15}warranty`(英文年+质保)与 `\d+\s*年[^。!?]{0,8}(?:质保|保修)`(中文年+质保),"8 years warranty" ✓ 命中、"under 5 years old" ✓ 放行。
|
||
3. **中文时限/质保承诺今天全部零拦截**(时间模式只有英文单位):"3天到货"/"6个月质保"/"电池保修8个月" 现墙全 False。补 `\d+\s*(?:天|周|个月)`——实证 9/9 安全("2024年款"/"5座SUV"/"600km续航"/"2024 Tang"/"50,000+ 台"全放行;年 不收,防车型年款误杀;"一周"中文数字形态不收,观察)。
|
||
|
||
**Files:**
|
||
- Modify: `app/services/boundary_filter.py`(PRICE_PATTERNS)
|
||
- Test: `tests/test_boundary_filter.py`
|
||
|
||
- [ ] **Step 1: 失败测试 + 既有政策测试更新**
|
||
|
||
```python
|
||
def test_warranty_commitment_violates():
|
||
# 政策变更(2026-09-10 用户定调: 不清楚的留资转经理): AI 输出的保修期限
|
||
# 是编造的公司政策承诺——从"白名单放行"改为墙级违规
|
||
assert violates_boundary("we offer 6 months warranty on every car")
|
||
assert violates_boundary("battery has 8 years warranty") # year 不在时间模式内,靠定向模式
|
||
assert violates_boundary("warranty of 8 years on battery") # EN 倒装
|
||
assert violates_boundary("电池保修8个月")
|
||
assert violates_boundary("8年质保")
|
||
assert violates_boundary("整车质保3年10万公里") # CN 倒装(质保在前)
|
||
assert violates_boundary("质保3年或10万公里")
|
||
|
||
|
||
def test_chinese_time_commitment_violates():
|
||
# 既有缺口: 中文时限承诺此前零拦截(时间模式只有英文单位)
|
||
assert violates_boundary("3天到货")
|
||
assert violates_boundary("6个月质保")
|
||
|
||
|
||
def test_age_and_year_talk_still_clean():
|
||
# 误杀防护: 二手车龄/车型年款/无数字保修讨论必须放行(实证 13 条全清)
|
||
assert not violates_boundary("under 5 years old")
|
||
assert not violates_boundary("5 years old car")
|
||
assert not violates_boundary("2024年款")
|
||
assert not violates_boundary("5座SUV")
|
||
assert not violates_boundary("600km续航")
|
||
assert not violates_boundary("does it come with warranty?")
|
||
assert not violates_boundary("warranty details from our manager")
|
||
assert not violates_boundary("how many years warranty?") # 无承诺数字的反问
|
||
```
|
||
|
||
同步更新既有 `test_warranty_clean`(其断言 "6 months warranty on the engine" 干净编码的是**旧政策**,改为断言违规;保留无数字形态干净的断言),测试内注释注明政策变更依据与日期。
|
||
|
||
- [ ] **Step 2:** 新测试 FAIL(现墙放行)→ **Step 3: 实现**——PRICE_PATTERNS 时间模式替换 + 新增(模式集已实证 10 命中/13 放行):
|
||
|
||
```python
|
||
# 时间承诺: 去掉 warranty 豁免(政策变更 2026-09-10——保修期限是编造的
|
||
# 公司政策承诺,不再白名单)
|
||
re.compile(r"\d+\s*[- ]?(day|week|month)s?\b", re.I),
|
||
# 年限保修定向(双向): year 不能进时间模式(车龄 "under 5 years old" 会误杀),
|
||
# 只收 数字+年 与 warranty/质保/保修 的组合(含倒装"质保3年"/"warranty of 8 years")
|
||
re.compile(r"\d+\s*years?\b[^.!?]{0,15}warranty", re.I),
|
||
re.compile(r"warranty\s*(?:of|for|:)?\s*\d+\s*years?\b", re.I),
|
||
re.compile(r"\d+\s*年[^。!?]{0,8}(?:质保|保修)"),
|
||
re.compile(r"(?:质保|保修)[^。!?]{0,8}\d+\s*年"),
|
||
# 中文时限承诺: 天/周/个月(年不收——"2024年款" 是车型年款)
|
||
re.compile(r"\d+\s*(?:天|周|个月)"),
|
||
```
|
||
|
||
- [ ] **Step 4:** 全量回归 `python -m pytest tests/ -q` 全绿。前置安全检查: `grep -rn "个月\|[0-9]天\|months\|years" app/ tests/` 全目录核对模板/既有测试文案无 "N 天/N个月/N months/N years" 形态(独立评审已预核当前零命中,但检查范围必须全 tests/——防测试文案藏形态)
|
||
- [ ] **Step 5: 提交** `fix(contact): 时限/保修承诺墙级封堵——去warranty豁免+中文单位+年限定向(编造面盘点#2)`
|
||
|
||
## 6. 残余风险
|
||
|
||
1. **GLM 把事实用成长篇**——使用规则+风格约束双保险,冒烟场景 1 验证;失守再考虑事实卡减条
|
||
2. **事实过时无察觉**——KB 是静态文件,出口量/分支变化需人工更新;changelog 记"事实来源=2026-09 官网资料+用户口径确认"
|
||
3. **事实软化红线的间接路径**——如客户问"你们出口 70 国那运费多少",GLM 可能借势聊运费;冒烟专项验证
|
||
4. **车型公共参数编错(接受残余)**——GLM 内部知识答续航/电池/配置,新款(2026 款)可能过期失真;零防线,方向性通常正确,冒烟观察
|
||
5. **关税/税率百分比(观察项)**——"25% duty" 形态价格墙不拦,靠 tariff/关税触发词的提醒层兜底;冒烟观察,失守再议墙收紧
|
||
6. **库存断言为 prompt 层封堵(非墙级)**——Task 3 改动 3/4 是软约束;若冒烟仍出现 "in stock" 断言,升级方案:库存断言句式进再索要墙同款输出过滤。**保修/时限承诺已由 Task 6 升为墙级确定性封堵**(双向年限定向+中文单位,2026-09-10 实证 10 命中/13 放行)
|
||
7. token 常驻成本实测草案 1289 chars ≈ 700-900 GLM token/轮——glm-4-flash 可忽略;若未来换贵模型,再评估方案 C
|
||
8. **中文数字时限形态不收**("一周到货"/"三天发货")——中文数字+单位组合形态长尾大、误杀面难控,观察期;若冒烟出现,回流补模式(与"漏词回流补词"同机制)
|
||
9. **英文非保修年限表述不拦**("it lasted 8 years"类)——year 进时间模式会误杀二手车龄聊天,定向模式只收 warranty 邻接;边缘接受
|
||
10. **Task 5 误杀类(知情)**:confirmed 语境下引用/核对类问句("Has your number changed?"/"Could you confirm your email?")会被当再索要丢弃——丢句不掐断,损失一句可接受;suspect 流不受影响
|
||
11. **Task 6 时态不区分**:"N天前"过去式引用(中文)现在也会被墙替换——与英文 "3 days ago" 既有行为一致(此前中文恰好豁免),一致性修复而非回归
|
||
|
||
## 7. 待确认问题(执行前)
|
||
|
||
| # | 问题 | 影响 | 结论(全部确认于 2026-09-10) |
|
||
|---|---|---|---|
|
||
| 1 | ~~主业务:新车出口 / 二手车 / 两者?~~ | 人设第一句 + 事实卡一行 | ✅ **新旧车都做** |
|
||
| 2 | ~~累计出口量:About 写 50,000+,Key Facts 写 10,000+,矛盾~~ | 事实卡一行 | ✅ **50,000+** |
|
||
| 3 | ~~Venezuela 要不要列入覆盖?~~ | 事实卡一行 | ✅ **列入** |
|
||
| 4 | ~~"Leopard、ROX"两个品牌名是否准确?~~ | 品牌清单 | ✅ **保留原文**(Leopard、ROX;Zeeker 亦保留原文拼写,用户确认) |
|
||
|
||
确认方式:直接回复"1 新车出口 2 五万 3 不列 4 剔除"这类即可,或改任意项。
|
||
|
||
## 8. 部署与回滚
|
||
|
||
重启生效(prompt 启动加载);无迁移/配置/前端;六个任务各自成 commit,逐个可 revert。
|