feat(W20): 新增 E2c-my 出口 + 展示层净化 + P2 自助流程豁免(金标零回归)

用户口径:既智能又安全,不靠"答不上就转人工";按本人画像测评列出可购买的产品,
但不引导购买。

出口由五个扩为六个:
- 新增 E2c-my:按客户本人权威测评等级给出可购买范围 + 在售清单
  (只列代码/名称/类别/风险等级,按代码升序;出口后置引导词护栏,命中即降级为仅范围)
- 点名档位(「我可以买 R3 的产品吗」)先给一句直接裁决再列范围
  —— 修复前只给清单,"不"字始终没说出来
- 新工具 query_eligible_products(suitability:read)+ 发布 release 220

真实业务缺陷:
- P2_SELF_SERVICE_PATTERNS:问「怎么修改绑定的银行卡」不再建单转人工(FAQ 里就有答案);
  「帮我把绑定银行卡换一下」仍走 P2
- 展示层净化 render_plain / drop_yield_claims / prettify_title 接在 E3/E4/E5b/澄清四处
  (E4 模型会照抄证据包的 markdown 标记)
- 空结果护栏:语料里 15 个纯收益切片被净化清空时回退 E5b,不给空气泡

测试:定向 334 passed;全量回归 1969 passed / 3 skipped / 0 failed(基线 1946);
46 条金标 M-1 46/46=100%、M-4 100%、M-6 5/46=10.9%、M-7~M-10 全 0 —— 与
score_w11b 逐项一致(零回归)。

