客服 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 生成的本地产物
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"artifact": "t2k-c10-source-reference-downgrade",
|
||||
"generated_at": "2026-09-19T01:41:43.772361+00:00",
|
||||
"date_local": "2026-09-19",
|
||||
"purpose": "执行 C-10「知识出口来源引用」(关键路径 C-01 → C-03 → C-09 → C-06 → C-07 → C-10 的收口步);裁定取「乙 · 本期降级」并加双向护栏",
|
||||
"scope": [
|
||||
"app/service/agent/implementations/customer_service.py(业务层:_references 降级为死代码并写明依据)",
|
||||
"tests/unit/service/test_customer_service_agent.py(护栏 A:AST 守无调用点)",
|
||||
"tests/unit/service/test_agent_governance.py(护栏 B:knowledge 来源必被拒)",
|
||||
"开发文档对应的 D2.2 / D2.4(口径回写,另见 D2.1 v6.6 / D1.6 §4.19)"
|
||||
],
|
||||
"decision": {
|
||||
"chosen": "乙 · 本期降级(不向客户展示来源引用)",
|
||||
"rejected": "甲 · 底座方先登记知识来源并放行(需会签)",
|
||||
"why_b": "甲依赖底座方改造 ToolExecutor 与治理层放行 knowledge 来源,属不可控外部依赖;本模块今天要交付,不能把交付卡在别人会签上。乙的代价只是「不展示出处」,可追溯性并未丢失(见下)。",
|
||||
"why_not_silent": "降级不是静默丢弃:D2.2 的 FR-CS-010 与验收表、D2.4 的 §5.7 与 G4/AC-06 都写明了「本期降级」及其技术原因与启用前提,避免下一轮误当成已完成功能。",
|
||||
"reversibility": "完全可逆:代码本体(_references)保留在位,护栏只是禁止调用;底座方放行后删掉护栏断言即可接通。"
|
||||
},
|
||||
"technical_reason": {
|
||||
"gate": "app/service/agent/governance.py:314-316(review_output 的来源白名单只认 memory / tool)",
|
||||
"failure_mode": "knowledge 来源会被判为「引用未来自本次已授权召回结果」,抛出 ForbiddenAgentError ⇒ **整个 run 失败**,不是降级、也不是少一个字段",
|
||||
"red_line": "S-8",
|
||||
"traceability_instead": "可追溯性由审计承接:agent.tool_executed 的工具调用记录含命中 doc_id 与分数;消息表亦留痕"
|
||||
},
|
||||
"implementation": {
|
||||
"docstring": "_references() 的 docstring 重写为「保留待用的死代码 —— 本期(MVP)不向客户展示来源引用」,写明 governance.py:314-316、S-8、审计承接、启用前提须会签",
|
||||
"helper_state": "函数体原样保留(source_type=\"knowledge\" 的 SourceReference 组装逻辑未改)",
|
||||
"no_call_sites": "改用其他出口返回正文;零调用点"
|
||||
},
|
||||
"guardrails": {
|
||||
"A_ast_no_call": {
|
||||
"file": "tests/unit/service/test_customer_service_agent.py",
|
||||
"test": "test_knowledge_exit_never_calls_the_disabled_reference_helper",
|
||||
"method": "ast.walk 找 ast.Call 且 func.attr == \"_references\",断言 calls == []",
|
||||
"why_ast": "该名字会合法出现在注释与文档字符串里,纯文本搜索会把「提一句」误判成「调用」",
|
||||
"second_assert": "同时断言 _references 函数定义仍在位(清理时不得被连带删除)"
|
||||
},
|
||||
"B_governance_rejects": {
|
||||
"file": "tests/unit/service/test_agent_governance.py",
|
||||
"test": "test_knowledge_reference_is_rejected_so_it_must_stay_disabled",
|
||||
"method": "以 source_type=\"knowledge\" 的 SourceReference 调 review_output,断言抛 ForbiddenAgentError",
|
||||
"why": "把「本期为何必须降级」的技术原因钉成可执行断言:若将来底座放行,这个测试会失败并提醒同步移除护栏 A"
|
||||
}
|
||||
},
|
||||
"html_downgrade_points": [
|
||||
{
|
||||
"doc": "D2.2",
|
||||
"locator": "FR-CS-010 行",
|
||||
"change": "追加 ⏸ 2026-09-19 本期降级说明 + <span class=\"pill p2\">⏸ 本期降级</span>"
|
||||
},
|
||||
{
|
||||
"doc": "D2.2",
|
||||
"locator": "US-CS-01 行",
|
||||
"change": "期望值补「⏸ 本期不展示来源引用:C-10 乙」"
|
||||
},
|
||||
{
|
||||
"doc": "D2.2",
|
||||
"locator": "验收表 · 一 · 功能正确性",
|
||||
"change": "「每条须带来源引用」→「⏸ 本期豁免(改由审计承接)」"
|
||||
},
|
||||
{
|
||||
"doc": "D2.4",
|
||||
"locator": "§5.7 末尾",
|
||||
"change": "追加降级段:governance.py:314-316、S-8、_references() 死代码 + 护栏、启用前提须会签"
|
||||
},
|
||||
{
|
||||
"doc": "D2.4",
|
||||
"locator": "G4 行",
|
||||
"change": "「可追溯」补「⏸ 本期:来源引用降级,可追溯性由审计承接」"
|
||||
},
|
||||
{
|
||||
"doc": "D2.4",
|
||||
"locator": "§5.7 三条要求 ①",
|
||||
"change": "「知识类回答必须附来源」补「⏸ 本期降级,见下段」(本轮新发现的口径残留,与降级冲突)"
|
||||
},
|
||||
{
|
||||
"doc": "D2.4",
|
||||
"locator": "AC-06 行",
|
||||
"change": "判据补「⏸ 本期豁免:C-10 乙不展示来源引用,改由审计承接」(同上,本轮新发现)"
|
||||
}
|
||||
],
|
||||
"eol_check": {
|
||||
"D2.2": "CRLF(本轮编辑后复核)",
|
||||
"D2.4": "LF —— 编辑后实测 bytes=147917, bareLF=1758, CRLF=0,与改前同为纯 LF,EOL 未被改写"
|
||||
},
|
||||
"gates": {
|
||||
"targeted_pytest": {
|
||||
"files": 4,
|
||||
"passed": 164,
|
||||
"failed": 0,
|
||||
"command": "pytest -q tests/unit/service/test_customer_service_agent.py tests/unit/service/test_agent_governance.py tests/unit/core/test_customer_service_rules.py tests/unit/core/test_compliance_context.py"
|
||||
},
|
||||
"ruff": {
|
||||
"scope": "app tests tools",
|
||||
"errors": 22,
|
||||
"note": "等于基线,无新增"
|
||||
},
|
||||
"mypy": {
|
||||
"scope": "app",
|
||||
"errors": 3,
|
||||
"note": "等于基线(agent_persistence_service.py:82 / run_query_service.py:79 / promotion_renderer.py:84,均为既有问题)"
|
||||
}
|
||||
},
|
||||
"not_done_in_this_step": [],
|
||||
"open_questions": {
|
||||
"C-10_enablement": "若后续要展示出处,需底座方:① ToolExecutor 把工具返回的知识 doc_id 登记为本次可引用来源;② 治理层放行 knowledge 来源。两步都要会签。"
|
||||
},
|
||||
"closure_after": {
|
||||
"document_writeback": "已完成:D2.1 升 v6.6(C-10 行标 [x] + 修订要点)、D1.6 新增 §4.19(第十五轮会话记录)",
|
||||
"full_gate": "已完成,见 full_gate"
|
||||
},
|
||||
"full_gate": {
|
||||
"pytest": {
|
||||
"command": "pytest -q -p no:cacheprovider",
|
||||
"result": "2 failed / 1579 passed / 2 skipped",
|
||||
"note": "2 failed = T0 基线同两项(profile_snapshot_current_invariant / worker_runtime[False]),不计入本轮回归;passed 1577 → 1579,增量 = 本步新增的 2 条护栏"
|
||||
},
|
||||
"ruff": {
|
||||
"command": "ruff check app tests tools",
|
||||
"errors": 22,
|
||||
"note": "= 基线"
|
||||
},
|
||||
"mypy": {
|
||||
"command": "mypy app",
|
||||
"errors": 3,
|
||||
"note": "= 基线"
|
||||
},
|
||||
"authoritative_docs": {
|
||||
"command": "python tools/check_authoritative_docs.py",
|
||||
"result": "checked 50 documents, no number collision"
|
||||
}
|
||||
},
|
||||
"environment_finding": {
|
||||
"title": "残留 Worker 抢消费 outbox 事件 ⇒ 集成测试偶发失败(环境级,非代码回归)",
|
||||
"first_symptom": "全量首跑多出第 3 个失败:tests/integration/test_memory_extraction.py::test_memory_is_not_recallable_across_customers",
|
||||
"diagnosis_steps": [
|
||||
"① 单文件重跑两次 → 3/3 通过 ⇒ 不稳定,不是确定性缺陷",
|
||||
"② 按同一链路脚本复现 seed → enqueue → consume_once → 返回 True ⇒ 消费机制本身正常",
|
||||
"③ 无人消费检测:插入一条 pending 事件后只观察、不消费 → 1 秒内被外部进程改成 published ⇒ 存在外部消费者"
|
||||
],
|
||||
"root_cause": "2026-09-18 17:52 启动的残留服务一直存活,其中 python -m app.worker 会抢消费 outbox 事件,与测试自身的 consume_once(aggregate_id=...) 竞争,先到先得 ⇒ 测试偶发拿不到事件而返回 False",
|
||||
"processes": [
|
||||
{
|
||||
"pid": 27204,
|
||||
"child": 8740,
|
||||
"cmd": "uvicorn app.main:app --host 127.0.0.1 --port 8000"
|
||||
},
|
||||
{
|
||||
"pid": 27084,
|
||||
"child": 2204,
|
||||
"cmd": "python -m app.worker"
|
||||
}
|
||||
],
|
||||
"action": "经用户批准,已 Stop-Process 停掉 4 个 PID;停止后全量 pytest 回到 2 failed 基线",
|
||||
"reusable_lessons": [
|
||||
"与 A-02「门禁基线前先停 Worker」完全吻合 —— 该前置不是形式主义,不停就会污染集成测试",
|
||||
"印证 S-7:知识集合重建后这两个服务早已过期",
|
||||
"遇到集成测试失败,先做「无人消费检测」排除外部消费者,再怀疑代码"
|
||||
]
|
||||
},
|
||||
"demo_precondition": {
|
||||
"issue": "API 与 Worker 现已停止(8000 端口无监听)",
|
||||
"must_do": "演示前按 S-7 重启 API + Worker,否则前端问答直接连不上"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user