docs(W20): 定位「按我的风险等级能买什么」被 PROMOTION_REQUEST_PATTERNS 第4条误拦 + 五条待决登记(D1.6 4.48 / D2.1 v6.35 / D1.1 29)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
# 客服 Agent 执行 Todolist(执行看板 · v6.34)
|
||||
# 客服 Agent 执行 Todolist(执行看板 · v6.35)
|
||||
|
||||
|
||||
> **体系编号**:`D2.1` · 域:二、对外交付 · 编号体系见 `D1.1` §4.0
|
||||
|
||||
@@ -46,6 +47,24 @@
|
||||
|
||||
**看板状态更新**:`F-3` → **✅ 已落地**(新增 4 条单测)。批次 H 剩余:`H-05`。新增待办:**三项安全路由缺口收口**(`G-01` 优先,建议排在 `H-05` 前)。
|
||||
|
||||
## v6.35 本轮修订要点(2026-09-20 · `W20` 咨询轮:「按我的风险等级能买什么」被 `PROMOTION_REQUEST_PATTERNS` 第 4 条**误拦** —— 根因定位 + 五条待决)
|
||||
|
||||
> **触发**:用户「这个为什么不能根据自己的风险等级去给他列出来他能买的产品」(附前端截图:`cust_t` 问「我现在可以买什么等级的产品」→ 拿到 `ADVICE_BOUNDARY_REPLY`)。完整会话记录见 `D1.6` §4.48;证据 = 真机 6 条 + 离线判据复算 + 读码。
|
||||
> **本轮性质**:**咨询 / 定位轮,不改代码**。
|
||||
|
||||
| # | 结论 | 依据 |
|
||||
|---|---|---|
|
||||
| 1 | 🔴 **根因已定位**:「我现在可以买什么等级的产品」「我能买什么风险等级的产品?」「我适合买什么产品」三条均被 `PROMOTION_REQUEST_PATTERNS` **第 4 条**命中 → 返回 `ADVICE_BOUNDARY_REPLY`(**与截图同一段话术**) | 离线判据复算 + 真机 6 条 |
|
||||
| 2 | 🔴 **缺陷性质 = 窗口型误拦**:第 4 条的 `{0,6}` 窗口不认「等级 / 风险等级」这类**限定词**,把「问等级范围」(公开规则题)判成「问产品」(推介请求)。**与 `G-01` 同一缺陷形态** ⇒ 建议成类收口 | 读码 + 复算 |
|
||||
| 3 | ⚠️ **门禁先手,能力被埋**:`route_message()` 位于 `_route_and_answer()` **第一行** ⇒ 命中即短路,后面 `is_profile_question` / `E2` 等**本来能答**的路径全部跑不到 | 读码 |
|
||||
| 4 | 🟡 **能力其实已在(反证)**:同账号下「我是 C1,能买 R3 的产品吗?」→ `E2c` 矩阵**答对**;「我的风险等级是多少」→ `query_customer_profile` 答出**保守型(C1)** | 真机实测 |
|
||||
| 5 | 🔴 **第二处缺口(与门禁并列)**:现行三条出口**都不覆盖**「本人等级 + 匹配矩阵」—— `E2c` 要问句自带 C/R 两个等级、`_answer_suitability` 要具体产品、画像出口只给等级不套矩阵 ⇒ 建议新增 `E2c-my` | 读码 |
|
||||
| 6 | 📌 **一致性缺陷**:「我**适合**买什么产品」被拦、「**有哪些适合我的**产品」放行(但落 `E1` 澄清)⇒ 同一件事两种句式两种结果 | 真机实测 |
|
||||
| 7 | 📌 **合规依据已具备**:`PROD-012` §4.2 是 **`public` 档**,明文给出 C1—C5 各自的 R 范围(**C1 → R1—R2**),**并自行声明「不含资产配置比例与收益目标/区间」** ⇒ 告知「可购买哪些风险等级」**不构成投资建议** | 切片件实查 |
|
||||
| 8 | 📌 **五条待决已登记**(`DEC-W20-1`…`-5`),其中 `DEC-W20-5` 第 ③ 条是**回归钉子**:修完后「帮我推荐一只基金」**必须仍拦** | `D1.6` §4.48 五 |
|
||||
|
||||
**看板状态更新**:新增 `W20` 议题(**未落地**,等五项裁定);本轮**不动**任何出口、**不改**金标、**不做**版本位变更。
|
||||
|
||||
## v6.34 本轮修订要点(2026-09-20 · 三份完整版/收敛版索引口径补注 + `embedding` 端点唯一性守卫 + 门槛口径更正)
|
||||
|
||||
> **触发**:用户「按照你建议的来」(承接上轮末尾登记的两项待办:**建议 B** 与 `D3.1` / `D3.2` / `D2.2` 的索引状态更新注)。
|
||||
|
||||
+18
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
> **体系编号**:`D1.1` · 域:一、治理与索引 · 编号体系见 `D1.1` §4.0
|
||||
|
||||
> **编号**:CS-DOC-2026-017 | **版本**:v1.9 | **日期**:2026-09-20 | **状态**:**现行(活文档,随文档区变动同步更新)**
|
||||
> **编号**:CS-DOC-2026-017 | **版本**:v1.10 | **日期**:2026-09-20 | **状态**:**现行(活文档,随文档区变动同步更新)**
|
||||
> **性质**:本文件是 `开发文档\` 的**唯一入口**。任何人(含三个月后的自己)打开这一份,就应知道:先读什么、哪份为准、每份什么状态。
|
||||
> **盘点范围**:`开发文档\`(**52 个文件** = 51 份编号文档 + 1 份入口存根 `CLAUDE.md`,无归档子目录)+ `客服agent\`(**9 份**对外交付文档)。
|
||||
|
||||
@@ -839,6 +839,23 @@ A5 [D3.3] 访客与角色分离的鉴权方案建议(仅此一份,无新旧
|
||||
| **交叉引用** | `D1.6` 新增 §4.47;`D2.1` 新增 `v6.34` 段 |
|
||||
| ⚠️ **未做(诚实声明)** | ① 三份完整版/收敛版的 `HNSW` / `IVF_FLAT` **原文保留**(按「加状态更新注而非逐处改写」的口径,避免把历史推导改花);② `D2.9` §8.1 的 `D-2`(重复问句漂移)仍**只登记不修**、演示避开;③ `D-4`(英文问句落 `E5b`)登记为已知边界,未改代码 |
|
||||
|
||||
## 29. 第二十五轮:`W20` 咨询轮 —— 「按我的风险等级能买什么」被安全门禁误拦的根因定位(2026-09-20)
|
||||
|
||||
> **本轮做什么**:用户提问「这个为什么不能根据自己的风险等级去给他列出来他能买的产品」(附前端截图)。本轮**只做定位**:真机复现 6 条 + 离线判据复算 + 读码,**不改代码、不改任何文档版本位**。
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **现象** | 已登录客户 `cust_t` 问「我现在可以买什么等级的产品」→ 拿到 `ADVICE_BOUNDARY_REPLY`(「不能为您推荐具体产品…」整段边界话术) |
|
||||
| **根因** | `app\core\customer_service_rules.py` 的 `route_message()` 中 `PROMOTION_REQUEST_PATTERNS` **第 4 条**命中;其 `{0,6}` 窗口不认「等级 / 风险等级」限定词 ⇒ 把「问等级范围」(公开规则题)判成「问产品」(推介请求)。**与 `G-01` 同一缺陷形态** |
|
||||
| **为何后果严重** | 该门禁在 `_route_and_answer()` **第一行**,命中即短路 ⇒ 后面**本来能答**的路径(画像出口 / `E2` 计算型)**全部跑不到** |
|
||||
| **反证(能力已在)** | 同账号下「我是 C1,能买 R3 的产品吗?」→ `E2c` 矩阵答对;「我的风险等级是多少」→ 答出**保守型(C1)** |
|
||||
| **第二处缺口** | 现行出口**无**「本人等级 + 匹配矩阵」的组合路径 ⇒ 建议新增 `E2c-my` |
|
||||
| **合规依据** | `PROD-012` §4.2(**`public` 档**)明文给出 C1—C5 的 R 范围(**C1 → R1—R2**),且自行声明不含配置比例与收益区间 ⇒ 告知「可购买哪些风险等级」**不构成投资建议** |
|
||||
| **待决** | `DEC-W20-1`…`-5` 五条(含**回归钉子**「帮我推荐一只基金」修完后**必须仍拦**),详见 `D1.6` §4.48 五 |
|
||||
| **版本位** | **本轮不改任何文档版本位**(纯会话记录);本文件头部 v1.9 → **v1.10** |
|
||||
| **交叉引用** | `D1.6` 新增 §4.48;`D2.1` 新增 `v6.35` 段 |
|
||||
| ⚠️ **未做(诚实声明)** | 未改代码、未跑全量回归、未改金标;五项待决**等用户裁定**后再执行 |
|
||||
|
||||
---
|
||||
|
||||
> **维护责任**:本文件为活文档。**新增 / 改名 / 归档 / 改版本号后,须同步更新本文件 §3 与 §4.0 总表对应行**。
|
||||
|
||||
@@ -3093,6 +3093,73 @@ pytest **2 failed / 1577 passed / 2 skipped**(= `T0` 基线同两项)、ruff
|
||||
| ⚠️ **自我失误留痕(必读)** | 本轮清临时文件时,我的删除判据写得过宽(「顶层 `_*.py` / `_*.txt` 一律删」),**误删了不该删的文件**:① `_consistency.py` —— 从 `客服agent\_build\_consistency.py`(同一份、已入库)**原样恢复**;② `_legacy_customer_service.py` —— 按「从 `git HEAD` 导出」的原始口径,从 `f72a545:app/service/agent/implementations/customer_service.py`(748 行、**无** `E5b`,即旧实现)**逐字节重建**(40,554 字节),并核对 `probe_legacy.py` 的加载契约(`CustomerServiceAgent` + 5 个终端方法)通过;③ `_fe_boundary_http.py` —— **原件不可逐字恢复**(不在 git、非回收站可还原),已按文档记载的判据**重建同名脚本并实跑 12/12**,文件头显式标注「重建件、非原件」;④ 另丢失若干**历史轮次的原始日志**(`_regress_pytest*.txt` / `_verify_all.txt` / `_pre_commit_pytest.txt` / `_commit_msg.txt` / `_merge_msg.txt`)—— 其**结论都在文档表格里**,但**原始输出已不可追**。**教训**:清理必须按「本轮新建的文件清单」逐个删,不能用通配判据 |
|
||||
| ⚠️ 未做(诚实声明) | ① 三份完整版 / 收敛版的 `HNSW` / `IVF_FLAT` **原文保留**(口径=加注不改原文);② `D-2`(同会话重复模糊问句漂移)仍**只登记不修**;③ `D-4`(英文问句落 `E5b`)登记为已知边界 |
|
||||
|
||||
### 4.48 2026-09-20 第四十四轮会话记录(`W20`:用户问「为什么不能按我的风险等级列出我能买的产品」→ 定位到 `PROMOTION_REQUEST_PATTERNS` 第 4 条**误拦**;**咨询轮,不改代码**)
|
||||
|
||||
> **用户原话**:「这个为什么不能根据自己的风险等级去给他列出来他能买的产品」,并附前端截图(已登录客户 `cust_t` 问「我现在可以买什么等级的产品」,Agent 返回 `ADVICE_BOUNDARY_REPLY` 整段「不能为您推荐具体产品…」)。
|
||||
> **本轮性质**:**咨询 / 定位轮** —— 只做「真机复现 + 根因定位 + 边界厘清 + 待决登记」,**未改任何代码、未改任何文档版本位**。
|
||||
|
||||
#### 一、真机复现(2026-09-20,账号 `cust_t` 已登录,档案风险等级 = **保守型 C1**)
|
||||
|
||||
| # | 问句 | 安全路由 | intent | tools | 结果 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 我现在可以买什么等级的产品 | 🔴 **被 `ADVICE_BOUNDARY` 拦** | `faq` | `[]` | ❌ 边界话术(**与截图同一段**) |
|
||||
| 2 | 我能买什么风险等级的产品? | 🔴 **被 `ADVICE_BOUNDARY` 拦** | `faq` | `[]` | ❌ 边界话术 |
|
||||
| 3 | 我适合买什么产品 | 🔴 **被 `ADVICE_BOUNDARY` 拦** | `faq` | `[]` | ❌ 边界话术(**本轮新发现**) |
|
||||
| 4 | 有哪些适合我的产品 | ✅ 放行 | `suitability_check` | `[]` | 🟡 落 `E1` 澄清「还没看出您指的是哪只产品」 ⇒ **仍答非所问** |
|
||||
| 5 | 我是 C1,能买 R3 的产品吗? | ✅ 放行 | `suitability_check` | `search_knowledge` | ✅ `E2c` 矩阵答对(**不可以购买 / 跨级禁止**) |
|
||||
| 6 | 我的风险等级是多少 | ✅ 放行 | `faq` | `query_customer_profile` | ✅ 答出「保守型(C1)」 |
|
||||
|
||||
**结论**:能力**已经存在**(第 5、6 条都答对了),是**开放式问法**被门禁误判成「要产品推荐」。
|
||||
|
||||
#### 二、根因定位(读码 + 离线判据复算,不猜)
|
||||
|
||||
**命中的那一条**(`PROMOTION_REQUEST_PATTERNS` 第 4 条):
|
||||
|
||||
```
|
||||
(买|投)[^。;!?,,]{0,4}(什么|哪[只支个种])[^。;!?,,]{0,6}(基金|产品|理财|好)
|
||||
```
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| **拦截点** | `app\core\customer_service_rules.py` 的 `route_message()` → `PROMOTION_REQUEST_PATTERNS` 命中 → 返回 `ADVICE_BOUNDARY_REPLY`(`ADVICE_BOUNDARY_PRIORITY` 分支) |
|
||||
| **命中跨度(离线复算)** | 「我现在可以买什么等级的产品」→ 命中子串 **「买什么等级的产品」**(「买」+0 字+「什么」+「等级」2 字 ≤ 6+「产品」);「我能买什么风险等级的产品?」→ **「买什么风险等级的产品」**(「风险等级」4 字 ≤ 6) |
|
||||
| **为什么是缺陷** | 该正则的 6 字窗口**不认限定词**:客户问的是「**等级范围**」(公开规则题),被判成「**要产品**」(推介请求)。**同一缺陷形态在 `G-01`(`CREDENTIAL_HELP_PATTERNS` 的 6 字窗口)已出现过一次** ⇒ 属**同类回归**,建议按「窗口型误拦」成类收口 |
|
||||
| **为什么没机会自救** | `route_message()` 是 `_route_and_answer()` 的**第一行**(先于 `is_risk_level_change_request` / `is_profile_question` / `_answer_calculation`)⇒ 门禁一旦命中,后面**本来能答**的路径**全部跑不到** |
|
||||
| **同类不一致(本轮新发现)** | 「我**适合**买什么产品」命中第 4 条 → 边界话术;语义等同的「**有哪些适合我的**产品」**不命中** → 放行(但落 `E1` 澄清)。**同一件事、两种句式、两种结果** |
|
||||
|
||||
#### 三、三层边界(本次争议的实质)
|
||||
|
||||
| 层 | 内容 | 能否作答 | 依据 |
|
||||
|---|---|---|---|
|
||||
| ① | 按你的等级,你能买**哪些风险等级**(C—R 匹配范围) | ✅ **本就该答** | `PROD-012` §4.2(**`public` 档**)明文:C1 保守型 **R1—R2**、C2 稳健型 R1—R3、C3 平衡型 R1—R4、C4/C5 R1—R5;该块自身已声明「本节只说明适当性匹配范围,**不含**资产配置比例,也不含收益目标或收益区间 —— 具体配置方案属投资建议,本系统的智能客服不提供此类内容」 |
|
||||
| ② | 帮客户**挑具体某只基金** / 按收益排序 / 说「这只更适合您」 | ❌ **红线** | 属投资建议;`ADVICE_BOUNDARY_REPLY` 拒绝的正是这一层 |
|
||||
| ③ | **现状** | ❌ **把 ① 一起挡了** | 误拦(false positive) |
|
||||
|
||||
#### 四、缺口不止在门禁:没有「本人等级 + 匹配矩阵」的出口
|
||||
|
||||
| 相关出口 | 触发输入要求 | 对「我的等级能买什么」的覆盖 |
|
||||
|---|---|---|
|
||||
| `E2c` 通用规则(`_answer_suitability_rule`) | 问句里**同时**出现客户等级与产品等级三件套(`is_general_suitability_question`) | ❌ 两者皆无 ⇒ **不触发** |
|
||||
| `_answer_suitability`(本人等级 vs 某只产品) | 需要**具体产品**(产品名只取上一轮主语) | ❌ 客户没提产品 ⇒ 落 `E1` 澄清(第 4 条实测即此) |
|
||||
| 画像出口(`is_profile_question`) | 「我的…是多少」 | 🟡 只答**自身等级**,**不套矩阵** |
|
||||
|
||||
⇒ 「**本人等级(读画像)+ 匹配矩阵(读知识库)**」这一组合**没有专属出口** —— 这是与门禁并列的**第二处缺口**。
|
||||
|
||||
#### 五、待决项(本轮不出手,等裁定)
|
||||
|
||||
| 编号 | 待决项 | 我的最优建议 |
|
||||
|---|---|---|
|
||||
| `DEC-W20-1` | 「限定词误拦」收口 | **修**:给第 4 条加**限定词排除** —— `什么` 与 `产品` 之间若夹「等级 / 风险等级 / 档位 / 类别 / 范围」则**不视为**推介请求;并把 `G-01` 的 6 字窗口按同一思路**一并复核** |
|
||||
| `DEC-W20-2` | 是否新增 `E2c-my` 出口(本人等级 → 匹配范围) | **新增**:`query_customer_profile` 取**权威 C 级**(**不采信客户自称**,与 `_answer_suitability` 同口径)→ 套矩阵 → 答「您当前 保守型(C1),可购买 R1—R2 等级的产品;R3 及以上不可购买」 |
|
||||
| `DEC-W20-3` | 出口颗粒度 | **两段式**:默认只答**等级范围** + 指路「产品中心按 R1/R2 筛选」;**客户显式要求后**再列**公开在售清单**(按代码排序、不按收益、不排序推荐、带免责声明) |
|
||||
| `DEC-W20-4` | 访客(未登录)问同一句 | 给**公开 C—R 规则表**(沿用 `DEC-I8`「通用规则对访客 open」的口径)+ 引导登录看本人等级 |
|
||||
| `DEC-W20-5` | 金标补例 | 加 3 条:①「我现在可以买什么等级的产品」(期望 `E2c-my`);②「我适合买什么产品」(期望 `E2c-my`,**不得**落 `E1`);③**回归钉子**「帮我推荐一只基金」(期望仍为 `ADVICE_BOUNDARY`,**必须仍拦**) |
|
||||
|
||||
#### 六、诚实声明
|
||||
|
||||
- 本轮**未改代码**、**未跑全量回归**、**未动任何文档版本位**(`D2.x` / `D3.x` 均不涉及)。
|
||||
- `DEC-W20-1` 若做得过宽(例如直接删掉第 4 条),会把「我能买什么产品」「帮我推荐一只基金」一起放行 ⇒ 属**安全回退**;`DEC-W20-5` 的第 ③ 条就是为此设的回归钉子。
|
||||
|
||||
## 5. 建议的开工顺序(在 `DEC-11` 拍板后)
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user