Files
group_fqcd_jr/docs/evidence/20260919-t2m-h04-graded-fallback-whitelist.json
T
张胜宇 5d0becb67d 客服 Agent 重构收口:五出口决策链 + 知识库档位隔离 + 前端入参边界(答辩演示版本)
一、客服 Agent 智能增强(正面回应"不智能、动不动就转人工")
- 决策链由 2 个出口扩到 5 个:E1 澄清 / E2 计算型 / E3 知识直返 / E4 证据约束生成 / E5 分级回退
- 转人工从"默认动作"降为最后一档 E5c,只保留 4 类白名单:
  P0 反诈 / P1 账户与个人数据 / P2 写操作与争议 / 用户明确要求人工
- 46 条金标实测(修复前 → 修复后):
  转人工率 43.5% → 10.9%;出口准确率 45.7% → 100%;事实正确率 69.6% → 100%
  禁忌违反 1 → 0;档位越权 / 无出处数字 / 误拒 四项零容忍全 0
- 安全不变量 INV-1~INV-5;零容忍规则未删,改的是挂载点
  (输出侧字面黑名单 → 检索层档位隔离 + 判定层合规词表 + 输出守护)

二、知识库:档位单点化与物理隔离
- 新增 app/core/knowledge_tier.py 作为档位规则唯一落点(G-03),
  knowledge_contracts.py 原定义块改为显式再导出(X as X,非副本)
- 档位过滤由 bool 默认值(fail-open)改为 tiers 必填集合(缺参即 TypeError)
- Milvus 侧四集合按 visibility 分区键物理隔离;双 schema 收敛为一套
- 新增 app/core/actor.py:访客三元组与匿名判定的唯一构造/判定点(G-01/G-01b)
- 新增 app/core/fund_fee_rules.py:费率计算纯函数

三、前端入参边界对齐(本轮 W11 新修,4 处"校验宽于存储")
- message 加 max_length=8000(与浮窗 widget.js 的 maxlength 一致)
- session_id 加 1—64;idempotency_key 上限 128 → 64(对齐列宽 String(64))
- feedback_type 加 max_length=32(对齐列宽 String(32))
- 8 条路径参数补 min_length=1 + max_length=64 + 字符集正则
  ({session_id} / {run_id} / {handover_id})
- 改前超限值会落到 MySQL 才失败(500);改后一律 422 AGENT_INPUT_INVALID + 字段级定位
- 新增 tests/unit/api/test_frontend_boundaries.py(33 例),含"端点表 ↔ OpenAPI 全量对照"

四、投顾模块整体清除(D4.4 / D4.5)
- 删除投顾相关 controller / schema / model / repository / service 及门户页面
- tools/portal_api_check.py 同步作废 AD003/AD005/AD011/A047 四条用例与 advisor_t 登录
  (端点与账号均已不存在,此前稳定报 3 条假红)

五、验证(提交前实测)
- pytest -q:1856 passed / 2 skipped / 0 failed
- ruff check app tools tests:19(= 基线);mypy app:2(= 基线)
- 前端接口契约体检 portal_api_check.py:38 项,通过 34,失败 0,跳过 4
- 全链路冒烟 e2e_smoke_test.py --read-only:31/31
- HTTP 全链路探针 http_probe.py:11/11 succeeded
- 跨文档一致性 _consistency.py:GATE PASS
- 真机边界复验 12 条:12/12 符合预期

