Files
group_fqcd_jr/docs/evidence/20260919-t7b-smoke-closeout.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

105 lines
6.2 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.
{
"evidence_id": "20260919-t7b-smoke-closeout",
"generated_at": "2026-09-19T17:05:57+08:00",
"session": "D1.6 §4.31 / D2.1 v6.18",
"what": "W7 收尾:宽链路冒烟(tools/e2e_smoke_test.py --read-only,5 条线)+ 门禁复跑,过程中发现并修掉 1 项真实合规回归(D-10)与 1 项冒烟脚本陈旧项(D-9)",
"how": "① 真 HTTP 打 127.0.0.1:8000(常驻 uvicorn)→ 队列 → 真 Worker(python -m app.worker);② 全量 pytest / ruff / mypy / _consistency.py;③ 修复后再跑 _eval_harness/http_probe.py 复验 11 条",
"smoke": {
"command": "tools/e2e_smoke_test.py --read-only",
"result": "31/31 通过",
"lines": {
"A 访客线": [
"A1 访客令牌 V001 HTTP=201",
"A2 提问受理 R001 HTTP=202",
"A3 问答完成 status=succeeded 3.43s",
"A4 知识库覆盖 transfer_required=False"
],
"B 客户线": [
"B1 客户登录 A034",
"B2 风险测评问卷 ONB001 HTTP=200",
"B3 账户看板 T001 HTTP=200",
"B4 持仓 T006 条数=2",
"B5 资金流水 T009 条数=1",
"B8 成交明细 T007 条数=1",
"B9 创建会话 C001 HTTP=201",
"B10 会话详情 C002 HTTP=200",
"B11 会话消息 C003 HTTP=200"
],
"C 风控线": [
"C1 风控登录",
"C2 风险概览 RK001 total=2 pending=1",
"C3 预警队列 RK002 条数=2",
"C4 八类证据 RK004 8/8",
"C5 通知记录 RK005 HTTP=200"
],
"E 运营线": [
"E1 运营登录",
"E2 场外邮件 total=0",
"E3 邮箱状态 status=uninitialized"
],
"F 管理员线": [
"F1 管理员登录",
"F2 角色列表 A035 list[4]",
"F3 角色详情 A036 dict[6]",
"F4 角色权限 A037 list[18]",
"F5 用户身份 A038 dict[9]",
"F6 配置发布 A002 list[10]",
"F7 模型端点 A012 list[2]",
"F8 审计记录 A033 list[20]",
"F9 转人工工单 list[11]",
"F10 画像候选 A039 list[1]"
]
},
"skipped_read_only": [
"B6 下单",
"B12 转人工",
"C6 手动扫描 RK006",
"C7 处置闭环",
"F11 工单处置闭环"
]
},
"defects": [
{
"id": "D-9",
"kind": "工具问题(非产品缺陷)",
"title": "冒烟脚本 D 投顾线是陈旧项,必然 FAIL,会掩盖真回归",
"detail": "投顾模块整体清除(D4.4/D4.5)后 advisor_t 账号与 advisor 角色均已不存在(sys_user 仅 9001-9006/9101-9105;sys_role 仅 customer/risk_operator/admin/operator),脚本 D1 投顾登录 / D2 已发布方案 必然失败",
"fix": "脚本收敛为 5 条线:改 section_advisor_operator_admin() -> section_operator_admin(),删除 DEMO_ADVISOR 常量与 D 段代码,并在 docstring 写明删除理由",
"evidence": "tools/e2e_smoke_test.py"
},
{
"id": "D-10",
"kind": "🔴 真实合规回归(已修)",
"title": "客服「不写长期记忆」闸门在重构中被删掉",
"detail": "WorkerRuntime.should_request_memory_extraction 收到 agent_type 却不参与判定:基线(git HEAD:app/worker/runtime.py)有 `if agent_type == \"customer_service\": return False`,本轮 diff 删除该闸门 ⇒ 客服运行只要消息命中偏好信号(如「我的风险偏好是稳健型」)就会产出 memory.extraction_requested,长期记忆被重新打开,与 DEC-19 裁定 (a)(短期会话记忆=开 / 长期记忆召回=关)正面冲突",
"caught_by": "tests/integration/test_worker_runtime_mysql.py::test_http_accept_worker_commit_query_and_repeat[False](断言 memory.extraction_requested 为空)",
"fix": "新增 NO_LONG_TERM_MEMORY_AGENT_TYPES = frozenset({\"customer_service\"}) 显式闸门;顺带改写 PROFILE_CANDIDATE_AGENT_TYPES 注释(原写法诱导「重构后填回白名单」,会绕过 DEC-19)",
"tests_added": [
"tests/unit/worker/test_runtime_worker_dispatch.py::test_customer_service_never_requests_memory_extraction",
"tests/unit/worker/test_runtime_worker_dispatch.py::test_customer_service_never_produces_profile_candidate"
],
"note": "对照组断言 risk_agent 仍照常抽取 —— 闸门不得误伤其它 Agent"
},
{
"id": "D-11",
"kind": "🟡 新发现·待决(答非所问)",
"title": "「我够哪一档?」答成风险等级,未答客户分层",
"detail": "问的是客户分层(金卡 50万+/白金 200万+/钻石 600万+/尊享 1000万+),答的是「您的风险测评等级是 保守型(C1)」。根因两层:① sys_user.customer_tier 全库 11/11 为 NULL(从未种过);② 画像快照 build_snapshot 按设计排除 customer_tier(REQUIRED_SNAPSHOT_FIELDS 显式 `if field != \"customer_tier\"`),故 render_profile 的 tier 行永远渲染不出来",
"impact": "FR-CS-024(转人工摘要含画像关键标签)与 D3.1 §3.5「读 customer_level 定转人工优先级」同样无数据可用",
"status": "待用户决策(见 D1.6 §4.31 六)"
}
],
"environment": {
"race": "常驻 Worker 与 tests/integration 抢同一 MySQL outbox:dispatch_one 抢不到件返回 False、或测试期间被塞入 memory.extraction_requested ⇒ 全量门禁必须在停掉常驻 Worker 后跑才可复现",
"proof": "停 Worker 前 2 failed(handover + worker_runtime);停 Worker 后复现基线 1 failed;修 D-10 后 0 failed。单独跑 handover 用例始终 PASS",
"process_shape": "Start-Process 起服务会出现 parent + child 两个 PID(命令行相同),不是重复启动;判活看端口 8000 的 OwningProcess 与日志 _http_api*.log / _http_worker*.log",
"restarted": "API http://127.0.0.1:8000 + Worker(python -m app.worker),隐藏窗口,日志在 D:\\桌面\\金融\\_http_*.log"
},
"gates": {
"pytest": "1742 passed / 0 failed / 2 skipped(优于基线:1 failed / 1739 passed / 2 skipped)",
"ruff": "22(= 基线)",
"mypy": "3(= 基线)",
"consistency": "GATE PASS(_consistency.py)",
"http_probe": "_eval_harness/http_probe.py → 11/11 全 succeeded(修复后复验)"
}
}