Files
group_fqcd_jr/docs/evidence/20260918-t2g-c09-visitor-advice-guard.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

207 lines
8.6 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": "t2g-c09-visitor-advice-guard",
"generated_at": "2026-09-18T12:49:24.354760+00:00",
"date_local": "2026-09-18",
"purpose": "执行 C-09「访客侧推介边界」(关键路径 C-01 → C-03 → C-09 → C-06 的第三步)",
"scope": [
"app/core/customer_service_rules.py(业务层)",
"app/service/agent/implementations/customer_service.py(业务层)"
],
"hit_signature_boundary": {
"input_side": "route_message() 的 PROMOTION_REQUEST_PATTERNS 与身份**无关**:访客与客户都被拦下并拿到 ADVICE_BOUNDARY_REPLY(C-01~C-03 已落地)",
"why_not_visitor_only": "输入侧按身份分化会让**已登录客户**拿到推介内容(知识块里真实存在配置建议),而 MVP 第 1 步要求客服无投资建议类内容。做成身份无关是**更严的超集**,不削弱任何红线。",
"output_side": "按 DoD ② 严格分化:护栏**只作用于访客**(`visitor` in context.roles),客户侧完全不套用",
"flagged_for_user": "若你本意是「输入侧也仅对访客生效」,改动量是 route_message 增加一个身份参数 —— 属另一处口径变更,本轮未做"
},
"implementation": {
"VISITOR_ADVICE_PATTERNS": [
"(推荐|建议)[^。;!?,,]{0,6}(购买|买入|申购|赎回|卖出|投资|配置|入手)",
"(为您|给你|我们)?推荐[^。;!?,,]{0,6}(基金|产品|组合|标的|理财)",
"(更|最)?适合(您|你)",
"(收益|回报|业绩|涨幅|净值)[^。;!?,,]{0,4}(最高|最好|最优|第一|排名)",
"(最值得|最划算)[^。;!?,,]{0,2}(买|购买|投资|入手)",
"(排名|排行)(第一|首位|前列)"
],
"visitor_advice_violation": "复用底座 compliance_context.is_prohibited_context 做语境豁免(询问/否定/引用),判定口径只此一处",
"agent_hook": "CustomerServiceAgent.handle() → _route_and_answer() + _guard_visitor_advice();原 handle 主体改名为 _route_and_answer,保证**所有出口**都过护栏",
"on_hit": "整条换成 ADVICE_BOUNDARY_REPLY,且 transfer_required=False / transfer_reason=None(不建单:访客没有工单承接方)",
"single_source": "名单与边界话术都只在业务层规则文件,底座不复制 —— 避免治理层注释里警告过的那类『常量各写一份必然漂移』"
},
"why_narrow": {
"不收的表述": [
"推荐",
"适合您"
],
"推荐二字本身": "政策原文「不得登载推荐性文字」含该词,连它一起拦会把合规问答整条打成边界话术",
"可以买入": "操作性问答「交易时间内可以买入,T+1 确认份额」会被误伤;名单只收『推荐/建议 + 交易动作』",
"持有": "产品字段标签「建议持有期限」不是建议"
},
"probe": {
"cases": 10,
"mismatch_count": 0,
"mismatch": [],
"rows": [
{
"text": "稳健型客户建议配置:货币基金 30% + 纯债基金 50%",
"expected_hit": true,
"actual": "建议配置",
"note": "配置指向",
"ok": true
},
{
"text": "该产品更适合您",
"expected_hit": true,
"actual": "更适合您",
"note": "适合性结论",
"ok": true
},
{
"text": "这只基金收益最高",
"expected_hit": true,
"actual": "收益最高",
"note": "排序性表述",
"ok": true
},
{
"text": "为您推荐的产品如下",
"expected_hit": true,
"actual": "为您推荐的产品",
"note": "指向具体标的的推荐",
"ok": true
},
{
"text": "建议您购买南方现金添利货币基金",
"expected_hit": true,
"actual": "建议您购买",
"note": "购买指向",
"ok": true
},
{
"text": "是否适合您需要结合风险测评,不能在这里下结论",
"expected_hit": false,
"actual": null,
"note": "询问/否定语境",
"ok": true
},
{
"text": "产品建议持有期限 6 个月",
"expected_hit": false,
"actual": null,
"note": "字段标签",
"ok": true
},
{
"text": "不得登载其他单位或个人的推荐性文字、图片等",
"expected_hit": false,
"actual": null,
"note": "政策原文里的『推荐』二字",
"ok": true
},
{
"text": "交易时间内可以买入,T+1 确认份额",
"expected_hit": false,
"actual": null,
"note": "操作性问答",
"ok": true
},
{
"text": "货币基金风险等级 R1,1 元起投,赎回 T+1 到账",
"expected_hit": false,
"actual": null,
"note": "纯公开资料",
"ok": true
}
]
},
"chunk_scan": {
"chunk_total": 628,
"per_pattern": [
{
"index": 1,
"pattern": "(推荐|建议)[^。;!?,,]{0,6}(购买|买入|申购|赎回|卖出|投资|配置|入手)",
"chunks_hit": 4,
"hits": [
{
"doc_id": "PROD-012",
"visibility": "public",
"source_file": "product/个人理财产品手册.md",
"hit": "建议配置",
"snippet": "属违规行为。 / / **保守型客户(C1)** / / - 建议配置:货币基金 60% + 纯债基金 40% / - 目标"
},
{
"doc_id": "PROD-012",
"visibility": "public",
"source_file": "product/个人理财产品手册.md",
"hit": "建议配置",
"snippet": "%—3.0% / / **稳健型客户(C2)** / / - 建议配置:货币基金 30% + 纯债基金 50% + 混合"
},
{
"doc_id": "PROD-012",
"visibility": "public",
"source_file": "product/个人理财产品手册.md",
"hit": "建议配置",
"snippet": "%—5.0% / / **平衡型客户(C3)** / / - 建议配置:纯债基金 30% + 混合基金 45% + 股票"
},
{
"doc_id": "PROD-012",
"visibility": "public",
"source_file": "product/个人理财产品手册.md",
"hit": "建议配置",
"snippet": ".5% / / **进取型客户(C4/C5)** / / - 建议配置:混合基金 35% + 股票基金 35% + QD"
}
]
},
{
"index": 2,
"pattern": "(为您|给你|我们)?推荐[^。;!?,,]{0,6}(基金|产品|组合|标的|理财)",
"chunks_hit": 0,
"hits": []
},
{
"index": 3,
"pattern": "(更|最)?适合(您|你)",
"chunks_hit": 0,
"hits": []
},
{
"index": 4,
"pattern": "(收益|回报|业绩|涨幅|净值)[^。;!?,,]{0,4}(最高|最好|最优|第一|排名)",
"chunks_hit": 0,
"hits": []
},
{
"index": 5,
"pattern": "(最值得|最划算)[^。;!?,,]{0,2}(买|购买|投资|入手)",
"chunks_hit": 0,
"hits": []
},
{
"index": 6,
"pattern": "(排名|排行)(第一|首位|前列)",
"chunks_hit": 0,
"hits": []
}
]
},
"knowledge_defect_found": {
"doc_id": "PROD-012",
"title": "产品手册 · 四、产品对比表与推荐策略 · 4.2 按客户类型的推荐策略",
"source_file": "product/个人理财产品手册.md",
"visibility": "public",
"content_hit": "建议配置:货币基金 60% + 纯债基金 40%(C1)… 等四档配置比例",
"verdict": "public 档携带**投资建议类内容**,与 MVP 第 1 步「游客线无投资建议类内容」冲突;运行时已被访客护栏挡住,故不是运行期漏洞,但属**语料缺陷**(该块对访客永远不可用)",
"recommendation": "登记为决策项:修语料(下线该节或降为非 public 档)需要补切片 + 重建集合 + 重跑检索自检,属知识侧动作,建议与 F-05 回写合并或单独立项"
},
"tests": {
"rules_level": "5 例真阳性 + 5 例克制用例(test_visitor_advice_violation_detects_advice / ..._ignores_non_advice)",
"agent_level": "4 例(访客被替换 / 客户原样 / 无措辞原样 / 边界话术不自绊)",
"targeted": "2 文件 69 passed"
},
"gate": {
"pytest": "2 failed / 1534 passed / 2 skipped(失败集与 T0 基线同一组)",
"ruff": "22(改动文件 All checks passed)",
"mypy": "3(无新增)",
"docs": "50 documents, no number collision",
"schema": "89 business tables passed"
}
}