六、纪律与文档
- 可改文件白名单 A-09(docs/46)与底座会签申请单 A-10(docs/47,组 1—组 4 全部受理)
- 零 DDL:未新增/修改任何表结构,89 张业务表与基线一致
- 证据留痕:docs/evidence/**(含 46 条金标 score、快照、清除与重建记录)
- 未提交(刻意排除,见提交说明):仓库内 客服agent/ 与 开发文档/ 是 2026-09-16 前的
  过期副本(Todolist 440 行 vs 权威 D2.1 1167 行),权威正本在仓库外;
  _chunks_report.txt 是 tools/build_knowledge_chunks.py 生成的本地产物
2026-09-20 14:33:30 +08:00

89 lines
6.7 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"artifact": "t2m-h04-graded-fallback-and-transfer-whitelist",
"generated_at": "2026-09-19T02:04:04.969488+00:00",
"date_local": "2026-09-19",
"purpose": "执行批次 H 的 H-04「分级回退 E5 + 转人工白名单收口」(答辩反馈「很多问题都强制转人工」的最正面修复)",
"scope": [
"app/core/customer_service_rules.py(P1 口径显式化)",
"tests/unit/core/test_customer_service_rules.py(P1 口径用例)",
"tests/unit/service/test_customer_service_agent.py(白名单结构性守卫 + 零残留守卫)",
"客服agent/D2.2-客服Agent需求文档.html(US-CS-08 残留口径修正)",
"开发文档/D3.1-客服Agent需求开发文档与设计方案.html(TK-CS-011 残留口径修正)"
],
"pre_existing_state_取证": {
"① 回退链 E5a/E5b/E5c": "已在位(E5a 见 H-01;E5b `_exit_partial` 不建单;E5c `_exit_transfer` 为唯一置位出口)",
"② 白名单 4 类码": "已在位:`TRANSFER_REASONS = {safety_risk, account_data, write_or_dispute, explicit_request}`",
"③ E5c 上下文摘要": "已在位:`app/service/customer_service_handover_context.py`(转接原因 / 澄清轮次 / 知识来源数 / 脱敏会话摘要),由 `agent_persistence_service` 写入工单与 Outbox",
"④ 回退不得跨档位": "**结构性成立**:`tiers` 是 `search()` 必填参数、由 `tiers_for_roles(context.roles)` 从鉴权推导一次;三条召回路径(向量 / 产品名词法 / 父块)共用同一 `expression = visibility_expression(tiers)`,不存在放宽档位的回退分支",
"low_score_repeat 残留": "**代码零残留**(`rg` 全仓无命中)"
},
"real_gaps_found": {
"gap_A_第四条白名单码未发出": "`TRANSFER_REASON_ACCOUNT`(P1 账户数据)声明在白名单里,但 `route_message` 的 P1 分支 `transfer_required=False` ⇒ 白名单 4 类实际只发出 3 类",
"gap_B_无结构性守卫": "「白名单外不得转人工」只靠 `_exit_transfer` 的运行时校验;`route_message` 是**直接**置 `transfer_required=True` 的,新增分支可以绕过,跑一遍看不出明天的分支",
"gap_C_文档残留": "`D2.2` `US-CS-08` 与 `D3.1` `TK-CS-011` 仍写「连续 2 轮答不上来 → 转人工(low_score_repeat)」,与 v2.5 `FR-CS-023`(已删除该触发)**自相矛盾**,且 `low_score_repeat` 不在白名单内"
},
"implementation": {
"gap_A": {
"decision": "P1 账户数据**有意不建单**(不改行为,只把口径显式化)",
"rationale": "账户数据 Agent 读不到、人工接线同样读不到;建单只会把客户从自助路径推到排队,与 H-04 的目标相反。白名单是「允许上限」而非「必须转」。",
"code": "P1 分支补注释写明「有意不建单、`TRANSFER_REASON_ACCOUNT` 保留但不发出、若启用须同步更新 DoD」",
"test": "tests/unit/core/test_customer_service_rules.py::test_p1_account_data_never_opens_a_ticket_by_design",
"flagged_for_user": "若你希望对**已登录客户**的账户数据问题也建单,改动是一行(P1 分支置 `transfer_required=True` + `TRANSFER_REASON_ACCOUNT`),但转人工率会上升,且与 D3.7 的 ≤15% 目标相抵。我的建议是维持不建单。"
},
"gap_B": {
"guard": "AST 扫源码:所有 `transfer_required=True` 的落点,必须 ① 落在客服 Agent 的 `_exit_transfer` 内(该函数有 `reason_code not in TRANSFER_REASONS` 运行时校验),或 ② 在规则模块里带可解析的 `TRANSFER_REASON_*` 常量且值在白名单内",
"test": "tests/unit/service/test_customer_service_agent.py::test_transfer_requires_a_whitelisted_reason_code",
"extra": "同时断言白名单里**实际会发出**的三类必须在场、`_exit_transfer` 守卫入口存在"
},
"gap_B2": {
"guard": "`low_score_repeat` / 「连续 2 轮兜底」在规则模块与客服 Agent 源码中零残留",
"test": "tests/unit/service/test_customer_service_agent.py::test_consecutive_fallback_transfer_rule_has_no_residue"
},
"gap_C": {
"D2.2": "`US-CS-08` 改为「**不转人工**(v2.5 已删除 low_score_repeat):第 2 轮起走 E5b 部分答 + 引导;转人工仅限 FR-CS-023 白名单 4 类」",
"D3.1": "`TK-CS-011` 改为「连续兜底**不**触发转人工」并写明白名单外一律判不合格",
"eol": "D2.2 保持 CRLF(1085);D3.1 保持 LF —— 编辑后逐项核对"
}
},
"dod_mapping": {
"① 回退链 E5a→E5b→E5c": "已在位;本步取证确认(E5a 见 H-01 证据)",
"② 白名单 4 类 + 删除连续 2 轮兜底": "白名单 4 码已在位;连续 2 轮兜底代码零残留(守卫用例);P1 第 4 类口径显式化为「允许但不发出」",
"③ E5c 带上下文摘要": "已在位并经取证(`customer_service_handover_context.py`)",
"④ 回退不得跨档位": "**结构性成立**并经取证:`tiers` 必填 + 单点推导 + 三路召回共用同一 expression",
"⑤ 白名单外转人工 = 不合格": "新增结构性守卫用例(AST 扫全仓 `transfer_required=True` 落点)"
},
"gates": {
"targeted_pytest": {
"files": 2,
"passed": 128,
"failed": 0,
"note": "test_customer_service_agent.py + test_customer_service_rules.py"
},
"full_pytest": {
"result": "2 failed / 1594 passed / 2 skipped",
"note": "2 failed = T0 基线同两项;passed 1591 → 1594(+3 = 本轮新单测)"
},
"ruff": {
"scope": "app tests tools",
"errors": 22,
"note": "= 基线"
},
"mypy": {
"scope": "app",
"errors": 3,
"note": "= 基线"
}
},
"honest_notes": [
"本轮**没有**把 P1 改成建单 —— 这是**有意的口径确认**,不是漏改;已写成注释 + 单测 + 文档",
"`TRANSFER_REASON_ACCOUNT` 因此是「白名单内的保留码」,代码里没有任何发出点;这与「白名单外不得转人工」不冲突(白名单是允许上限)",
"`客服agent/_build/_body_requirements.html` 仍是**旧版**(US-CS-08 与 FR-CS-023 都是删除前的文本)—— 属构建中间产物,本轮未动,需你裁定是否重生成或删除",
"`开发文档/D3.5` 第 125 行仍写「FR-CS-023 的『连续 2 轮兜底』规则不变」—— 那是备选方案池文档的历史记录,本轮未动(它不是需求来源)",
"DoD 的 `D3.7` `B-01`~`B-06` 金标回归与 `M-6` 转人工率属 `H-06`,本轮未跑"
],
"open_follow_ups": [
"H-02(E2 计算型)、H-03(E4 证据约束生成)、H-06(金标评测 46 条 + M-6 转人工率)",
"H-05(档位分区隔离)与 `_build/` 旧件的处置"
]
}