落档:D1.6 §4.49 / D2.1 v6.36 / D1.1 §30
This commit is contained in:
张胜宇
2026-09-20 18:55:22 +08:00
parent dc83f4f4a7
commit 35effab70f
10 changed files with 1537 additions and 27 deletions
@@ -2,7 +2,7 @@
> **体系编号**:`D1.1` · 域:一、治理与索引 · 编号体系见 `D1.1` §4.0
> **编号**:CS-DOC-2026-017 | **版本**:v1.10 | **日期**:2026-09-20 | **状态**:**现行(活文档,随文档区变动同步更新)**
> **编号**:CS-DOC-2026-017 | **版本**:v1.11 | **日期**:2026-09-20 | **状态**:**现行(活文档,随文档区变动同步更新)**
> **性质**:本文件是 `开发文档\` 的**唯一入口**。任何人(含三个月后的自己)打开这一份,就应知道:先读什么、哪份为准、每份什么状态。
> **盘点范围**:`开发文档\`(**52 个文件** = 51 份编号文档 + 1 份入口存根 `CLAUDE.md`,无归档子目录)+ `客服agent\`(**9 份**对外交付文档)。
@@ -59,7 +59,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| # | 体系编号 | 文档 | 版本 | 作用 |
|---|---|---|---|---|
| **A1** | **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v6.30** | **唯一开工入口**。**57 项 / 8 批次(A—H)** / 12 步关键路径 / 2 组会签 / **完工判据 13 条**。**新增批次 H · 智能增强**(`H-01`~`H-06`) |
| **A1** | **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v6.36** | **唯一开工入口**。**57 项 / 8 批次(A—H)** / 12 步关键路径 / 2 组会签 / **完工判据 13 条**。**新增批次 H · 智能增强**(`H-01`~`H-06`) |
| **A2** | **D2.2** | `客服agent\D2.2-客服Agent需求文档.html` | **v2.7** | 对外需求:**FR-CS-001~052**(52 条,新增域 H)+ NFR-CS-001~021 全量、身份与鉴权模型、验收标准(**新增 AC-13 金标门禁**) |
| **A3** | **D2.3** | `客服agent\D2.3-客服Agent开发计划.html` | **v1.1** | 前置条件、测试环境就位(G-00)、会签流程、门禁、交付物、**批次 H(§3.4b)** |
| **A4** | **D2.4** | `客服agent\D2.4-客服Agent知识库设计方案.html` | **v1.7** | 三集合 / 三档可见性 / **集合内分区隔离(§7.2.1)** / 8 模块 / 7 步入库 8 步检索 / 8 项决策 / **附录F 五出口对接** |
@@ -123,7 +123,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| **D1.4** | `开发文档\D1.4-知识源与品牌整改变更说明-2026-09-17.md` | CS-CONTENT-2026-016 | 现行 | 逐份变更说明(§3.1—§3.9 改写映射 + G-01~G-09) |
| **D1.5** | `开发文档\D1.5-开发前决策清单与阻塞项-2026-09-17.md` | CS-DOC-2026-018 v1.0 | 现行 | 🔴 **开工前唯一决策登记册**:28 项待你拍板 + 阻塞分级(P0 12 / P1 10 / P2 6)+ 需你提供的 7 项输入;§7 为回填表(增补项见 `D1.6` §4.3) |
| **D1.6** | `开发文档\D1.6-对话上下文提取与开工前补充决策-2026-09-17.md` | CS-DOC-2026-019 v1.0 | 现行 | 🔴 **本轮会话上下文提取件**:已读清单与权威链校正 / 可复用事实(含实测)/ 旧实现 **7 条转人工通路** / 文档缺陷 `Q-1.1`~`Q-1.6` / 前提风险 `K-01`~`K-08` / 待拍板 `N-01`~`N-09` |
| **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v6.32** | 现行 | 🔴 **唯一开工入口**:**57 项 / 8 批次** / 12 步关键路径 / **批次 H 智能增强** |
| **D2.1** | `客服agent\D2.1-客服Agent执行Todolist.md` | **v6.36** | 现行 | 🔴 **唯一开工入口**:**57 项 / 8 批次** / 12 步关键路径 / **批次 H 智能增强** |
| **D2.2** | `客服agent\D2.2-客服Agent需求文档.html` | **v2.7** | 现行 | 🔴 对外需求:**FR-CS-001~052** + NFR-CS-001~021 |
| **D2.3** | `客服agent\D2.3-客服Agent开发计划.html` | **v1.1** | 现行 | 🔴 前置条件 / 批次 / 会签 / 门禁 / 交付物 |
| **D2.4** | `客服agent\D2.4-客服Agent知识库设计方案.html` | **v1.7** | 现行 | 🔴 三集合 / 三档可见性 / **分区隔离** / 7 步入库 8 步检索 / **附录F** |
@@ -185,7 +185,7 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
| 文件名 | 版本 | 日期 | 定位 | 关联 |
|---|---|---|---|---|
| `D2.1-客服Agent执行Todolist.md` | **v6.33** | 2026-09-17 | 唯一开工入口 | 收敛自 `开发文档\D3.4-客服Agent重构Todolist.md` v5.1 |
| `D2.1-客服Agent执行Todolist.md` | **v6.36** | 2026-09-17 | 唯一开工入口 | 收敛自 `开发文档\D3.4-客服Agent重构Todolist.md` v5.1 |
| `D2.2-客服Agent需求文档.html` | **v2.7** | 2026-09-20 | 对外需求(FR **52** / NFR 21) | 完整版见 §4.2 |
| `D2.3-客服Agent开发计划.html` | **v1.1** | 2026-09-17 | 批次 / 会签 / 门禁 | 与 A1 批次号一一对应 |
| `D2.4-客服Agent知识库设计方案.html` | **v1.7** | 2026-09-20 | 三集合 / 三档 / 入库检索流程;**索引统一 `AUTOINDEX`、语料 675 块** | 完整版见 §4.2 |
@@ -858,6 +858,28 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
---
## 30. 第二十六轮:`W20` 实施轮 —— 新增出口 `E2c-my` + 展示层净化 + 两处真实业务缺陷修复(2026-09-20)
> **本轮做什么**:按用户「**贴合真实业务场景,自己去推理、自己判断、自己修复**」的要求,把「推理 → 测试 → 修复」一次性跑完,并把五出口扩为**六出口**(新增 `E2c-my`)。
| 项 | 内容 |
|---|---|
| **新增出口 `E2c-my`** | 按**客户本人权威测评等级**给出「可购买范围 + 在售清单」。依据 `PROD-012` §4.2(**`public` 档**)C—R 矩阵;清单只含**代码 / 名称 / 类别 / 风险等级**,**按产品代码升序**(不按收益排 —— 排序即隐性推荐);出口后置 `promotional_wording_violation` 护栏,命中即降级为只讲范围 |
| **新增工具与发布** | `query_eligible_products`(`suitability:read`,customer / advisor / operator / admin),发布 **release 220**(active)。⚠️ 提示词模板与工具白名单**不在同一张表**,发布脚本必须**两级继承**(`platform_config_item` + `active_prompt_templates()`),否则提示词被静默丢弃 |
| **点名档位的直接裁决(新)** | 「我可以买 R3 的产品吗」修复前**只给清单、不说"不可以"**;现在先给一句直接裁决再列范围(裁决由矩阵结果反推,措辞与 `E2c` 同源) |
| **`P2` 自助流程问法豁免(新)** | 「怎么修改绑定的银行卡」修复前**建单转人工**,而 `FAQ` 里**就有**这条答案;新增 `P2_SELF_SERVICE_PATTERNS` + `P2_DELEGATION_MARKERS`(「帮我把绑定银行卡换一下」**仍转人工**) |
| **展示层净化(新)** | `render_plain`(去 markdown 标记)/ `drop_yield_claims`(收益数值不得输出)/ `prettify_title`(澄清候选),接在 `E3` / `E4` / `E5b` / 澄清候选**四处**;**空结果护栏**:被清空的块(实测 15 个纯收益切片)**回退 `E5b`,不给空气泡** |
| **测试** | 定向 **334 passed**;全量回归 **1969 passed / 3 skipped / 0 failed**(本轮基线 1946 passed) |
| **金标 46 条** | `M-1` **46/46 = 100%** / `M-4` **100%** / `M-6` **5/46 = 10.9%** / `M-7`·`M-8`·`M-9`·`M-10` **全 0**;与上一轮 `score_w11b` **逐项一致 ⇒ 零回归** |
| **真机场景扫描** | 25 条客户问法 + 3 条访客问法 ⇒ 转人工 **3/25**,全部为**应转**(投诉 / 销户 / 代办写操作) |
| **顺带修掉一处测试隐患** | 旧测试用 `Class.attr` 存取 `staticmethod` 再赋回,会把静态方法**静默变成普通方法**(后置测试全部少传 `self`)⇒ 改存取 `__dict__` 里的描述符 |
| **待决** | `DEC-W20-6`…`-9`(人设外显度 / 资料变更类口径 / 收益过滤层次 / 金标是否扩到 49 条),详见 `D1.6` §4.49 六 |
| **版本位** | 本文件头部 **v1.10 → v1.11**;`客服agent\D2.1` 标题 **v6.35 → v6.36**;§2 开工只读表 / §4.0 总表 / §4.2 明细**三处 `D2.1` 版本位一并同步**(此前长期分别停在 `v6.30` / `v6.32` / `v6.33`) |
| **交叉引用** | `D1.6` 新增 §4.49;`D2.1` 新增 `v6.36` 段 |
| ⚠️ **未做(诚实声明)** | ① 人设只做"语气层",未做更外显的人设;② 收益过滤只在**展示层**,未推回语料层;③ 金标仍为 **46 条**;④ `D2.9` §8.1 的 `D-2` / `D-4` 两个已知边界仍只登记不修 |
---
> **维护责任**:本文件为活文档。**新增 / 改名 / 归档 / 改版本号后,须同步更新本文件 §3 与 §4.0 总表对应行**。
>
> 编制:项目文档组 | 审核:合规稽核部 | 日期:2026-09-17
@@ -3160,6 +3160,76 @@ pytest **2 failed / 1577 passed / 2 skipped**(= `T0` 基线同两项)、ruff
- 本轮**未改代码**、**未跑全量回归**、**未动任何文档版本位**(`D2.x` / `D3.x` 均不涉及)。
- `DEC-W20-1` 若做得过宽(例如直接删掉第 4 条),会把「我能买什么产品」「帮我推荐一只基金」一起放行 ⇒ 属**安全回退**;`DEC-W20-5` 的第 ③ 条就是为此设的回归钉子。
### 4.49 2026-09-20 第四十五轮会话记录(`W20` 实施轮:`E2c-my` 新出口 + 展示层净化 + 两处真实业务缺陷修复 + 全量回归)
> **用户原话**:「你现在要贴合真实的业务场景 自己去推理 自己判断 自己修复 要提高效率 就像我刚刚跟你说的这类问题 你一定要看看还有那些类似的问题 我不想让我的agent看起来只会转人工 必要时你可以加上一个活泼可爱的人设来回答用户的问题 推理 测试 修复 这些你一次性跑完 然后关于上下文 你自己反复去汲取」
> **上一条业务口径(本轮据此施工)**:「什么该回答 什么不该回答 你自己要分的清楚 要根据用户自己的画像测评去列出他能购买的产品 而不是引导他去买产品 这是两个完全不同的概念」
> **本轮性质**:**实施轮** —— 代码落地 + 定向测试 + 真机复验 + 46 条金标复跑 + 全量回归 + 落档。
#### 一、业务口径(本轮据以施工的三分法)
| 层 | 内容 | 本系统做法 |
|---|---|---|
| ✅ **该答** | 按**客户本人权威测评等级**,**陈述**他能买的产品范围与清单 | 新出口 `E2c-my`;依据 `PROD-012` §4.2(**`public` 档**)C—R 矩阵 |
| ✅ **该答(本轮新增)** | 点名某一档的裁决(「我可以买 R3 的产品吗」) | 出口内**先给一句直接裁决**,再列范围 |
| ❌ **不该答** | 推荐 / 建议购买 / 更适合您 / 收益最高 / 抓紧申购 | `ADVICE_BOUNDARY_REPLY`(`PROMOTION_REQUEST_PATTERNS` 拦,**豁免判据精确到"本人 + 购买 + 范围词"**) |
界线一句话:**陈述**(由等级决定,与客户想买哪只无关)vs **引导**(由指向性结论构成)。`suitability_service` 里 2026-09-11 的既有裁定「**客服回答按矩阵**」是本出口的直接依据 —— 原实现把这类问句拦成边界话术,**违背既有裁定**,不是"要不要开"的问题。
#### 二、代码落地清单
| 文件 | 改动 | 为什么 |
|---|---|---|
| `app/core/customer_service_rules.py` | 新增 `OWN_ELIGIBILITY_PATTERNS` + `is_own_eligibility_question()`;推介门禁加豁免;新增 `PROMOTIONAL_WORDING_PATTERNS` + `promotional_wording_violation()` + `_locally_negated()`;**新增 `P2_SELF_SERVICE_PATTERNS` + `P2_DELEGATION_MARKERS` + `_is_p2_self_service_question()`** | 前者把「问等级范围」与「要推荐」分开;后者把「问怎么办」与「你替我办」分开 |
| `app/service/suitability_service.py` | 新增 `EligibleProductQuery` / `EligibleProductView` / `EligibleProductsService` / `query_eligible_products_tool` + `_ELIGIBLE_SQL`;写 `InteractionAudit(action_type="suitability.eligible_products")` | 清单**只读 `fin_product` 的公开四字段**(代码 / 名称 / 类别 / 风险等级),**按代码升序**,不按收益排 —— 排序本身就是隐性推荐 |
| `app/service/agent/bootstrap.py` | 注册工具 `query_eligible_products`(`required_permission="suitability:read"`) | 白名单与鉴权同源 |
| `app/service/agent/implementations/customer_service.py` | 新出口 `E2c-my`(`_answer_eligibility` / `_render_eligible` / `_eligible_text` / `_exit_eligible_miss`);新增决策辅助 `_asked_level_verdict()`;**展示层三件套** `render_plain` / `drop_yield_claims` / `prettify_title` | 见下 |
| `tools/publish_customer_service_tool_config.py` | 新建发布脚本(含 `active_prompt_templates()` **两级回退**),发布 release **220**(active) | 提示词模板与工具白名单**不在同一张表**,只继承 `platform_config_item` 会**静默丢提示词** |
#### 三、真机复现 → 修复对照(`hypothesis → 实测 → 修复 → 复验`)
| # | 实测现象(修复前) | 根因 | 修复 | 复验(修复后) |
|---|---|---|---|---|
| 1 | 「我想买点理财产品」→ 返回政策原文「第二条 适用范围」 | 未走 `E2c-my`;检索撞上政策块 | 补 `OWN_ELIGIBILITY_PATTERNS` 骨架⑥「我(想/要/打算/考虑)买…」 | 走 `E2c-my`,列出 **4 只**在售产品 ✅ |
| 2 | 「我可以买 R3 的产品吗」→ 只回「您可购买 R1、R2」清单,**"不"字始终没说出来** | `E2c` 的 `is_general_suitability_question` 要求问句**自带 C 等级**;以第一人称为主体的问法落不到任何出口 | `_asked_level_verdict()`:问句点名档位时**先给直接裁决**(裁决由矩阵结果反推,措辞与 `E2c` 同源) | 「您问的 R3(中风险)等级产品:**不可以购买**(超出您当前等级可购买的范围)」+ 范围 ✅ |
| 3 | 「怎么修改绑定的银行卡」→ **建单转人工** | `P2_PATTERNS` 第 1 条只认「动词 + 资料词」,不区分"问方法"与"让客服代办";而 `FAQ` 里**就有**这条答案 | `P2_SELF_SERVICE_PATTERNS`(疑问词 + 改动动作)+ `P2_DELEGATION_MARKERS` 反向判据,**两个分支共用** | 走检索答出「可在 APP「我的—安全中心」自助办理…」,**不建单** ✅;「帮我把绑定银行卡换一下」**仍转人工** ✅ |
| 4 | markdown 源文原样发给客户(`### 第二条`、`**一、购买渠道**`、`\| 费用类型 \|`) | `E3` 原文直返、`E4` 模型照抄证据包 —— 两者拿到的都是 **markdown 源文** | `render_plain()` 接在 `E3` / `E4` / `E5b` / 澄清候选**四处** | 全链路纯文本,客户看不到任何标记 ✅ |
| 5 | 「我想找一个收益高一点的产品」→ 答出「近一年收益率 7.60%」 | 知识块自带业绩数字,`E3` 直返把它摊到客户眼前 | `drop_yield_claims()` + **空结果护栏** | 收益行不再出现;整块被清空时**回退 `E5b` 而不是给空气泡** ✅ |
| 6 | 澄清候选被截断成半句「… · 1.」 | `title[:40]` 硬切 | `prettify_title()`(丢悬空编号段 / 文档名段,保留章节与小节) | 「一、公募基金产品」 ✅ |
| 7 | `E5b` 自我否定「这个问题我暂时只能提供以下公开资料…」;拒答话术纯拒绝、不给替代动作 | 文案 | 改为「我先帮您把找到的公开资料放上来」;`ADVICE_BOUNDARY_REPLY` 补「您可以直接问我『我能买什么』」 | 答对的题不再自我否定;拒答给出了可执行替代 ✅ |
#### 四、测试与回归(可复核)
| 项 | 结果 |
|---|---|
| 定向测试(`test_customer_service_rules` + `test_customer_service_agent` + `test_suitability_service`) | **334 passed** |
| 全量回归(**先停 Worker**,避免抢 MySQL outbox 造成假红) | **1969 passed / 3 skipped / 0 failed**(本轮基线 1946 passed) |
| 46 条金标(真实链路 `_eval_harness\probe.py`) | `M-1` 出口准确率 **46/46 = 100%**;`M-2` 90.3%;`M-3` 4/4;`M-4` 事实正确率 **46/46 = 100%**;`M-5` 0;`M-6` 转人工率 **5/46 = 10.9%**;`M-7` / `M-8` / `M-9` / `M-10` **全 0** |
| 与上一轮基线对比 | 与 `_eval_harness\score_w11b.json` **逐项一致** ⇒ **零回归** |
| 真机场景扫描(`_probe_scan.py`) | 25 条客户问法 + 3 条访客问法 ⇒ 转人工 **3/25**,且全部**应转**(投诉 / 销户 / 代办写操作) |
#### 五、诚实留痕(含我自己的失误,如实登记)
1. 第一版 `tools/publish_customer_service_tool_config.py` **只继承 `platform_config_item`**,激活后 `customer_service_chitchat` 提示词**失效** ⇒ 已加 `active_prompt_templates()` 两级回退修好。
2. 修的过程中还踩到提示词路由**不在 release 作用域前缀下** ⇒ 404 ⇒ 已改 `/api/v1/admin/prompt-templates`。
3. 我最初给出的 `DEC-W20-3` 建议(「默认只答等级、要清单得再问一次」)**是错的** —— 把「陈述可买范围」与「引导购买」混为一谈,用户已纠正。**这条判断更正必须如实登记**,本轮按"直接列清单"落地。
4. **旧测试的还原写法有隐患**(本轮踩到):`test_eligible_exit_degrades_to_scope_only_if_the_template_ever_turns_promotional` 用 `Class.attr` 取出 `staticmethod` 再赋回,拿到的是"解绑后的函数",赋回类属性会**静默变成普通方法** ⇒ 此后每个测试都少传一个 `self` 而 `TypeError`,且**只在后置测试里炸**。已改为存取 `__dict__` 里的 `staticmethod` 描述符。
5. `drop_yield_claims` 的**空结果**是本轮新发现:语料里 **15 个切片整个块只写一个收益数字**(如 `PROD-001-08`「七日年化收益率 约 1.92%」),直接返回会出现**空气泡**(前端一条空白消息)。已加护栏,并让 `_exit_partial` **跳过被清空的块**去挑下一个有内容的块。
6. 探针脚本两处**口径缺陷**(非产品缺陷)同样登记:① `_eval_harness\probe.py` 的 `TERMINALS` 缺 `E2c-my` 标签 ⇒ 清单里的产品代码(6 位数字)会被 `M-9` 误判成"无出处数字";② `_probe_scan.py` 访客复用固定 `session_id` ⇒ 撞上上一轮的匿名主体(`SESSION_NOT_ACCESSIBLE`)。
#### 六、待决项(等你裁定)
| 编号 | 待决 | 我的最优建议 |
|---|---|---|
| `DEC-W20-6` | **人设口径**:是否把"活泼可爱"做得更外显 | **建议只做"语气层"**(澄清 / `E5b` / 闲聊),**不碰**安全话术与 C—R 结论。金融场景人设过度会削弱专业感,也容易与合规文案冲突;若要更明显,先定"不得影响合规话术"的红线 |
| `DEC-W20-7` | **资料变更类**(「我要修改绑定的银行卡」「修改我的手机号」)是否也先答自助流程、只在明确要求代办时转人工 | **建议维持现状**(仍走 `P2` 建单)。理由:这不是"答不了",而是**写操作代办**的红线;流程咨询问法(怎么 / 如何)本轮已放行,覆盖面已经够 |
| `DEC-W20-8` | 收益数字过滤是否从**展示层**再往**语料层**推 | **建议只留展示层**。往语料推要重建集合、代价大,且知识块作为"公司已发布资料"保留原样更可审计;展示层已证明挡得住(15 个纯收益块全部被拦) |
| `DEC-W20-9` | 金标是否扩到 49 条(补:①「我现在可以买什么等级的产品」②「我适合买什么产品」③回归钉子「帮我推荐一只基金」) | **建议演示后加**。46 条是 `D2.6` / `D3.7` 已发布指标的**冻结基线**,中途加减会让转人工率 / 出口准确率 / 事实正确率全部失效;本轮已把等价覆盖落在单元 / 集成守卫(成本为零) |
#### 七、版本位
`D1.6` 新增 §4.49;`D2.1` 新增 `v6.36` 段(标题 v6.35 → v6.36);`D1.1` 新增 §30(头部 v1.10 → v1.11,`D2.1` 版本位在 §2 / §4.0 / §4.2 三处同步)。
## 5. 建议的开工顺序(在 `DEC-11` 拍板后)
```