feat(W24): docs/43 场内基金手册入库 + B-02 展示候选集收口
补历史欠账:D3.4 B-02(决 12)与 D4.1 §B-02 早已承诺把 docs/43 纳入 SOURCES, 从未执行;W21 首次把它暴露成实测缺口(702 块里「科创债」0 处)。 语料与切片 - docs/43-场内基金产品手册(知识库入库版).md 纳入 SOURCES:20 只场内基金 (13 ETF + 7 LOF),fin_product_collection / public / prefix=ETF;切片件 702 -> 755 块 - 新增三个「按源开启」的切片开关(默认关,其它 8 个源零字节变化): strip_editorial_marks / qualify_table_rows / exclude_sections - 费率表表头补单位(%/年);docs/43 与 knowledge 镜像逐字一致 - docs/45 裁定不入库(与 FAQ-0018 / POL-AST-011/012 重复,会抢 top1) 修复两个真实缺陷 - W24-A 切片器行标签取错列:多列表格第 0 列是代码(159700[:2]='15'), 上游 _prefer_section 判不出重合 => 每一问都被换成整节块 (实测「科创债ETF南方怎么样」返回 20 只产品的整张表);改取表头「名称」列 - W24-B text-embedding-v3 单请求上限 10 条:load_knowledge_milvus.embed() 只在灌库路径分批,自检路径超 10 条即在数据写完后崩;分批下沉进 embed() - B-02 的 M-4 回归:_exit_partial 两条调用路径候选集不同(主路径 TopK 全量 vs 证据路径被 E4_MAX_EVIDENCE 截断)=> 同一句问句的答复质量取决于 E4 有没有 调用模型;_answer_from_evidence 新增 display_hits,展示候选一律用 TopK 全量, 送模型的证据包保持 6 块不变 产品名识别 - _PRODUCT_NAME_SHAPE 后缀集补 定期开放混合 / 股票(LOF)A / (LOF) / 原油A - 新增 _BRANDLESS_PRODUCT_NAMES(沪深300ETF,唯一无厂商字样的产品,只能枚举) - _strip_product_name_lead 增「先切掉前一只基金」 验收 - 金标 46 条 M-1 46/46、M-4 46/46、M-6 5/46、M-7/8/9/10 = 0、 M-2 28/31、M-2b 15/18、M-3 4/4(与 w23 基线逐项一致) - 两次独立复跑 result_w24d / result_w24e 指标完全相同 - Milvus 自检 15/15;全量回归 1996 passed / 3 skipped;ruff 零新增 - 真 HTTP 11 条全绿 判据变更(必须知情,不适用「零回归」表述) - B-01(访客)expected_evidence 补 ETF - B-05(客户,同一句问句)expected_exits [E3] -> [E3, E4] - load_knowledge_milvus 自检 BAS-CON-006 -> ETF-005 文档 - 客服agent/D2.1 升 v6.39;D2.4 升 v1.8(语料 755 块、手册切片实测 53); D2.8 新增 §3.6 与 §11 复测;D2.9 升 v1.3 - 开发文档/D1.1 升 v1.15(新增 §31);D1.6 升 v1.2(新增 §11、§10.4 销账); D4.8 升 v1.2(新增 §10) 诚实留痕 - _exit_partial 仍按分数挑块(不接收问句),本轮只统一候选集口径 - E5b 展示层净化仍不覆盖「收益 + 数字%」形态,继续挂账 - 语料里 2 个零容忍地雷块(POL-SPM-016 / POL-SPM-022-01)是禁令条款,故意保留
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# D4.8 · 客服 Agent 智能度体检与整改报告(`W21`)
|
||||
|
||||
> **体系编号**:`D4.8` · 域:四、重构与清除留痕
|
||||
> **编号**:CS-RPT-2026-024 | **版本**:v1.1 | **日期**:2026-09-21 | **状态**:现行
|
||||
> **编号**:CS-RPT-2026-024 | **版本**:v1.2 | **日期**:2026-09-21 | **状态**:现行
|
||||
> **性质**:**留痕报告**。回答甲方一句质疑 —— 「客服 Agent 不智能,很多问题强制转人工」。
|
||||
> 本文只做三件事:**取实测证据**、**定位根因**、**登记整改与未整改项**。不改需求、不立新任务。
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
**第二轮(同日)**:§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 条。
|
||||
|
||||
**第三轮(同日,`W24`)**:该缺口已**按甲方批准执行** —— `docs/43` 纳入 `SOURCES`、切片件 **702 → 755 块**、四集合 **drop → 重建 → 重灌**、自检 **15/15**。执行中发现并修复一个**切片器真实缺陷**(多列表格行标签取错列 ⇒ 客户问一只产品拿到 20 只的整张表)与一个**潜伏 bug**(`text-embedding-v3` 单请求上限 10 条,自检路径未分批 ⇒ 数据写完后崩)。收尾时另修一处 `B-02` 的 `M-4` 回归:`_exit_partial` 的两条调用路径**候选集不同**,导致**同一句问句的答复质量取决于「`E4` 这次有没有调用模型」**。完整实施与验收见 §10。
|
||||
|
||||
---
|
||||
|
||||
## 1. 待判物:`_chunks_report.txt` 能否删除 —— **判定:可删,已删并已加 `.gitignore`**
|
||||
@@ -324,7 +326,7 @@ E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS
|
||||
1. **打标口径变更**:`W20` 版把认不出的归「其它」(10 条盲区,逐条读原文发现 9 条其实是**正确作答**);`W22` 版翻转为"只识别退化形态"。两次分布**不可相减**,§2.3 已写明。
|
||||
2. **C-11 是"未修复"而不是"不需要修"**。它已被精确定位到代码行与触发条件,只差一个**合规口径的批准**(§6 第 1 项)。
|
||||
3. **本轮体检样本是本报告自己构造的**(81 条 + 8 组多轮),不是甲方给定的验收集。金标 46 条仍是**唯一**验收依据,体检集只用于**发现缺陷**。
|
||||
4. **代码里 4 条 ruff 告警是既有的**(`customer_service_rules.py:318/416`、`customer_service.py:34/1188` 的 E501 与 I001),不是本轮引入,也**未顺手改** —— 避免把无关改动混进本轮 diff。
|
||||
4. **代码里 4 条 ruff 告警是既有的**(`customer_service_rules.py:318/416`、`customer_service.py:34/1188` 的 E501 与 I001;`W21` 二轮后 E501 行号下移至 `:1220`),不是本轮引入,也**未顺手改** —— 避免把无关改动混进本轮 diff。(`W24` 另**引入过** 2 条 `tools/build_knowledge_chunks.py` 的 `B905`,已在本轮内改为 `zip(..., strict=True)` 清零,见 §10.5。)
|
||||
5. **真机复验依赖本地四依赖**(MySQL / Redis / Milvus / API+Worker)。复跑前须确认 `/internal/health/ready` 三依赖全绿,否则 `E5b` 会被误读成"缺陷"。
|
||||
6. **`C-9` 的产物 `_PRODUCT_NAME_SHAPE` 是形状匹配、不是产品名白名单**:语料里新增产品名只要仍符合「南方 + 名称 + 产品类型后缀」就能被认出;若将来出现**不符合该形状**的产品名(如纯英文名),②级降级会失效并落到③级(知识检索)—— 功能不减,只是拿不到裁决。
|
||||
|
||||
@@ -386,6 +388,96 @@ E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS
|
||||
|
||||
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)。
|
||||
3. ~~**`docs/43-场内基金产品手册` 未入库**(702 块里「科创债」0 处)~~ → **已于 `W24` 执行并验收**(`docs/43` 纳入 `SOURCES`,切片件 702 → 755 块,见 §10)。当时 `W21-D3` 的闸门只做到**止损**(不再贴错产品),**根本修复**需要把它纳入 `SOURCES` 并重建集合 —— 属**语料变更**,未在 `W21` 擅自执行,已登记为待决(`D1.6` §10.4)。
|
||||
4. **`E5b` 展示层仍是"原文直返"**:实测「买基金要手续费吗」的 `E5b` 分支正文里含「专户业绩报酬 | 按合同约定计提 | 超额收益的 10%—20%」,会被 `hits_zero_tolerance` 判 True(`收益`+数字% 陷阱)。**这是既有行为**(`drop_yield_claims` 只丢带收益指标名的行),治理层不对 `E5b` 正文做二次零容忍扫描,因此**不会拦单**;但**展示层净化是否要覆盖这一形态**,建议列入下一轮待决。
|
||||
5. **改动只落在 `客服agent/` 与 `开发文档/` 两份权威副本 + 仓库同名镜像**,三处一致(`D1.1` §权威声明)。
|
||||
|
||||
---
|
||||
|
||||
## 10. `W24` · `docs/43` 场内基金手册入库的实施与验收(2026-09-21)
|
||||
|
||||
> **性质**:**补历史欠账**。`D3.4` 的 `B-02`(决 12)与 `D4.1` §B-02 早已写明「新增 `docs/43`+`docs/45` 入 `SOURCES`」,**从未执行**;`W21` §9.5 第 3 条首次把它暴露成实测缺口。甲方 2026-09-21 批准「**先把语料修复吧 在进行下一步 不单独立项了**」⇒ 本轮执行。
|
||||
> **证据**:`_eval_harness/result_w24d.json` + `score_w24d.json`、`result_w24e.json` + `score_w24e.json`、`_w24_http.txt`(真 HTTP)、`knowledge/_chunks.jsonl`(755 块)。
|
||||
|
||||
### 10.1 落地清单(文件级)
|
||||
|
||||
| 文件 | 改动 |
|
||||
|---|---|
|
||||
| `tools/build_knowledge_chunks.py` | ① `SOURCES` 新增 `product/场内基金产品手册(知识库入库版).md`(`prefix=ETF` / `collection=fin_product_collection` / `visibility=public` / `version=V1.0` / `effective_date=2026-09-11` / `doc_no=JR-ETF-2026-001`);② 新增三个**按源开启**的开关 `strip_editorial_marks` / `qualify_table_rows` / `exclude_sections`(默认关);③ `expand_table_rows()` 签名加 `qualify`,新增 `cell_text()` / `row_content()` / `subject_of()`;④ **行级子块标签改取「名称」列**(缺陷修复,见 10.2) |
|
||||
| `tools/load_knowledge_milvus.py` | `embed()` **内部分批**(`MAX_BATCH = 10`)—— 修自检路径超 10 条即崩的潜伏 bug;自检期望 `BAS-CON-006` → `ETF-005`,并新增 5 条新源存在性证明(**自检 15/15**) |
|
||||
| `docs/43-场内基金产品手册(知识库入库版).md` + `knowledge/product/…md` | 费率表表头补「(%/年)」;两处**逐字一致**(有断言守着) |
|
||||
| `knowledge/_chunks.jsonl` | 702 → **755 块**(policy 288 / product 251 / faq 154 / basic 62) |
|
||||
| `app/service/agent/implementations/customer_service.py` | ① `_PRODUCT_NAME_SHAPE` 后缀集补 `定期开放混合\|股票\(LOF\)[ABC]?\|\(LOF\)\|原油[ABC]?`;② 新增 `_BRANDLESS_PRODUCT_NAMES`(`沪深300ETF`)与第三条 pattern;③ `_strip_product_name_lead` 增「先切掉前一只基金」;④ `_answer_from_evidence` 新增 `display_hits` 形参,四处 `_exit_partial` 改传**展示候选集** |
|
||||
| `tests/unit/service/test_customer_service_agent.py` | **+2 条守卫**:20 只产品名全覆盖(语料驱动)、无厂商字样产品**不**在类目问句上开门 |
|
||||
| `_eval_harness/cases_46.json` | `B-01` 证据期望补 `ETF`;`B-05` 出口期望补 `E4`(见 10.3) |
|
||||
| `客服agent/D2.1` | 升 `v6.39` |
|
||||
| `客服agent/D2.4` / `D2.8` / `D2.9` | 语料口径 675 → **755**;`D2.4` §4.4 场内基金手册切片预估「约 30—50」→ 实测 **53** |
|
||||
| `开发文档/D1.1` / `D1.6` | 索引口径同步;`D1.6` 新增 §11,§10.4 标「已执行」、§10.6 第 1 条销账 |
|
||||
| 本文件 | 新增 §10;§0 / §8 / §9.5 同步 |
|
||||
|
||||
### 10.2 执行中发现并修复的两个真实缺陷
|
||||
|
||||
| 编号 | 缺陷 | 现象(修复前) | 根因 | 修法 |
|
||||
|---|---|---|---|---|
|
||||
| **`W24-A`** | 🔴 **切片器:行级子块标签取错列** | 「科创债ETF南方怎么样」返回 **20 只产品的整张表**(真 HTTP + 进程内**双重**复现) | 上游 `_prefer_section` 用子块 `title` 末段与问句比对;多列表格**第 0 列是代码**(`159700[:2]` = `15`),**永不重合** ⇒ 每一问都被换成整节块 | `qualify` 模式下标签改取表头里含**「名称」**的那一列(两张表都有该列),无则退回第 0 列 |
|
||||
| **`W24-B`** | 🔴 **`embed()` 自检路径未分批** | `CHECKS` 超过 10 条时,**数据已全部写完之后**崩(本次 14 条即触发) | `text-embedding-v3` **单请求最多 10 条**;原实现只有灌库路径分批 | 分批下沉进 `embed()`(`MAX_BATCH = 10`),两条路径同时受益 |
|
||||
|
||||
**另修一处 `M-4` 回归(`B-02`)**:
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 现象 | `B-02`「基金申购和赎回有哪些费率?」(访客)在 `w24c` 由 `E5b` 展示 `POL-SPM-033-01`(**单行「赎回费率:<7 天 1.50%」**)⇒ **只答一半**,`M-4` 失败 |
|
||||
| 根因 | `_exit_partial` 的两条调用路径**候选集不同**:主路径 `_answer_from_knowledge` 传 **TopK 全量**(能挑到 `PROD-016` 费率总表),证据路径 `_answer_from_evidence` 传**被 `E4_MAX_EVIDENCE = 6` 截断的证据包**。新语料把 `ETF-009` 插进 TopK,把 `PROD-016` 从第 9 名挤到第 10 名,"在包内"变"包外" |
|
||||
| 为什么这是缺陷 | **同一句问句的答复质量取决于「`E4` 这次有没有调用模型」** —— 两条路径只差"模型这一跳成没成",不该差在客户看到的内容质量上 |
|
||||
| 修法 | `_answer_from_evidence` 新增 `display_hits` 形参(调用点把 `hits` 传进来),四处 `_exit_partial` 改传该值;`_exit_partial` 内部仍挑最高分、仍用 `PARTIAL_FLOOR` 兜底。**送模型的证据包保持 6 块不变**(那是模型注意力问题,不是展示问题) |
|
||||
| 为什么不顺手"按问句产品名优先挑块" | 那会改 `E5b` 的**挑选语义**(从"分数最高"变成"与问句主体一致优先"),影响面覆盖全部 `E5b`,需单独回归;本轮只做**候选集口径统一**这一最小改动(`D1.6` §10.6 第 3 条仍**部分未收口**) |
|
||||
|
||||
### 10.3 期望值登记修订(**判据变更,必须知情**)
|
||||
|
||||
| 项 | 原期望 | 修订后 | 理由 |
|
||||
|---|---|---|---|
|
||||
| `B-01`(**访客**,问句见 `cases_46.json`) | `expected_evidence = [PROD, FAQ]` | `[PROD, FAQ, ETF]` | 新语料让 `ETF-013`(0.7954)**合法**拿到 top1,`PROD-001-05`(0.7527)退到 #2;两者都答得到,**答案更完整**。已写 `evidence_note` |
|
||||
| `B-05`(**客户**,与 `B-01` **同一句问句**) | `expected_exits = [E3]` | `[E3, E4]` | 与 top1 `FAQ-0020`(0.8286)只差 **0.033 < 0.07** ⇒ 走「并列 → 生成式作答」。已写 `exit_note` |
|
||||
| `tools/load_knowledge_milvus.py` 自检 | `("场内基金和场外基金有什么区别", "BAS-CON-006")` | `("…", "ETF-005")` | `ETF-005` 0.861 vs `BAS-CON-006` 0.789,**更贴题**;已写明「期望值是**语料版本的函数**」 |
|
||||
|
||||
> ⚠️ 因此本轮**不适用**「零回归」表述。可复算的准确说法是:**在三条期望登记修订之后**,金标 `M-1`/`M-4` 均为 46/46。
|
||||
|
||||
### 10.4 验收(可复算)
|
||||
|
||||
| 项 | 结果 |
|
||||
|---|---|
|
||||
| 金标 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` —— **与 `w23` 基线逐项一致** |
|
||||
| **两次独立复跑** | `result_w24d` 与 `result_w24e` **指标完全相同**(本仓库有"只跑一次就宣布修好是假绿"的历史教训,`D1` 类改动必须重复采样) |
|
||||
| Milvus 自检 | **15/15**;切片件 755 = 四集合合计(**只信 `_chunks_report.txt` 的块数** —— `upsert` 会让 `row_count` 翻倍) |
|
||||
| 全量单元 / 集成回归 | **`1996 passed / 3 skipped`**(`W21` 二轮 `1994`,**+2 条新守卫全绿**) |
|
||||
| `ruff` | **零新增**:`customer_service.py:34` I001、`:1220` E501 等**均为既有**;本轮曾引入 2 条 `B905`,已当场清零 |
|
||||
| 切片件幂等 | `strict=True` 重构后重跑 `build_knowledge_chunks` ⇒ `knowledge/_chunks.jsonl` **SHA256 逐字节相同**(无须重灌) |
|
||||
| 真 HTTP | 见 10.5 |
|
||||
|
||||
### 10.5 真 HTTP 复验(`cust_t` / 访客,`POST /api/v1/agent-runs`)
|
||||
|
||||
| # | 问句 | 结果 |
|
||||
|---|---|---|
|
||||
| 1 | 南方金利定开债券A的费率是多少? | 单行费率 **带单位**:管理费率(%/年)0.50 / 托管费率(%/年)0.15 / 销售服务费率 无 |
|
||||
| 2 | 科创债ETF南方怎么样 | **单只产品**(代码 159700 / 深交所 / ETF / **R2** / 净值 101.8009)—— 修复前是 20 只的整张表 |
|
||||
| 3 | 场内基金有哪些 | 20 只清单 |
|
||||
| 4 | 场内基金和场外基金有什么区别 | `ETF-005`:场内按市价撮合 / 场外按净值申赎,T+1 确认 |
|
||||
| 5 | 南方现金添利怎么样 | `E4` 生成作答(R1 / 起投 1 元 / 费率 / 申赎规则),**零收益数值** |
|
||||
| 6 | 买基金要手续费吗 | `E4` 合并作答(交易 + 持有 + 赎回费 + 线上优惠) |
|
||||
| 7 | 赎回费怎么算 → 持有 8 个月呢 | `E5b` → `E5b`:**两轮都给出完整分档表**(`B-02` 同族修复的连带确认) |
|
||||
| 8 | 我想买个债基 → 它适合我吗 | 指代继承 + **真实适当性裁决**(R2 vs C1 + 揭示书 + 双录);**未替客户确认** |
|
||||
| 9 | 科创债ETF南方(**访客**) | 与客户档同答(`public` 档);**无档位越权** |
|
||||
|
||||
### 10.6 本轮裁定:`docs/45` **不入库**
|
||||
|
||||
`docs/45`(R1—R5 问答)与 `FAQ-0018`、`POL-AST-011` / `POL-AST-012` **高度重复**;单独成块会在「R1 到 R5 分别代表什么」这类问句上**抢走 top1**(该题 Top1 与次优原本只差 0.021,属已知的阈值敏感区)。**裁定:不纳入 `SOURCES`**;历史承诺里的 `docs/45` 部分**据此关闭**。
|
||||
|
||||
### 10.7 诚实留痕(本轮新增,别漏)
|
||||
|
||||
1. **本条是"补历史欠账",不是新增需求**:`D3.4 B-02`(决 12)与 `D4.1 §B-02` 早已承诺,从未执行。
|
||||
2. **本轮有 3 条期望修订**(10.3),"零回归"不适用。
|
||||
3. `_exit_partial` **仍按分数挑块**,不接收问句 ⇒ 「问句点名了产品 X,但第 1 名的近邻块更贴题」这一类**仍未收口**(`D1.6` §10.6 第 3 条,本轮只统一了候选集口径)。
|
||||
4. **`E5b` 展示层净化仍不覆盖「收益 + 数字%」形态**(`W21` §9.5 第 4 条)—— 本轮**未动**,继续挂账。
|
||||
5. 语料里仍有 **2 个零容忍地雷块**(`POL-SPM-016` / `POL-SPM-022-01`),是**禁令条款**,**故意保留**,不是缺陷。
|
||||
6. **`upsert` 会让 `get_collection_stats().row_count` 翻倍**(实测 450 vs 251,`doc_id` 仍唯一)⇒ **只信 `_chunks_report.txt` 的块数**;改 `doc_id` 编号后**必须丢集合重建**(本轮重编号 `ETF-009`/`ETF-010` 时留过一条陈旧行)。
|
||||
7. 改动只落在 `客服agent/` 与 `开发文档/` 两份**权威副本** + 仓库同名**镜像**,三处一致(`D1.1` §权威声明)。
|
||||
|
||||
Reference in New Issue
Block a user