Files
group_fqcd_jr/docs/customer-service-routing-legacy-keywords.md
T

206 lines
14 KiB
Markdown
Raw Normal View History

# 一期客服确定性路由:关键词表留痕与迁移对照
> **日期**:2026-09-16
> **触发**:客服模块重构前代码清理(依据《客服与投顾模块重构前代码清理建议-2026-09-15.md》§二 A2/A3/B1/B2)
> **性质**:**留痕文档**。一期路由已删除,但其词表是这些拦截能力的**唯一存放处**,
> 故原样抄录于此,并给出逐条迁移去向。**不要只依赖 git 历史追溯**——本轮出现过
> "死代码里的能力被当成已实现"的误判,词表必须留在可检索的文档里。
>
> ## ⚠️ 2026-09-16 后续更新(形态A 模块级清除,必读)
>
> 上文 §一 写的「保留未删」**已被后续的客服模块整体删除推翻**:
>
> - `app/service/agent/customer_service_routing.py`(含 `chitchat_streak()` 与 `_CHITCHAT_*`)
> —— **整个文件已删除**,`chitchat_streak()` 不复存在。
> - 先期把 8 条提示词注入词迁移进去的 `app/core/customer_service_rules.py`
> —— **也随模块一并删除**。
>
> **后果(重建时必须补齐)**:本文件 §二 的词表现在是这些拦截能力**在本仓库中的唯一副本**。
> 以下能力当前**在生效路径上不存在**:
> 1. 提示词注入拦截(8 条词)—— 红队 RT-009/010 仍会失败;
> 2. 凭据披露拦截(`_SECURITY_DISCLOSURE_PHRASES`);
> 3. 安全关键词路由(`_SECURITY_KEYWORDS`)与零容忍词(`ZERO_TOLERANCE_WORDS`)。
>
> `app/service/agent_run_application_service.py` 的 `build_outbox_metadata()` 也不再覆写
> `chitchat_streak` / `clarification_round` / `session_context`(`AgentRequest` 上的字段仍在,
> 但已无写入方)。**重建客服 Agent 时按 §三 的对照表逐条恢复,并保留本文件作为验收基线。**
---
> ## ✅ 2026-09-18 更新(`C-06` 双向验证)
>
> 上面「提示词注入拦截在生效路径上不存在、红队 `RT-009`/`010` 仍会失败」的警告**已过期**:
> `app/core/customer_service_rules.py` 的 `PROMPT_INJECTION_KEYWORDS` 8 条已在 **P0 之后、
> 合规拦截之前**生效,红队 `RT-009`/`RT-010` 在 `C-06` 双向验证中**由失败转为通过**
> (证据 `docs/evidence/20260918-t2i-c06-bidirectional.json`)。
> §二 的词表仍是**迁移对照基线**,不要据此认为当前实现仍有缺口。
---
## 一、删除的是什么
| 项 | 位置(删除前) | 判定 |
|---|---|---|
| `CustomerServiceIntentRouter.classify()` | `app/service/agent/customer_service_routing.py` L40-65 | **全仓无调用方**。全仓 `.classify(` 仅命中 `app/service/agent/base.py:205` 的 `IntentClassifier.classify`(另一个类,无关) |
| `CustomerServiceRoute` | 同文件 L7-12 | 只在 `classify()` 内部被构造 10 次,无外部消费方 |
| 9 组关键词常量 | 同文件 L16-37 | 仅被 `classify()` 使用(`_CHITCHAT_*` 除外,见下) |
**关键事实(决定了本次删除的风险等级)**:`classify()` 无生产调用方,因此
**删除本身不产生任何行为变更**。它的真实代价不是"回归",而是"这些词表原本只存在于
一个不生效的位置"——例如提示词注入拦截的 8 条词**从未在任何生效路径上运行过**
(红队 RT-009/010 因此必然失败)。所以本次处置是**先迁移、后删除**,而不是只删除。
**保留未删**:`_CHITCHAT_MESSAGES`、`_CHITCHAT_PHRASES`(`chitchat_streak()` 依赖,
而 `chitchat_streak()` 被 `app/service/agent_run_application_service.py:57` 用于
组装 outbox 元数据的 `chitchat_streak` 字段)。二期路由 `app/core/customer_service_rules.route_message()`
(`customer_service.py:215` 调用)与 `IntentClassifier` 均已接管其余能力。
---
## 二、一期词表原样留痕(删除前的完整内容)
```python
_SECURITY_KEYWORDS = ("验证码", "密码泄露", "被盗", "诈骗", "非本人交易")
# 明确的凭据披露和提示词注入必须在知识检索前拦截,避免把攻击内容当作普通 FAQ。
_SECURITY_DISCLOSURE_PHRASES = ("密码是", "密码为", "我的密码", "验证码是", "验证码为")
_PROMPT_INJECTION_KEYWORDS = (
"忽略之前", "忽略所有规则", "系统提示词", "开发者消息", "泄露提示词", "越过限制",
"不要遵守规则", "显示内部指令",
)
_COMPLIANCE_KEYWORDS = ("推荐", "收益最高", "稳赚", "保本", "帮我买", "替我交易")
_ACCOUNT_KEYWORDS = ("持仓", "收益", "订单", "定投", "银行卡", "风险测评", "投诉进度")
_HUMAN_TRANSFER_KEYWORDS = ("转人工", "人工客服", "投诉", "赔偿", "法律", "纠纷")
_POLICY_KEYWORDS = (
"申购", "赎回", "到账", "费率", "手续费", "确认份额", "交易日", "分红", "规则", "政策"
)
_PRODUCT_KEYWORDS = (
"产品", "基金代码", "基金经理", "份额类别", "a类", "c类", "净值", "风险等级"
)
_REFERENCE_PHRASES = ("这个", "那个", "它的", "刚才", "上面", "前面", "这只", "那只")
# ↓ 以下两组已保留在原文件(chitchat_streak 在用),此处仅作对照
_CHITCHAT_MESSAGES = frozenset({
"你好", "您好", "嗨", "哈喽", "在吗", "谢谢", "谢谢你", "再见", "拜拜",
"你是谁", "你叫什么", "你今天开心吗",
})
_CHITCHAT_PHRASES = ("今天天气", "讲个笑话", "你几岁", "你开心吗", "你忙吗")
```
### 原 `classify()` 的判定顺序(供追溯语义用)
```
security(_SECURITY_KEYWORDS ∪ _SECURITY_DISCLOSURE_PHRASES) → intent=security_notice
→ _PROMPT_INJECTION_KEYWORDS → intent=compliance_refusal
→ _COMPLIANCE_KEYWORDS → intent=compliance_refusal
→ _ACCOUNT_KEYWORDS → intent=account_entry
→ _HUMAN_TRANSFER_KEYWORDS → intent=human_transfer
→ 闲聊 → intent=chitchat
→ _POLICY_KEYWORDS → knowledge_intents=(policy_explain,)
→ _PRODUCT_KEYWORDS → knowledge_intents=(product_inquiry,)
→ 兜底 → knowledge_intents=(faq,)
```
---
## 三、迁移对照表(逐条)
### 3.1 已迁入二期(本次完成)
| 一期条目 | 原属词表 | 二期落点 | 说明 |
|---|---|---|---|
| `非本人交易` | `_SECURITY_KEYWORDS` | `P0_KEYWORDS` | 二期 P0 原先只有「不是我本人操作」,该说法会漏到知识检索 |
| `替我交易` | `_COMPLIANCE_KEYWORDS` | `P2_KEYWORDS` | 不被 `P2_PATTERNS` 覆盖(句式白名单无「交易」二字),须用字面词补 |
| `纠纷` | `_HUMAN_TRANSFER_KEYWORDS` | `P2_KEYWORDS` | 投诉赔偿类的常见同义说法 |
| 8 条注入词(全部) | `_PROMPT_INJECTION_KEYWORDS` | **新增** `PROMPT_INJECTION_KEYWORDS` + `PROMPT_INJECTION_PRIORITY` + `PROMPT_INJECTION_REPLY` | 落地于 `route_message()`,位置在 **P0 之后、合规拦截之前**(混合输入先拿反诈话术) |
### 3.2 已被二期覆盖,无需迁移
| 一期条目 | 二期覆盖方式 |
|---|---|
| `验证码`、`密码泄露`、`被盗`、`诈骗`、`转账给`、`安全账户` 等 | 已在 `P0_KEYWORDS` |
| `密码是` / `密码为` / `我的密码` / `验证码是` / `验证码为` | 全部含「密码」或「验证码」子串,被 `P0_KEYWORDS` 覆盖 |
| `稳赚`、`保本` | 已在 `ZERO_TOLERANCE_WORDS`(输入侧) |
| `帮我买` | 已在 `P2_KEYWORDS` |
| `转人工`、`人工客服`、`投诉`、`赔偿`、`法律` | 已在 `P2_KEYWORDS` |
| `投诉进度` | 已在 `P1_KEYWORDS` |
| `风险测评` | `P1_KEYWORDS` 有「风险测评结果」(限定式) |
### 3.3 有意不迁(含理由)
| 一期条目 | 处置 | 理由 |
|---|---|---|
| `推荐` | **不迁**,交 Todolist **C-09**(访客侧推介边界) | C-09 的 DoD 明确把「推介请求句式」的**输入侧**识别限定在**访客**,且要求**客户侧行为不变**。现在若把它加进全局输入拦截,会改变客户侧行为,与 C-09 ③ 的回归要求正面冲突 |
| `收益最高` | **不迁**,交 Todolist **C-03 / C-05** | ① 它属「收益承诺 / 比较类」而非安全类;② 加入 `ZERO_TOLERANCE_WORDS` 会破坏 `tests/unit/core/test_customer_service_rules.py:87-92` 的 DB 镜像断言(那 11 条与库内 `agent_negative_word` 被断死相等);③ MVP 已定**输出侧**只拦收益比较与稀缺性,C-05(数据层)是设计指定的落点 |
| `持仓` / `收益` / `订单` / `定投` / `银行卡` 等**裸词**账户问法 | **不迁**,交 Todolist **C-03 ③** | 二期 P1 刻意用「我的持仓」这类**限定式**,避免把公开问题(「什么是持仓」)误判成本人账户查询。补裸词需配套设计,属 C-03 范围 |
| `_POLICY_KEYWORDS`、`_PRODUCT_KEYWORDS` | **不迁** | 其作用是产出 `knowledge_intents=(policy_explain/product_inquiry)`。二期意图由底座 `IntentClassifier` 产出,`INTENT_POLICY`/`INTENT_PRODUCT` 已在 `customer_service.py:44-45` 独立定义,不再需要关键词路由 |
| `_REFERENCE_PHRASES` | **不迁** | 原用于标记 `requires_context`;二期无此机制(会话上下文由 `customer_service_session_memory_service` 承担) |
---
## 四、本次改动清单
| 文件 | 动作 |
|---|---|
| `app/service/agent/customer_service_routing.py` | 删除 `classify()`、`CustomerServiceRoute`、9 组词表;保留 `chitchat_streak()` 与 `_CHITCHAT_*`(87 行 → 约 45 行) |
| `app/service/agent/customer_service_agent.py` | **整文件删除**(5 行向后兼容桩,生产零引用) |
| `app/core/customer_service_rules.py` | 新增 `PROMPT_INJECTION_KEYWORDS` / `PROMPT_INJECTION_PRIORITY` / `PROMPT_INJECTION_REPLY` + 注入拦截分支;`P0_KEYWORDS` 补 `非本人交易`;`P2_KEYWORDS` 补 `替我交易`、`纠纷` |
| `tests/unit/service/test_agent_governance.py` | import 从 `customer_service_agent` 改为 `implementations.customer_service` |
| `tests/unit/core/test_customer_service_rules.py` | 新话术纳入 `REPLY_CONSTANTS`(过"自绊检查"+ 带官方联系方式两条守卫);新增注入拦截 / 混合优先级 / 迁移词回归共 4 组用例 |
| 本文件 | 新增(留痕) |
---
## 五、验证方式与本次限制
设计上应执行 `docs/14` §13 的统一验收命令集。**本次的实际限制**:
- 本机 Managed Python 3.13.14 **未安装项目依赖**(pytest / fastapi / sqlalchemy / pydantic / redis / pymilvus 等全部 `ModuleNotFoundError`),仓库内**无 venv** → `pytest`、`ruff`、`mypy` **无法运行**。
- 因此本次实际执行的验证为:① 全量 `py_compile` 语法校验;② 全仓静态引用扫描(确认无悬空 import)。
- **遗留动作**:补齐依赖后必须补跑 `pytest tests/unit/core/test_customer_service_rules.py`、
`pytest tests/unit/service/test_agent_governance.py` 与全量 `ruff check`。
---
## 六、2026-09-18 重建记录(词表最终落点)
客服 Agent 本体已于 2026-09-18 重建(`T2`)。**本文件 §二 的词表已按 §三 对照表逐条恢复**,最终落点如下:
| 词表 | 重建后的落点 |
|---|---|
| `_SECURITY_KEYWORDS` | `app/core/customer_service_rules.py` 的 `P0_KEYWORDS` |
| `_SECURITY_DISCLOSURE_PHRASES` | 同文件的 `SECURITY_DISCLOSURE_PHRASES` |
| `_PROMPT_INJECTION_KEYWORDS`(8 条) | 同文件的 `PROMPT_INJECTION_KEYWORDS` + `PROMPT_INJECTION_PRIORITY` + `PROMPT_INJECTION_REPLY`,位置在 **P0 之后、合规拦截之前** |
| `_COMPLIANCE_KEYWORDS`(`稳赚` / `保本` 部分) | 同文件的 `ZERO_TOLERANCE_WORDS` |
| `_HUMAN_TRANSFER_KEYWORDS` | 同文件的 `P2_EXPLICIT_HUMAN_KEYWORDS` / `P2_WRITE_DISPUTE_KEYWORDS` |
| `_CHITCHAT_MESSAGES` / `_CHITCHAT_PHRASES` | 同文件的 `CHITCHAT_MESSAGES` / `CHITCHAT_PHRASES`(原在 `customer_service_routing.py`,该文件已删,故**收口进规则模块**);消费方是 `agent_run_application_service.build_outbox_metadata` 的 `chitchat_streak` 覆写 |
| `_POLICY_KEYWORDS` / `_PRODUCT_KEYWORDS` / `_ACCOUNT_KEYWORDS` / `_REFERENCE_PHRASES` | 按 §3.3「有意不迁」维持不迁 |
⚠️ **本文件 §二 与 §三 的对照关系不再需要**(词表已落到生效路径);保留它们是为了记录「能力曾经只存在于一个不生效的位置」这段事实。
---
## 七、2026-09-19 `C-07` 收口:残留取证、留痕完整性与门禁补跑
> `C-07`(删除一期确定性路由 + 兼容桩)的删除动作已由 **2026-09-16「形态A 模块级清除」代删**;本步做的是**取证与收口**(前置 `S-3` 要求的 `C-06` 已于 2026-09-18 通过)。
| 收口项 | 实测 |
|---|---|
| 文件残留 | `app/service/agent/customer_service_routing.py`、`app/service/agent/customer_service_agent.py`(5 行兼容桩)**均不存在** |
| 符号残留 | `CustomerServiceIntentRouter` **0 命中**、`CustomerServiceRoute` **0 命中**;`.classify(` 6 命中**全部**是底座 `IntentClassifier`(`base.py:205` 与两处测试) |
| 词表残留 | `_PROMPT_INJECTION_KEYWORDS` / `_SECURITY_DISCLOSURE_PHRASES` 的命中**全部是注释里的留痕引用**,无任何定义 |
| import 指向 | `bootstrap.py:26` 与 `test_customer_service_agent.py:16` → `app.service.agent.implementations.customer_service`;`chitchat_streak` 的消费方 `agent_run_application_service.py:14` → `app.core.customer_service_rules` |
| **留痕完整性(程序化)** | 用 `ast` 取出两版历史文件(`e9b3d27` 2026-09-10 / `ef098e6` 2026-09-11)的全部常量,逐字面比对本文档 §二:**缺失 = 0**(原版 7 组;后版 10 组,含新增注入 8 条、凭据披露 5 条、指代 8 条) |
| **补正一处** | §二「原 `classify()` 的判定顺序」块用了 5 个 `intent` 名,缺第 6 个 **`public_knowledge`**(一期 `CustomerServiceRoute` 的公开知识直答取值;此名另见于 `D4.6` 基线文档的「预期路由」列)。对应关系:`public_knowledge` ≡ 二期的 `knowledge_intents=(policy_explain / product_inquiry / faq)`,由底座 `IntentClassifier` 产出 |
| 门禁(补跑 §五 遗留) | pytest **2 failed / 1577 passed / 2 skipped**(失败集 = `T0` 基线同两项,与本步无关)、ruff **22**、mypy **3**、`compileall app` 通过 |
- 证据:`docs/evidence/20260919-t2j-c07-legacy-routing-removal.json`