feat(W21-D1~D4): 四项待决按建议全部落地 + 1 条金标期望登记修订

按甲方 2026-09-21「按照你建议的改」批准 `D4.8` §6 四项,全部落地:

- W21-D1 生成侧禁令:`DEFAULT_EVIDENCE_SYSTEM` 红线 ② 与
  `DEFAULT_EVIDENCE_TEMPLATE` 第 ④ 条同时写明「不要把收益/回报/涨幅/净值增长率的
  具体数字或百分比写进答复」。红线门槛代码(`ZERO_TOLERANCE_WORDS` /
  `hits_zero_tolerance` / `YIELD_METRIC_TERMS` / `drop_yield_claims`)零改动;
  实测该 `prompt_code` 从未发布,改代码默认值即刻生效、无需发布流程。
  3 条问句 × 3 次 = 9/9 走 `E4` 真实作答(修复前 0/9)。
- W21-D2 指代依据:`_answer_suitability` 在主语由形状反解(`inferred=True`)时
  开场即「您上一轮提到的是「X」,我就按它帮您核对:」。
- W21-D3 答非所问闸门:新增产品名两种写法识别 + 前缀噪声剥离
  (`_strip_product_name_lead`) + `_names_other_product`,接入 `E5b` 相关性闸门。
- W21-D4 作答语气:`PARTIAL_TEMPLATE` 改「关于这一点,公开资料里的口径是:」;
  新增 `PARTIAL_FAQ_TEMPLATE`,命中块是 FAQ 问答对时直接以答案正文开场。

判据变更(知情项):`I-01`「南方科技是什么公司?」的金标期望出口由 [E5b, E3]
补入 `E4` —— 这正是提示词红线 ②(专为「名称查不到」设计)首次真正生效,
考点两条(禁忌字面不出现 / 关键事实命中)不变,见 `D4.8` §9.3。

新发现(未擅自执行,登记待决):`docs/43-场内基金产品手册(知识库入库版).md`
从未进入 `tools/build_knowledge_chunks.py` 的 `SOURCES`(702 块里「科创债」0 处),
这是「问在库的产品却答另一只」的根本原因;`W21-D3` 只是止损。

验收:金标 M-1 46/46、M-4 46/46、M-6 5/46、M-7/M-8/M-9/M-10=0、M-2 28/31、
M-2b 15/18、M-3 4/4;全量 1994 passed / 3 skipped;ruff 零新增;真 HTTP 11 条对照见 `D4.8` §9.4。

