一、客服 Agent 智能增强(正面回应"不智能、动不动就转人工")
- 决策链由 2 个出口扩到 5 个:E1 澄清 / E2 计算型 / E3 知识直返 / E4 证据约束生成 / E5 分级回退
- 转人工从"默认动作"降为最后一档 E5c,只保留 4 类白名单:
P0 反诈 / P1 账户与个人数据 / P2 写操作与争议 / 用户明确要求人工
- 46 条金标实测(修复前 → 修复后):
转人工率 43.5% → 10.9%;出口准确率 45.7% → 100%;事实正确率 69.6% → 100%
禁忌违反 1 → 0;档位越权 / 无出处数字 / 误拒 四项零容忍全 0
- 安全不变量 INV-1~INV-5;零容忍规则未删,改的是挂载点
(输出侧字面黑名单 → 检索层档位隔离 + 判定层合规词表 + 输出守护)
二、知识库:档位单点化与物理隔离
- 新增 app/core/knowledge_tier.py 作为档位规则唯一落点(G-03),
knowledge_contracts.py 原定义块改为显式再导出(X as X,非副本)
- 档位过滤由 bool 默认值(fail-open)改为 tiers 必填集合(缺参即 TypeError)
- Milvus 侧四集合按 visibility 分区键物理隔离;双 schema 收敛为一套
- 新增 app/core/actor.py:访客三元组与匿名判定的唯一构造/判定点(G-01/G-01b)
- 新增 app/core/fund_fee_rules.py:费率计算纯函数
三、前端入参边界对齐(本轮 W11 新修,4 处"校验宽于存储")
- message 加 max_length=8000(与浮窗 widget.js 的 maxlength 一致)
- session_id 加 1—64;idempotency_key 上限 128 → 64(对齐列宽 String(64))
- feedback_type 加 max_length=32(对齐列宽 String(32))
- 8 条路径参数补 min_length=1 + max_length=64 + 字符集正则
({session_id} / {run_id} / {handover_id})
- 改前超限值会落到 MySQL 才失败(500);改后一律 422 AGENT_INPUT_INVALID + 字段级定位
- 新增 tests/unit/api/test_frontend_boundaries.py(33 例),含"端点表 ↔ OpenAPI 全量对照"
四、投顾模块整体清除(D4.4 / D4.5)
- 删除投顾相关 controller / schema / model / repository / service 及门户页面
- tools/portal_api_check.py 同步作废 AD003/AD005/AD011/A047 四条用例与 advisor_t 登录
(端点与账号均已不存在,此前稳定报 3 条假红)
五、验证(提交前实测)
- pytest -q:1856 passed / 2 skipped / 0 failed
- ruff check app tools tests:19(= 基线);mypy app:2(= 基线)
- 前端接口契约体检 portal_api_check.py:38 项,通过 34,失败 0,跳过 4
- 全链路冒烟 e2e_smoke_test.py --read-only:31/31
- HTTP 全链路探针 http_probe.py:11/11 succeeded
- 跨文档一致性 _consistency.py:GATE PASS
- 真机边界复验 12 条:12/12 符合预期
六、纪律与文档
- 可改文件白名单 A-09(docs/46)与底座会签申请单 A-10(docs/47,组 1—组 4 全部受理)
- 零 DDL:未新增/修改任何表结构,89 张业务表与基线一致
- 证据留痕:docs/evidence/**(含 46 条金标 score、快照、清除与重建记录)
- 未提交(刻意排除,见提交说明):仓库内 客服agent/ 与 开发文档/ 是 2026-09-16 前的
过期副本(Todolist 440 行 vs 权威 D2.1 1167 行),权威正本在仓库外;
_chunks_report.txt 是 tools/build_knowledge_chunks.py 生成的本地产物
14 KiB
一期客服确定性路由:关键词表留痕与迁移对照
日期: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—— 也随模块一并删除。后果(重建时必须补齐):本文件 §二 的词表现在是这些拦截能力在本仓库中的唯一副本。 以下能力当前在生效路径上不存在:
- 提示词注入拦截(8 条词)—— 红队 RT-009/010 仍会失败;
- 凭据披露拦截(
_SECURITY_DISCLOSURE_PHRASES);- 安全关键词路由(
_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_KEYWORDS8 条已在 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 均已接管其余能力。
二、一期词表原样留痕(删除前的完整内容)
_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