# D4.8 · 客服 Agent 智能度体检与整改报告(`W21`) > **体系编号**:`D4.8` · 域:四、重构与清除留痕 > **编号**:CS-RPT-2026-024 | **版本**:v1.3 | **日期**:2026-09-21 | **状态**:现行 > **性质**:**留痕报告**。回答甲方一句质疑 —— 「客服 Agent 不智能,很多问题强制转人工」。 > 本文只做三件事:**取实测证据**、**定位根因**、**登记整改与未整改项**。不改需求、不立新任务。 --- ## 0. 一句话结论 本轮用 **81 条真实口语问法 + 8 组多轮追问**(真 HTTP,非进程内)打了一遍现有实现,**定位并修复了 3 类真实缺陷、登记了 1 类需甲方裁定的合规口径问题**。修完之后: - **金标 46 条**:`M-1 出口 46/46`、`M-4 事实 46/46`、`M-6 转人工 5/46`(白名单 5 条,**零越界**)、`M-7/M-8/M-9/M-10 = 0`、`M-2 28/31`、`M-2b 15/18`、`M-3 4/4` —— 与 `W20` 基线**逐项一致,零回归**。 - **全量单元/集成回归**:`1985 passed / 3 skipped`(`W20` 基线 `1969 passed / 3 skipped`,**新增 16 条守卫全部通过**)。 - **智能度体检**:退化形态由 `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 条。 **第三轮(同日,`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`** | 判据 | 结论 | |---|---| | 是不是源码依赖? | 不是。全仓库 `rg` 无任何模块 import / 读取该文件 | | 内容能否复算? | 能。它是 `tools/build_knowledge_chunks.py` 的**控制台输出落盘稿**,每一个数字都能从已入库的 `knowledge/_chunks.jsonl` 重新算出 | | 是否入库(git 追踪)? | 否。`git ls-files` 无记录,`git ls-remote` 侧同样无 | | 删掉会不会丢信息? | 不会。同一份统计口径已写进 `D2.4` §6 / `D2.8` §2,语料条数守卫由 `tools/build_knowledge_chunks.py` 的 `FAQ_EXPECTED_COUNT` 承担 | **动作**:删除文件 + 在 `.gitignore` 末尾追加排除项,避免它再次被生成后误入库。 ``` # tools/build_knowledge_chunks.py 的输出副产物(可由 knowledge/_chunks.jsonl 复算,不入库) _chunks_report.txt ``` > 留痕口径:这是**工具产物**,不是项目内容。同类处理见 `.gitignore` 里已有的 `.workbuddy/`(AI 助手会话记忆)。 --- ## 2. 体检方法(可复现) ### 2.1 为什么要用"真 HTTP"而不是进程内 harness 进程内金标 harness(`_eval_harness/probe.py`)测的是**判定链**(出口 / 检索 / 事实),它跳过了 **API → 队列 → Worker → 治理层** 这一段。而"不智能"的体感恰恰主要来自那一段:队列里重算 `chitchat_streak`、治理层追加免责声明、输出侧红线二次校验。 因此体检走**真 HTTP**(`POST /api/v1/agent-runs` + 轮询 `GET /api/v1/agent-runs/{id}`),以**客户档**(种子账号 `cust_t`)提问,与金标 harness **互补而非替代**: - 真 HTTP 测不到的(检索命中明细 / `M-2` / `M-3` / `M-5`)**不假判**,仍以进程内 harness 为准; - 进程内测不到的(治理层替换 / 免责声明 / 队列重算)以真 HTTP 为准。 ### 2.2 样本构成 | 类别 | 条数 | 说明 | |---|---|---| | 单轮口语问法 | 65 | 概念题 / 时效题 / 产品题 / 账户题 / 闲聊 / 边界题 / 反诈题全覆盖 | | 多轮追问链 | 8 组 16 轮 | 指代(「它适合我吗」)/ 参数追问(「持有 8 个月呢」)/ 类目切换(「货币基金呢」)/ 主语省略(「最低多少钱」) | | **合计** | **81 条** | —— | ### 2.3 打标口径(**必须先读,否则会误读成"退步"**) 打标器**只识别"退化的形态"**,认不出来的一律计为「作答」: | 标 | 含义 | 是否退化 | |---|---|---| | `作答` | 有实质回答(直答 / 生成 / 计算 / 适当性 / 闲聊) | 否 | | `E5b部分答` | 「关于这一点,公开资料里的口径是:…」(`W21-D4` 前为「我先帮您把找到的公开资料放上来…」)——贴了资料,未必贴对 | 是 | | `E5b空答` | 「我暂时没找到对应的公开资料」 | 是(但诚实) | | `澄清` | 「您想了解的是下面哪一项呢?」 | 是 | | `转人工` | 建单或走人工话术 | 是 | | `P1账户` / `P0反诈` / `合规拒答` / `推介边界` / `引导登录` | **设计如此**,是能力边界而非退化 | 否(但计入"非作答") | ⚠️ **`W20` 版打标器口径不同**:它把认不出的归为「其它」(`W20` 有 10 条盲区,逐条读原文发现 9 条其实是**有实质内容的作答**)。因此「`W20` 的 25 条退化」与「`W22` 的 16 条退化」**不能直接相减**,两者分母与标类都不同。 ### 2.4 `W22` 结果分布(81 条) | 标 | 条数 | |---|---| | 作答 | **65** | | E5b部分答 | 6 | | 澄清 | 3 | | P1账户 | 2 | | 转人工 | 2 | | 合规拒答 | 2 | | 推介边界 | 1 | | E5b空答 | 0 | --- ## 3. 本轮定位并**已修复**的缺陷(3 类) ### C-8 · 🔴 账户盈亏问法落进"误导性澄清" **现象**(实测):`我的基金赚了多少钱` → `E1` 澄清,三个候选是 ``` 1. 1.1 南方现金添利货币市场基金〔示例〕 · 万份收益 2. 各类基金的起投金额分别是多少? 3. 为什么不同产品的收益率差别这么大? ``` **根因**:`P1_PATTERNS` 第 2 条要求「第一人称 + **收益** + 疑问词」,而客户说的是「**赚了**多少」—— 字面不同、语义同一;`ACCOUNT_DATA_PATTERNS` 也不覆盖盈亏金额。 **为什么这比"答不上来"更糟**:三个候选**全是公开知识**,客户问的是**自己账户的盈亏金额**(Agent 无权读取)。让客户在三件不相干的事里挑一件,客户挑完拿到的仍是答非所问 —— 一个"看似有回应"的问题掩盖了能力边界。 **修法**(`app/core/customer_service_rules.py` · `P1_PATTERNS` 第 9 条):补「第一人称 + 盈亏动词 + **金额疑问词**」骨架,**金额疑问词是必要条件**。 **修复后**: ``` Q: 我的基金赚了多少钱 intent=transfer_human transfer=False A: 抱歉,当前智能客服无法读取本人账户数据(如持仓、收益、订单、银行卡号或投诉进度), 因此不能在这里为您查询或核对。您可以登录后在「我的账户」页面自助查看, 或拨打官方客服电话 400-889-8899(每日 7:00—22:00)由人工协助处理。 ``` **反向守卫**(判据必须窄,否则自废能力):`我买的基金亏了怎么办` 问的是**怎么办**、不含金额词 ⇒ **照旧走知识检索**(库里确有答案:净值型产品不保本 + 赎回流程 + 到账时限),实测答复正确。 --- ### C-9 · 🔴 多轮指代断链,把"已经说过的"又问一遍 **现象**(实测,两条独立链路): | 链路 | 修复前 | 修复后 | |---|---|---| | `我想买个债基` → `它适合我吗` | `E2d` 澄清:「…我还没看出您指的是哪只产品。请告诉我具体的基金名称或代码」 | **真实适当性裁决**:`check_suitability` 被调用,答「南方稳健增利债券 A 为 R2…您当前的风险测评等级为 C1…购买前需签署产品风险揭示书…本次购买需双录」 | | `赎回费怎么算` → `持有 8 个月呢` | 同上(也被误判成适当性题 → 又走同一条死胡同) | 正确作答:「持有 8 个月(即 30—365 天区间)的赎回费率…」 | **两个根因**(不同,必须分开治): 1. **主语反解取不到**。`_topic_of` 只在答复**前 4 行的固定形状**里取主语(`X:…` / `### 2.1 X` / `…为 R2`)。上一轮是 **FAQ 型答复**(首行是「问:…」)时反解为**空**,本出口于是回一句"请告诉我具体的基金名称或代码"—— 而客户**刚刚**才被告知是那只产品。 2. **意图分类把短追问标成 `suitability_check`**,于是绕开了知识出口里已经做好的「追问继承」逻辑(`_search_query` 的补主语 / 补谓词分支根本轮不到执行)。 **修法**(`app/service/agent/implementations/customer_service.py` · `_answer_suitability`)—— 建成一条**四级降级链**,每级都比上一级更有信息量,且**都不越权**: | 级 | 判据 | 动作 | |---|---|---| | ① 严格主语 | `_previous_topic`(既有逻辑,来自知识块字段,**可信**) | 走适当性裁决 | | ② 形状反解 | 新增 `_product_name_in_history`:在上一轮答复前 6 行里按**「南方 + 名称 + 产品类型后缀」**认产品名 | 走适当性裁决 | | ③ 有上文无主语 | 新增 `_suitability_followup_fallback`:`history` 非空即交回知识检索 | 知识作答(答不上还有 `E5b`) | | ④ 首轮无指代 | `history` 为空 | 保留 `E2d` 澄清(这正是金标 `E-01` 的口径) | 外加一道**前置闸门**:**纯参数追问**(`持有 8 个月呢` / `1 万块呢`,正则见 `_PARAM_FOLLOWUP_PATTERNS`)**无条件**交回知识检索 —— 它从来不是适当性问题。 **安全上不放宽的三条**: - 本出口**从不给**"能不能买"的结论给访客;访客在更早的分支就被引导登录,降级链够不着。 - ②级抽出的名字若**查不到风险等级**,**不**回"我给不出结论",而是回落到知识检索 —— **绝不因一次推测**降级成 `E5b`。 - ③级的 `_previous_topic` 为空时的澄清**保留**:首轮「它费率多少?」没有指代对象,问清才是对的。 **为什么不能只做「删掉澄清、一律检索」**:那会把首轮指代也放行,客户问「它费率多少?」会拿到一段与"它"无关的任意资料 —— 从"答不上来"变成"答错"。 --- ### C-10 · 🔴 「你们投诉电话是多少」被强制转人工 **现象**(实测):`你们的投诉电话是多少` → **`P2` 建单转人工**,客户拿到的是一段「这件事需要人工为您办理」。 **根因**:`P2_WRITE_DISPUTE_KEYWORDS` 里有一个**裸词**「投诉」。该词表是**关键词子串匹配**,于是"问投诉渠道"和"提交投诉"撞在同一个判据上。 **为什么这是"只会转人工"的典型成因**:正确答案**就在库里** —— - `POL-SPM-036-01`(第十八条 投诉渠道 · 电话投诉) - `FAQ-0054`(公司有哪些投诉渠道?处理时限是多久?) **知道投诉电话是公开信息,受理投诉才是人工的事。** 两者混为一谈,就把一条**能答**的问句推给了人工。 **修法**(`customer_service_rules.py`):新增 `P2_CONTACT_INQUIRY_PATTERNS` + `_is_p2_contact_inquiry()`,与既有 `_is_p2_self_service_question`(「怎么修改绑定的银行卡」)**并列成第二类豁免**: - 判据一:渠道词(电话 / 热线 / 号码 / 邮箱 / 联系方式 / 渠道 / 地址)+ 疑问词; - 判据二:「投诉 / 客服 / 服务 / 人工」+ 渠道词同现; - **并要求不带明确投诉意图**(`P2_COMPLAINT_INTENT_MARKERS`:我要投诉 / 投诉你们 / 要投诉…)。 **修复后**: ``` Q: 你们的投诉电话是多少 intent=faq transfer=False A: 第十八条 投诉渠道:电话投诉 客服热线:400-889-8899(每日 7:00—22:00) Q: 我要投诉,让你们经理来找我 ← 能力不减 intent=transfer_human transfer=True A: 这件事需要人工为您办理。… ``` **回归守卫**:`我要投诉` / `投诉你们客服` / `我要投诉你们客服电话打不通` **全部照旧建单**(后者句子里有"电话",但带明确投诉意图词 ⇒ 不放行)。 --- ## 4. 定位但**未修复**的缺陷(1 类)—— 需甲方裁定 ### C-11 · 🟡 `E4` 证据约束生成的答复被"收益数值"闸门整条拦回 `E5b` **现象**(实测,三条同因):`南方现金添利怎么样`(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。 **根因链**(逐环取证): ``` E4 生成稿里出现「收益率 / 七日年化」等 YIELD_METRIC_TERMS → hits_zero_tolerance(answer) 判 True → _exit_partial(evidence, note="证据约束生成未过合规校验") → 客户看到 E5b 兜底话术(而 E5b 展示的**恰恰是同一批内容**) ``` **为什么没有直接改**:试过把"合规判定"挪到"展示层净化"**之后**(即先 `drop_yield_claims` 再判),实测**更差** —— `drop_yield_claims` 是**整行丢弃**,而生成稿常是**单行长段**,于是三条里有两条变成"内容全部为收益数值"⇒ **整条被删空**,客户体验反而下降。改动已回退,并把原因写进代码注释留痕。 **这为什么是"决策"而不是"缺陷"**:`D2.2` / `D2.4` 的既定口径是「收益数值属不得输出内容」。E4 生成稿里引用**产品卡上的收益数字**算不算"输出收益数值",是**合规口径问题**,不该由代码单方面放宽。三个可选方案与我的建议见 §6 第 3 项。 --- ## 5. 验收结果 ### 5.1 金标 46 条(进程内 harness,`_eval_harness/score_w22c.json`) | 指标 | `W20` 基线 | `W22` 本轮 | 判定 | |---|---|---|---| | `M-1` 出口准确率 | 46/46 = 100.0% | **46/46 = 100.0%** | ✅ 持平 | | `M-2` Top1 命中率 | 28/31 = 90.3% | **28/31 = 90.3%** | ✅ 持平 | | `M-2b` 难例命中率 | 15/18 = 83.3% | **15/18 = 83.3%** | ✅ 持平 | | `M-3` 证据召回率 | 4/4 | **4/4** | ✅ 持平 | | `M-4` 事实正确率 | 46/46 = 100.0% | **46/46 = 100.0%** | ✅ 持平 | | `M-5` 引用不可解析数 | 0 | **0** | ✅ 持平 | | `M-6` 转人工率 | 5/46 = 10.9% | **5/46 = 10.9%** | ✅ 白名单 5 条,**未新增** | | `M-7/M-8/M-9/M-10` | 0/0/0/0 | **0/0/0/0** | ✅ 持平 | 转人工白名单(逐条未变):`F-05` / `G-01` / `G-03` / `G-04` / `G-05`。 ### 5.2 全量回归 `1985 passed / 3 skipped`(`W20` 基线 `1969 passed / 3 skipped`)。新增守卫 **16 条**,分布: | 文件 | 新增 | 守什么 | |---|---|---| | `tests/unit/core/test_customer_service_rules.py` | 6 | 风险等级红线不被产品名误触发(`W21` 首轮) | | `tests/unit/service/test_customer_service_agent.py` | 8 | 主语反解 / 追问继承 / 常识补位闸门 / E5b 相关性闸门 + 本轮 **C-9 四条** | | `tests/unit/service/test_customer_service_red_lines.py` | 4 | 风险等级反例 + 本轮 **C-8 两条 / C-10 两条** | ### 5.3 真机 HTTP 复验(`_w20_evidence/_w22_final.txt`) | # | 问句 | 修复前 | 修复后 | |---|---|---|---| | 1 | 你们的投诉电话是多少 | **转人工** | 答出 400-889-8899 + 7:00—22:00 | | 2 | 我的基金赚了多少钱 | 误导性澄清(万份收益 / 起投金额) | `P1` 如实告知 + 自助入口 | | 3 | 帮我看看我适合啥 | 澄清("请告诉我具体基金名称") | `E2c-my` 按本人 C1 列 **4 只**可买产品 | | 4 | 有风险低的吗 | 治理层替换(零容忍误伤) | 正常作答(R1 / R2 类型 + C—R 矩阵) | | 5 | 我想买个债基 → 它适合我吗 | 澄清 | **真实适当性裁决** | | 6 | 赎回费怎么算 → 持有 8 个月呢 | 澄清 | 正确答出 30—365 天档位费率 | | 7 | 我要投诉,让你们经理来找我 | 转人工(正确) | 转人工(**保持不变**) | --- ## 6. 需甲方决策的事项(附建议)—— 🔵 **四项已于 2026-09-21 全部裁定并落地** ### 第 1 项 · 零容忍词的**输出侧**口径(最高优先) **背景**:甲方明确提过"零容忍词会让转人工/兜底频率大增"。本轮实测**定位到具体的一处**:不是输入侧(客户问句),而是 **E4 生成稿的输出侧**(`YIELD_METRIC_TERMS`:收益率 / 年化)。 **三个可选方案**: | 方案 | 做法 | 影响面 | 我的建议 | |---|---|---|---| | **A(推荐)** | 改 **E4 提示词**:明确禁止生成稿出现任何收益数值 / 收益率 / 年化数字 | 只动提示词与一句话,**不动红线代码**;命中率提升最大 | ⭐ **推荐** | | B | 把 `_YIELD_NUMBER_*` 从"整行丢弃"改成"**就地替换为『(收益数值已按合规要求隐去)』**" | 动展示层公共函数,`E3` 直返同样受影响 | 可行但影响面大 | | C | 维持现状 | 三条问句继续走 `E5b` 兜底 | 不作为 | **我建议 A 的理由**:问题的成因在"模型不该写",不在"闸门不该拦"。改提示词是**在源头消除**,红线代码与单测一条都不用动;而且是**可回归**的(复跑那三条问句即可验证)。 **裁定(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)帮您核对"** —— 让客户看得见指代依据,错了也能立刻纠正。这比反问更省客户一步。 ### 第 3 项 · 知识库里**没有**的产品被问到时的回答 **背景**:`科创债ETF南方怎么样` 的 top1 是 **南方稳健增利债券 A 的产品卡**(0.6696)—— 客户问 A,拿到的却是 B 的资料。这是"答非所问",比"没找到"更伤。 **裁定(2026-09-21)**:**加**,见 §9。落地时补了两条**判据收窄**(都不是原建议里写的): ① 产品名的**两种真实写法**都要认 —— 除「南方 + 名称 + 产品类型后缀」外,还要认「名称 + `ETF南方`」(场内基金手册里 10 只 ETF 全是品牌在后的写法); ② 正则**没有左边界**会把动词吞进产品名(「我想买科创债ETF南方」),而**吞进来会造出假「没找到」** —— 加 `_strip_product_name_lead` 逐字剥离前缀功能字。 **我的建议**:**加**。理由:金融场景下"诚实说没找到"优于"贴一段别的产品的资料";且这条闸门已有同源实现(`_hits_share_terms`),改动面很小。 ### 第 4 项 · `E5b`「我先帮您把找到的公开资料放上来」的措辞 **背景**:`E5b` 是本轮退化条里最大的一类(6/16)。它的**内容往往是对的**(如「债基和货基哪个收益高」贴出的 FAQ 正文就是标准答案),但**开场白的语气**让它显得像兜底。 **裁定(2026-09-21)**:**改**,见 §9。 **我的建议**:**改**。这是**零风险**的改动(只动模板字符串),但对"看起来智不智能"的体感影响最大 —— 同样一段内容,开场白不同,客户对"这个客服会不会答"的判断完全不同。 --- ## 7. 本轮改动清单(文件级) | 文件 | 改动 | |---|---| | `app/core/customer_service_rules.py` | `P1_PATTERNS` + C-8 账户盈亏骨架;新增 `P2_CONTACT_INQUIRY_PATTERNS` / `P2_COMPLAINT_INTENT_MARKERS` / `_is_p2_contact_inquiry()`;`route_message` 的 P2 分支接入第二类豁免并同步文档串 | | `app/service/agent/implementations/customer_service.py` | 新增 `_PRODUCT_NAME_SHAPE` / `_product_name_in_history()` / `_suitability_followup_fallback()`;`_answer_suitability` 改为四级降级链 + 纯参数追问前置闸门;C-11 未修复原因写入 E4 分支注释留痕 | | `tests/unit/service/test_customer_service_agent.py` | +4(C-9 守卫) | | `tests/unit/service/test_customer_service_red_lines.py` | +4(C-8 / C-10 守卫) | | `.gitignore` | +`_chunks_report.txt` | | `客服agent/D2.1-…Todolist.md` | 升 `v6.37`,登记本轮修订 | | `开发文档/D1.1-文档索引与权威声明.md` | 升 `v1.13`,登记 `D4.8` 与本轮版本位移 | > `W21` 首轮已完成的部分(7 类缺陷 C-1~C-7、语料补强 4 组 FAQ / 3 个 basic 章节 / 1 个 product 章节、`FAQ_EXPECTED_COUNT 65→69`、集合条数 `basic 105 / product 398 / faq 300 / policy 576`)见 `客服agent\D2.1` 的 `v6.37` 条目,不在此重复。 > > ⚠️ **口径更正(2026-09-21 补注)**:上面那组「集合条数」`105 / 398 / 300 / 576` **不是存活行数** —— 它是 `upsert` 留下的**墓碑行被计入 `get_collection_stats().row_count`** 的结果(同 `D2.1` `v6.9` 条目:「重灌后脚本打印 298 / 576 / 382 = 存活行 149/288/191 的**两倍**」)。**判据**:按 `doc_id` 查单条只返回 1 行。**本轮(`W24`)复测的存活行数是 `product 251 / faq 154 / policy 288 / basic 62 = 755`**(`docs\evidence\knowledge-collections.json`),与 `knowledge\_chunks.jsonl` **逐集合一致**。**引用块数时一律用 `_chunks.jsonl` / `_chunks_report.txt`,不要用 `row_count`。** --- ## 8. 诚实留痕(别漏) 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;`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` 是形状匹配、不是产品名白名单**:语料里新增产品名只要仍符合「南方 + 名称 + 产品类型后缀」就能被认出;若将来出现**不符合该形状**的产品名(如纯英文名),②级降级会失效并落到③级(知识检索)—— 功能不减,只是拿不到裁决。 --- ## 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 处)~~ → **已于 `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` §权威声明)。 --- ## 11. `W25` · 手动测试用例按实测重建 + 展示层误删真缺陷修复(2026-09-21) > **本轮做什么**:用户要求「**按照所有的问题帮我更新测试用例 我要看 我要依据测试用例去演示**」⇒ 用**当前实现**把 `D2.9` 的 46 条金标(含多轮)+ 11 条边界 + 安全 4 条 + 场内基金 5 条**全部重跑真 HTTP**,逐条回填**客户可见的答复原文**;过程中发现并修掉一处**展示层净化误删答复正文**的真缺陷。 > **性质**:**取证 + 回填 + 顺手修复**,不新增需求、不新增任务号。 ### 11.1 落地清单(文件级) | 文件 | 改动 | |---|---| | `_w25_http_manual.py`(新增,工作区根,**不入库**) | 一次跑完 46 金标(含 `chain` 多轮,同会话)+ 11 条 `Z` + 安全 4 条 + 场内基金 5 条 ⇒ `_w25_http_manual.json` / `.txt`(答复**全文** + `intent` / `transfer_required` / 工具 / 耗时) | | `app/service/agent/implementations/customer_service.py` | `drop_yield_claims()` 增加**业绩基准权重豁免**:收益词与百分比之间出现 `×` / `✕` / `✖` / `*` / `乘` / `指数` ⇒ 判为「业绩基准公式」,不算收益数值;抽出 `_is_yield_claim()` 承载判据 | | `tests/unit/service/test_customer_service_agent.py` | 新增 `test_drop_yield_claims_keeps_the_benchmark_formula_but_drops_both_word_orders`(公式**保留** + 两种语序的真实收益数值**照删**,一次钉两个方向) | | `_eval_harness/result_w25.json`、`score_w25.json`(工作区根,不入库) | 46 条金标的出口与检索命中明细、十项指标判分 | | `客服agent/D2.9-…md`(**v1.3 → v1.4**)+ 仓库镜像 | §2 每条金标「基线」列 → **本轮实测(出口 · top1)**;**每组表格后追加该组逐条实测答复原文**(46 条全量);§3 / §4 / §1.3 / §5 实测列刷新;**新增 §2.10 场内基金演示线**;§3.1 由「三个缺口」扩为**四个**(新增 ④) | | `客服agent/D2.5-…md` §4.7 + 仓库镜像 | **出口口径更正**:场内基金演示线第 1 条 `E3 → E4`、第 2 条 `E3 → E5b`(内容是逐字正确的,只是套了「公开资料里的口径是」这句开场白);第 3 / 4 条确为 `E3`、第 5 条确为 `E4` | ### 11.2 本轮修复的真实缺陷(`W25-A`) | 项 | 内容 | |---|---| | **现象** | 真 HTTP 问 `A-06`「什么是业绩比较基准?」⇒ 答复**只有一行**「问:什么是业绩比较基准?」,「**答:…**」整段消失 | | **根因(已定因)** | 展示层 `drop_yield_claims()` 的判据是「**整行含收益数值就不输出**」;`FAQ-0022` 的答案行里有「沪深300指数收益率×60%+中证全债指数收益率×40%」,其中的 `60%` / `40%` 是**权重**、不是收益数值,却被同一条正则命中 ⇒ **整行被删** | | **为什么金标没抓到** | `A-06` 的 `key_facts` 是「业绩比较基准」这个**纯词**,而它**在「问:」那一行里还在** ⇒ `M-4` 照样判过。这是「**事实判据通过、但客户看到的东西是坏的**」的典型 —— 只有逐条人工比对答复原文才看得出来 | | **修复** | 收益词与百分比之间出现乘号或指数名 ⇒ 判为业绩基准公式**豁免**;两种语序的真实收益数值(`近一年收益率 7.60%` / `7.60% 的近一年收益率`)**照删** | | **附带改善** | `Z-04`(英文问句 `hello, what is the subscription fee?`)从 `E5b` 兜底抬到 **`E4`** —— **未**为英文问句单独加任何召回路 | ### 11.3 验收(可复算) | 项 | 结果 | |---|---| | 金标 46 条(`score_w25.json`) | `M-1 46/46`、`M-4 46/46`、`M-6 5/46`、`M-2 28/31`、`M-2b 15/18`、`M-3 4/4`、`M-7/M-8/M-9/M-10 = 0` —— **与 `w24e` 逐项相同** | | 真 HTTP(46 条) | 全部 `succeeded`,HTTP 非 200 **0 条**;转人工 **5 条**(`F-05` / `G-01` / `G-03` / `G-04` / `G-05`,全在白名单内) | | 边界 11 条 | `Z-01`/`Z-02` 预期 422 ✓、`Z-07` 预期 404 ✓、`Z-08` 三轮形状全 `E1` ✓ | | 全量 `pytest` | **1997 passed / 3 skipped**(上一轮 `1996`,`+1` 条新回归测试) | | `ruff check app tests tools` | **零新增**(27 条既有告警全部落在**本次未改动**的行上,逐条核对过) | ### 11.4 诚实留痕(本轮新增,别漏) 1. **本轮只改了一处代码**(展示层净化),**没有**动检索、阈值、出口选择、语料 —— `M-1` / `M-2` / `M-2b` / `M-3` 的分布因此**逐项不变**。 2. **`E-02` / `H-01`(「那风险高吗?」)实测只回 28 字**(`E3` 原文直返一个单行块:「南方稳健增利债券 A〔示例〕:风险等级 R2(中低风险)」)—— **内容正确但偏短**,本轮**没做厚**,登记为**可选优化**。 3. **`D2.9` §2 的「期望出口 / 期望要点 / 禁止出现」三列逐字沿用旧文**(它们是 `D3.7` 的判据,不是实测结果);本轮只换「实测」列并追加答复原文。 4. **出口仍在进程内 harness 判**(`result_w25.json`):HTTP 响应里没有 `Top1` 与引用可解析信息,**拿答复形状猜出口会假判** —— 上一轮已有 13 条假阴性的教训。 5. 本轮**未做**(继续挂账):① `_exit_partial` **仍按分数挑块**;② `D-2` 残余(同句重问**候选会变**);③ 金标仍 **46 条**(`DEC-W20-9`)。 6. 改动只落在 `客服agent/` 与 `开发文档/` 的**权威副本** + 仓库同名**镜像**,三处一致。