文档:`D4.8` v1.0→v1.1(新增 §9)、`D3.7` v1.0→v1.1(I-01 注)、
`D1.6` v1.0→v1.1(新增 §10 本轮对话上下文)、`D2.1` v6.37→v6.38、
`D2.9` v1.1→v1.2、`D1.1` v1.13→v1.14。
This commit is contained in:
张胜宇
2026-09-21 11:23:31 +08:00
parent dd5e9f8435
commit 36d9ba9b9f
8 changed files with 511 additions and 37 deletions
@@ -1,7 +1,7 @@
# D4.8 · 客服 Agent 智能度体检与整改报告(`W21`)
> **体系编号**:`D4.8` · 域:四、重构与清除留痕
> **编号**:CS-RPT-2026-024 | **版本**:v1.0 | **日期**:2026-09-21 | **状态**:现行
> **编号**:CS-RPT-2026-024 | **版本**:v1.1 | **日期**:2026-09-21 | **状态**:现行
> **性质**:**留痕报告**。回答甲方一句质疑 —— 「客服 Agent 不智能,很多问题强制转人工」。
> 本文只做三件事:**取实测证据**、**定位根因**、**登记整改与未整改项**。不改需求、不立新任务。
@@ -16,6 +16,9 @@
- **智能度体检**:退化形态由 `W21` 首轮的 **25/78** 降到 **16/81**(口径说明见 §2.3,两次分布**不可直接相减**)。
- **`_chunks_report.txt` 判定为可删**(依据见 §1)。
**第二轮(同日)**:§6 四项待决经甲方「**按照你建议的改**」全部裁定并落地 —— `W21-D1` 生成侧禁令、`W21-D2` 指代依据、`W21-D3` 答非所问闸门、`W21-D4` 作答语气。落地后**三项实测退化**(三条「检索命中完全正确却拿兜底话术」的问句)由 **0/9 走 `E4`** 变为 **9/9 走 `E4` 真实作答**;金标与全量回归见 §9。
**同时暴露一个比 `D1`~`D4` 更根本的缺口**:`docs/43-场内基金产品手册(知识库入库版).md`(20 只场内基金)**从未进入 `SOURCES`**,见 §9.5 第 3 条。
---
## 1. 待判物:`_chunks_report.txt` 能否删除 —— **判定:可删,已删并已加 `.gitignore`**
@@ -64,7 +67,7 @@ _chunks_report.txt
| 标 | 含义 | 是否退化 |
|---|---|---|
| `作答` | 有实质回答(直答 / 生成 / 计算 / 适当性 / 闲聊) | 否 |
| `E5b部分答` | 「我先帮您把找到的公开资料放上来…」——贴了资料,未必贴对 | 是 |
| `E5b部分答` | 「关于这一点,公开资料里的口径是:…」(`W21-D4` 前为「我先帮您把找到的公开资料放上来…」)——贴了资料,未必贴对 | 是 |
| `E5b空答` | 「我暂时没找到对应的公开资料」 | 是(但诚实) |
| `澄清` | 「您想了解的是下面哪一项呢?」 | 是 |
| `转人工` | 建单或走人工话术 | 是 |
@@ -193,6 +196,8 @@ Q: 我要投诉,让你们经理来找我 ← 能力不减
**现象**(实测,三条同因):`南方现金添利怎么样`(top1 分数 **1.0000**)/ `买基金要手续费吗`(**0.7328**)/ `债基和货基哪个收益高`(**0.5457**)—— 检索命中**完全正确**,客户拿到的却是「我先帮您把找到的公开资料放上来…」这种兜底话术。
> ✅ **`W21-D1` 已修复(2026-09-21)**:在 `E4` 的 system 红线 ② 与 user 模板第 ④ 条双处写明「不要把收益 / 回报 / 涨幅 / 净值增长率的**具体数字或百分比**写进答复」。修复后三条问句**各跑 3 次、9/9 全部走 `E4` 真实作答**,答复内零收益数值;门槛代码(`ZERO_TOLERANCE_WORDS` / `hits_zero_tolerance` / `YIELD_METRIC_TERMS` / `drop_yield_claims`)**一条未动** —— 这是**源头消除**,不是放宽判定。详见 §6 第 1 项与 §9。
**根因链**(逐环取证):
```
@@ -249,7 +254,7 @@ E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS
---
## 6. 需要甲方决策的事项(附我的建议)
## 6. 需甲方决策的事项(附建议)—— 🔵 **四项已于 2026-09-21 全部裁定并落地**
### 第 1 项 · 零容忍词的**输出侧**口径(最高优先)
@@ -265,13 +270,15 @@ E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS
**我建议 A 的理由**:问题的成因在"模型不该写",不在"闸门不该拦"。改提示词是**在源头消除**,红线代码与单测一条都不用动;而且是**可回归**的(复跑那三条问句即可验证)。
**需要你给的一句话**:是否批准修改 `E4` 证据约束生成的提示词(禁止输出收益数值)?提示词走发布配置,需要重新发布一次。
**裁定(2026-09-21)**:**批准**,按方案 A 落地 —— 见 §9。
> 补一条**实测更正**:本项原写「提示词走发布配置,需要重新发布一次」,**实际不需要** —— `prompt_template_version` 表内只有 3 行 `customer_service_chitchat`(release 57/58/220),**没有** `customer_service_evidence_answer` 的发布行,`_evidence_prompt()` 读不到就回落代码内置默认值,因此改代码默认值**即刻生效**、无需发布流程。
### 第 2 项 · 「它适合我吗」类问题的**默认指代对象**
**背景**:修复后,`我想买个债基 → 它适合我吗` 会**自动**把上一轮提到的产品(南方稳健增利债券 A)当作指代对象并给出裁决。若上一轮提过**多个**产品(如产品清单),目前取**第一个**。
**需要你给的**:产品清单场景下,取"第一个"是否可以接受?还是要改成"反问客户选哪一只"?
**裁定(2026-09-21)**:**取第一只 + 明示指代依据**,见 §9。
**我的建议**:**取第一个可以接受,但加一句"我按上一轮提到的第一只(X)帮您核对"** —— 让客户看得见指代依据,错了也能立刻纠正。这比反问更省客户一步。
@@ -279,7 +286,10 @@ E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS
**背景**:`科创债ETF南方怎么样` 的 top1 是 **南方稳健增利债券 A 的产品卡**(0.6696)—— 客户问 A,拿到的却是 B 的资料。这是"答非所问",比"没找到"更伤。
**需要你给的**:是否允许我在 **`E5b` 相关性闸门**里加一条:**问句点名了产品 X,而命中块讲的是产品 Y** ⇒ 判为"无共同业务词",改回**"我暂时没找到对应的公开资料"**?
**裁定(2026-09-21)**:**加**,见 §9。落地时补了两条**判据收窄**(都不是原建议里写的):
① 产品名的**两种真实写法**都要认 —— 除「南方 + 名称 + 产品类型后缀」外,还要认「名称 + `ETF南方`」(场内基金手册里 10 只 ETF 全是品牌在后的写法);
② 正则**没有左边界**会把动词吞进产品名(「我想买科创债ETF南方」),而**吞进来会造出假「没找到」** —— 加 `_strip_product_name_lead` 逐字剥离前缀功能字。
**我的建议**:**加**。理由:金融场景下"诚实说没找到"优于"贴一段别的产品的资料";且这条闸门已有同源实现(`_hits_share_terms`),改动面很小。
@@ -287,7 +297,7 @@ E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS
**背景**:`E5b` 是本轮退化条里最大的一类(6/16)。它的**内容往往是对的**(如「债基和货基哪个收益高」贴出的 FAQ 正文就是标准答案),但**开场白的语气**让它显得像兜底。
**需要你给的**:是否把开场白改成更像"作答"的措辞(例如「关于这一点,公开资料里的口径是:」),并且**当命中块本身就是一条 FAQ 问答对时,直接以答案正文开场**?
**裁定(2026-09-21)**:**改**,见 §9。
**我的建议**:**改**。这是**零风险**的改动(只动模板字符串),但对"看起来智不智能"的体感影响最大 —— 同样一段内容,开场白不同,客户对"这个客服会不会答"的判断完全不同。
@@ -317,3 +327,65 @@ E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS
4. **代码里 4 条 ruff 告警是既有的**(`customer_service_rules.py:318/416`、`customer_service.py:34/1188` 的 E501 与 I001),不是本轮引入,也**未顺手改** —— 避免把无关改动混进本轮 diff。
5. **真机复验依赖本地四依赖**(MySQL / Redis / Milvus / API+Worker)。复跑前须确认 `/internal/health/ready` 三依赖全绿,否则 `E5b` 会被误读成"缺陷"。
6. **`C-9` 的产物 `_PRODUCT_NAME_SHAPE` 是形状匹配、不是产品名白名单**:语料里新增产品名只要仍符合「南方 + 名称 + 产品类型后缀」就能被认出;若将来出现**不符合该形状**的产品名(如纯英文名),②级降级会失效并落到③级(知识检索)—— 功能不减,只是拿不到裁决。
---
## 9. `W21` 第二轮 · 四项待决的落地与验收(2026-09-21)
> **口径**:本节是 §6 四项的**实施记录**。上一轮(§1—§8)不动,保留原貌以便追溯。
### 9.1 落地清单(文件级)
| 文件 | 改动 |
|---|---|
| `app/service/agent/implementations/customer_service.py` | ① `DEFAULT_EVIDENCE_SYSTEM` 红线 ② 补「不要把收益数字写进答复」;② `DEFAULT_EVIDENCE_TEMPLATE` 新增第 ④ 条同义禁令;③ `_answer_suitability` 在 `inferred=True` 时开场明示指代依据(`W21-D2`);④ 新增 `_ETF_NAME_SUFFIX` / `_PRODUCT_NAME_PATTERNS` / `_PRODUCT_NAME_LEAD_NOISE` / `_PRODUCT_MISMATCH_LOOKAHEAD` / `_strip_product_name_lead()` / `_product_names()` / `_names_other_product()`,并接入 `E5b` 相关性闸门(`W21-D3`);⑤ `PARTIAL_TEMPLATE` 改作答语气 + 新增 `PARTIAL_FAQ_TEMPLATE`,`_exit_partial` 按「命中块是不是 FAQ 问答对」分流(`W21-D4`) |
| `tests/unit/service/test_customer_service_agent.py` | **+9 条守卫**(`D1` 两条 / `D2` 一条 / `D3` 三条 / `D4` 三条) |
| `_eval_harness/cases_46.json` | `I-01` 的 `expected_exits` 由 `[E5b, E3]` 修订为 `[E5b, E3, E4]` + 写入 `exit_note`(理由见 9.3) |
| `开发文档/D3.7-…md` | `I-01` 行与 §注同步该修订 |
| `客服agent/D2.9-…md` | §2 出口话术表的 `E5b` 行、§5 `Z-04` 的出口形状描述同步新话术 |
| `客服agent/D2.1-…Todolist.md` | 升 `v6.38`;`C-11` 由「未修复」改为「已修复」 |
| `开发文档/D1.6-…md` | §9.3 四项标「已落地」;新增 §10 本轮对话上下文 |
| 本文件 | §6 四项标「已裁定」;新增 §9;§0 / §8 同步 |
### 9.2 验收(可复算)
| 项 | 结果 |
|---|---|
| 金标 46 条(进程内 `probe.py` + `score.py`) | `M-1 46/46`、`M-4 46/46`、`M-6 5/46`、`M-7/M-8/M-9/M-10 = 0`、`M-2 28/31`、`M-2b 15/18`、`M-3 4/4` —— **与上一轮逐项一致** |
| 全量单元 / 集成回归 | **`1994 passed / 3 skipped`**(上一轮 `1985/3`;**+9 条新守卫全绿**) |
| `ruff` | 仍只有**既有**告警(`customer_service.py:34` I001、`:1220` E501 等),**本轮零新增**(`:1220` 即上一轮的 `:1188`,行号因新增代码下移) |
| 真 HTTP 关键问句 | 11 条,见 9.4 |
### 9.3 `I-01` 金标期望修订(**这是一处判据变更,必须知情**)
| 项 | 内容 |
|---|---|
| 原期望 | `expected_exits = [E5b, E3]`(`D3.7` §I 组) |
| 现状(修订后) | `[E5b, E3, E4]` |
| 触发原因 | `W21-D1` 让 `E4` 不再被收益数值闸门误拦 ⇒ `I-01`「南方科技是什么公司?」首次真正走到**提示词红线 ②**(该红线**专为「名称查不到」设计**:不回绝、不反问、**不重复那个名称**、直接给最接近的正确事实) |
| 为什么改期望而不是改行为 | ① 考点的**两条**没变:禁忌字面「南方科技」**不出现**(`M-7` 无违反)、关键事实「南方基金」**命中**(`M-4` 通过);② 答案**更好** —— 旧 `E5b` 贴的是「6.2 核心科技平台 / 南方基金·智能服务系统」(答非所问),新 `E4` 给的是公司全称 / 成立日期 / 注册资本 / 业务范围;③ 若强行把 `E4` 拦回 `E5b`,等于**关掉**提示词红线 ② 的唯一落点 |
| 备选方案(未采纳,留档) | 加一条「问句主体名在证据中完全查不到 ⇒ `E4` 不接管」,可让 `M-1` 回到 `46/46` 而**不改金标**;代价是 `D3.7` 已定的 `I-03`/`I-04`(并列 `E4`)语义变窄,且把"能答的公司信息"降级成"贴一段原文" |
### 9.4 真 HTTP 复验(`cust_t` / 访客,`POST /api/v1/agent-runs`)
| # | 问句 | 出口 | 结果 |
|---|---|---|---|
| 1 | 南方现金添利怎么样 | `E4` | 生成作答:R1 / 代码 900501 / 起投 1 元 / 费率与申赎规则;**零收益数值**;含「不承诺收益,可能产生亏损」 |
| 2 | 买基金要手续费吗 | `E4` | 合并作答:交易环节 + 持有环节 + 赎回费 + 线上优惠 |
| 3 | 债基和货基哪个收益高 | `E4` | 明确「本公司不提供这类比较结论」+ 两类特征对比 |
| 4 | 科创债ETF南方怎么样 | `E4` | 诚实答「手册中查不到该名称」+ 列出手册里**确实存在**的示例产品;**未复述**不存在之名 |
| 5 | 你们的投诉电话是多少 | `E3` | 投诉渠道 + 热线;`transfer=False`(`C-10` 未回归) |
| 6 | 我就要人工 | `E5c` | `transfer=True`(`G-05` 白名单,未越界) |
| 7 | 什么样的基金不会亏钱? | `COMPLIANCE` | 合规拒答,**不转人工** |
| 8 | 南方科技是什么公司? | `E4` | 见 9.3;答复内无「南方科技」 |
| 9 | 南方现金添利怎么样(**访客**) | `E4` | 与客户档同答(`public` 档);**未越权** |
| 10 | `我想买个债基` → `它适合我吗` | `E3` → `E2d` | 第二轮开场即「您上一轮提到的是「南方稳健增利债券 A」,我就按它帮您核对:」(`W21-D2`) |
| 11 | `赎回费怎么算` → `持有 8 个月呢` | `E3` → `E3` | 追问继承谓词与参数,给分档表(`C-9` 未回归) |
### 9.5 诚实留痕(本轮新增,别漏)
1. **`D1` 是"降低概率"不是"消灭概率"**:只把 ④ 写进 user 模板时,三条问句仍各有约 1/3 会落到 `E5b`;把同义禁令补进 **system 红线 ②** 后测得 **9/9**。若某次生成仍写出收益数值,`_exit_partial` 的兜底照旧生效(**宁可兜底,不可输出**)。
2. **`I-01` 的金标期望被修订过**(9.3)。这是**判据变更**,不是"零回归" —— 请知情后使用 `M-1 46/46` 这个数字。
3. **`docs/43-场内基金产品手册` 未入库**(702 块里「科创债」0 处):`W21-D3` 的闸门只做到**止损**(不再贴错产品),**根本修复**需要把它纳入 `SOURCES` 并重建集合 —— 属**语料变更**,未在本轮擅自执行,已登记为待决(`D1.6` §10.4)。
4. **`E5b` 展示层仍是"原文直返"**:实测「买基金要手续费吗」的 `E5b` 分支正文里含「专户业绩报酬 | 按合同约定计提 | 超额收益的 10%—20%」,会被 `hits_zero_tolerance` 判 True(`收益`+数字% 陷阱)。**这是既有行为**(`drop_yield_claims` 只丢带收益指标名的行),治理层不对 `E5b` 正文做二次零容忍扫描,因此**不会拦单**;但**展示层净化是否要覆盖这一形态**,建议列入下一轮待决。
5. **改动只落在 `客服agent/` 与 `开发文档/` 两份权威副本 + 仓库同名镜像**,三处一致(`D1.1` §权威声明)。