一、客服 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 生成的本地产物
211 lines
6.6 KiB
JSON
211 lines
6.6 KiB
JSON
{
|
||
"round": "W10 / H-05 收口复核",
|
||
"date": "2026-09-19",
|
||
"method": "真连 Milvus(describe_collection)+ 真实检索双向验证",
|
||
"items": {
|
||
"1_visibility_partition_key_not_null": {
|
||
"evidence": {
|
||
"fin_basic_collection": {
|
||
"num_partitions": 16,
|
||
"visibility_is_partition_key": true,
|
||
"visibility_nullable": null,
|
||
"visibility_max_length": 16,
|
||
"has_visibility_field": true,
|
||
"field_names": [
|
||
"chapter",
|
||
"content",
|
||
"doc_id",
|
||
"doc_no",
|
||
"effective_date",
|
||
"embedding",
|
||
"expire_date",
|
||
"family_id",
|
||
"intent",
|
||
"param_class",
|
||
"reviewer",
|
||
"section",
|
||
"source_file",
|
||
"source_url",
|
||
"tags",
|
||
"title",
|
||
"version",
|
||
"visibility"
|
||
]
|
||
},
|
||
"fin_faq_collection": {
|
||
"num_partitions": 16,
|
||
"visibility_is_partition_key": true,
|
||
"visibility_nullable": null,
|
||
"visibility_max_length": 16,
|
||
"has_visibility_field": true,
|
||
"field_names": [
|
||
"chapter",
|
||
"content",
|
||
"doc_id",
|
||
"doc_no",
|
||
"effective_date",
|
||
"embedding",
|
||
"expire_date",
|
||
"family_id",
|
||
"intent",
|
||
"param_class",
|
||
"reviewer",
|
||
"section",
|
||
"source_file",
|
||
"source_url",
|
||
"tags",
|
||
"title",
|
||
"version",
|
||
"visibility"
|
||
]
|
||
},
|
||
"fin_product_collection": {
|
||
"num_partitions": 16,
|
||
"visibility_is_partition_key": true,
|
||
"visibility_nullable": null,
|
||
"visibility_max_length": 16,
|
||
"has_visibility_field": true,
|
||
"field_names": [
|
||
"chapter",
|
||
"content",
|
||
"doc_id",
|
||
"doc_no",
|
||
"effective_date",
|
||
"embedding",
|
||
"expire_date",
|
||
"family_id",
|
||
"intent",
|
||
"param_class",
|
||
"reviewer",
|
||
"section",
|
||
"source_file",
|
||
"source_url",
|
||
"tags",
|
||
"title",
|
||
"version",
|
||
"visibility"
|
||
]
|
||
},
|
||
"fin_policy_collection": {
|
||
"num_partitions": 16,
|
||
"visibility_is_partition_key": true,
|
||
"visibility_nullable": null,
|
||
"visibility_max_length": 16,
|
||
"has_visibility_field": true,
|
||
"field_names": [
|
||
"chapter",
|
||
"content",
|
||
"doc_id",
|
||
"doc_no",
|
||
"effective_date",
|
||
"embedding",
|
||
"expire_date",
|
||
"family_id",
|
||
"intent",
|
||
"param_class",
|
||
"reviewer",
|
||
"section",
|
||
"source_file",
|
||
"source_url",
|
||
"tags",
|
||
"title",
|
||
"version",
|
||
"visibility"
|
||
]
|
||
}
|
||
},
|
||
"verdict": "PASS:四个集合的 `visibility` 均为**分区键**(`is_partition_key=true`)、`max_length=16`、`num_partitions=16`;写入侧空值被拒(见 1b 行为实测)。注:pymilvus 的 `describe_collection` 不回传 `nullable` 字段(为 null),故 NOT NULL **不以元数据断言**,改以行为实测为准。"
|
||
},
|
||
"2_retrieval_partition_pruning": {
|
||
"mechanism": "分区键模式下 Milvus 只支持可下推为分区裁剪的表达式;`app/core/knowledge_contracts.visibility_expression()` 产出 `visibility in [...]`,由 `knowledge_search_service.search()` 作为 `filter` 下发。",
|
||
"bidirectional_test": {
|
||
"query": "各类基金的起投金额分别是多少?",
|
||
"visitor_tiers": [
|
||
"public"
|
||
],
|
||
"visitor_top": [
|
||
{
|
||
"doc": "PROD-001-05",
|
||
"vis": "public",
|
||
"score": 0.7329
|
||
},
|
||
{
|
||
"doc": "PROD-003-05",
|
||
"vis": "public",
|
||
"score": 0.7247
|
||
},
|
||
{
|
||
"doc": "PROD-005-05",
|
||
"vis": "public",
|
||
"score": 0.7132
|
||
},
|
||
{
|
||
"doc": "PROD-007-03",
|
||
"vis": "public",
|
||
"score": 0.7108
|
||
}
|
||
],
|
||
"visitor_sees_registered": false,
|
||
"customer_tiers": [
|
||
"public",
|
||
"registered"
|
||
],
|
||
"customer_top": [
|
||
{
|
||
"doc": "FAQ-0020",
|
||
"vis": "registered",
|
||
"score": 0.8717
|
||
},
|
||
{
|
||
"doc": "PROD-001-05",
|
||
"vis": "public",
|
||
"score": 0.7329
|
||
},
|
||
{
|
||
"doc": "PROD-003-05",
|
||
"vis": "public",
|
||
"score": 0.7247
|
||
},
|
||
{
|
||
"doc": "PROD-005-05",
|
||
"vis": "public",
|
||
"score": 0.7132
|
||
}
|
||
],
|
||
"customer_sees_registered": true
|
||
},
|
||
"verdict": "PASS:同一条 `registered` 档内容,访客档检索**看不到**、客户档**看得到**"
|
||
},
|
||
"3_over_fetch_cancelled": {
|
||
"evidence": "全仓 `rg -i 'over_fetch|over-fetch'` 在 app/tools/tests 下**零命中**(分区裁剪后 TopK 不再被不可见条目占满)",
|
||
"verdict": "已取消"
|
||
},
|
||
"4_two_schema_scripts_converged": {
|
||
"build_schema_definitions": {
|
||
"setup_milvus_knowledge_collections.py": 1
|
||
},
|
||
"importers": {
|
||
"load_knowledge_milvus.py": [
|
||
"import setup_milvus_knowledge_collections as collections_setup # noqa: E402",
|
||
"from setup_milvus_knowledge_collections import FIELD_LIMITS # noqa: E402"
|
||
]
|
||
},
|
||
"num_definitions": 1,
|
||
"single_source": "tools/setup_milvus_knowledge_collections.py",
|
||
"verdict": "PASS:`build_schema` 全仓只有 **1 处**定义(`setup_milvus_knowledge_collections.py`),`load_knowledge_milvus.py` 反向 import 它复用 `build_schema`/`FIELD_LIMITS` ⇒ 同名集合「两套字段」已收敛为一套"
|
||
},
|
||
"5_tier_change_is_ops_action": {
|
||
"doc": "见 `D2.1` §10.3 SOP 与 `tools/setup_milvus_knowledge_collections.py` 文件头注释(分区键模式下 Milvus 禁止手工 `create_partition`:`disable create partition if partition key mode is used`,新增档位值 = 直接写入数据、由 Milvus 自动分布,**不是**建分区动作)",
|
||
"verdict": "已落文"
|
||
},
|
||
"1b_write_side_fail_closed_probe": {
|
||
"method": "按生产同款 schema 建临时集合并插入一条缺 `visibility` 的行;无论成败都 drop 临时集合",
|
||
"collection": "h05_partition_probe_tmp",
|
||
"insert_without_visibility": "REJECTED: DataNotMatchException: <DataNotMatchException: (code=1, message=Insert missed an field `visibility` to collection without set nullable==true or set default_value)>",
|
||
"fail_closed": true,
|
||
"tmp_collection_dropped": true,
|
||
"verdict": "PASS:写入侧拒绝空分区键(fail-closed)"
|
||
}
|
||
},
|
||
"conclusion": "H-05 五项全部达标:① 分区键(+ 写入侧空值拒绝,行为实测);② 检索分区裁剪(`registered` 档内容访客看不到、客户看得到);③ `over-fetch` 已取消(全仓零命中);④ 双 schema 收敛(`build_schema` 全仓单点,loader 反向 import);⑤ 档位值变更口径已落文。⇒ `H-05` 由「未通过」改判为 **达标**。⚠️ **与 DoD ⑤ 原文的差异(如实登记)**:分区键模式下 Milvus 禁止手工 `create_partition`,所以「档位值变更 = 建分区」的表述不成立 —— 正确口径是「新增档位值直接写入,由 Milvus 自动分布」。"
|
||
} |