"""客服 Agent 的**出口码注册表**(`W27` 新增,`D3.9` §3.1 / §6.2)。 ## 为什么要有这个模块 `D3.7` §3 记着一条老口径:「旧实现**没有** `E1`—`E5` 出口码」,评测探针只能靠"包装终止型 方法"给每条打标。那套包装法有两个代价: 1. **它测的是探针的包装,不是产品声明** —— 出口改名/换位置,标就变了,而 `D3.7` 的 验收结论是拿这些标算出来的; 2. 有些**行为正确但没出口方法**的分支(例如 `E8` 风险等级代办拦截)会被记成 「没有任何出口」,于是**做对的安全行为被判未达标**(`D3.9` §2 的度量失真)。 因此 `W27` 起改为:`CoreResult.exit_code` 由**出口自己声明**,读侧不反解文本。 第一个(也是目前唯一)消费方是门禁 `F5` 的**免责声明分档**(`DEC-W27-10`): 业务出口附完整投资免责话术,非业务出口只附轻量提示。 ## 与 `D3.6` / `D3.7` 的编号关系 本模块的编号**直接沿用** `D3.6` §3 的出口码(`E1` 澄清 / `E2a`—`E2e` / `E3` / `E4` / `E5a`—`E5c`),并补三个 `D3.6` 未给码的出口: - `E0`:`L0` 表层判定层本身(`D3.9` §3.1)。它是**路由层**不是终态出口,因此 **不写入 `CoreResult.exit_code`**,只作为日志/审计的分组名; - `E6`:行情出口(`D3.9` §4); - `E8`:风险等级代办拦截(`D3.7` 的 `E-08` 红线 1 ④); - 安全出口沿用 `SafetyRoute.priority`(`P0` / `P1` / `P2` / `COMPLIANCE` / `PROMPT_INJECTION` / `ADVICE_BOUNDARY`),`D3.7` §3 已按这个口径给安全出口打标; - `LOGIN` 沿用 `D3.7` 金标里已经在用的写法(`H-03` 的期望出口并列里就有它); - `CHAT` / `CONTACT` 是本轮新增:闲聊与"问渠道联系方式"没有历史编号,但 `D3.9` §6.2 明确把它们列入**非业务档**,所以必须有一个可声明的码。 """ from __future__ import annotations # ---- `L0` 表层判定层(路由分组名,不写入 `CoreResult.exit_code`) ---- EXIT_SURFACE = "E0" # ---- 出口 `E1` / `E2` 族:澄清与计算型 ---- #: `E1`:澄清。**非业务档**(`D3.9` §6.2)—— 「请问您想了解哪一只」不该挂投资风险提示。 EXIT_CLARIFY = "E1" #: `E2a`:类别费率试算(给算法 + 区间,不给单一结论金额)。 EXIT_CALC_CATEGORY_FEE = "E2a" #: `E2b`:单只产品赎回费递进。 EXIT_CALC_REDEMPTION = "E2b" #: `E2c`:`C—R` 通用匹配规则(矩阵一格)。 EXIT_SUITABILITY_RULE = "E2c" #: `E2d`:适当性裁决(产品风险等级 × 客户档案等级)。 EXIT_SUITABILITY_VERDICT = "E2d" #: `E2c-my`(`W21` 新增):**以本人等级为对象**的可购买范围清单。 #: 与 `E2c` 的分工是"问句里有没有自带参数":`E2c` 收 `C1 能买 R3 吗` 这类自带 C/R #: 的规则题,本码收 `我能买什么等级的产品` 这类**只问范围**、等级从画像里取的问法。 EXIT_ELIGIBLE = "E2c-my" #: `E2e`:本人画像 / 客户分层作答(`query_customer_profile`)。 EXIT_PROFILE = "E2e" # ---- 出口 `E3` / `E4`:知识直返与证据约束生成 ---- EXIT_KNOWLEDGE_DIRECT = "E3" EXIT_EVIDENCE = "E4" # ---- 出口 `E5` 族:分级回退 ---- #: `E5a`:知识侧的澄清(候选来自当前可见档位)。**非业务档**。 EXIT_CLARIFY_KB = "E5a" #: `E5b`:部分答 + 引导。**业务档**—— 即使正文为空,客户拿到的也是"业务上答不上来", #: 该给的投资风险提示一个字都不能少(这正是分档判据必须用出口码的原因)。 EXIT_PARTIAL = "E5b" #: `E5c`:转人工。**业务档**(安全与责任披露最不能省的一档)。 EXIT_TRANSFER = "E5c" # ---- 出口 `E6` / `E8` ---- #: `E6`:行情出口(净值序列 / 区间涨跌)。数字来自受控数据源,**业务档**。 EXIT_QUOTE = "E6" #: `E8`:风险等级代办拦截(`D3.7` `E-08` 红线 1 ④)。业务档。 EXIT_RISK_LEVEL_GUARD = "E8" # ---- 无编号出口:沿用既有语义名 ---- #: 闲聊。非业务档。 EXIT_CHITCHAT = "CHAT" #: 引导登录(访客请求超出公开服务范围)。非业务档;`D3.7` 金标已用 `LOGIN` 这个名字。 EXIT_LOGIN = "LOGIN" #: 问渠道联系方式(公开信息,不是提交投诉)。非业务档 —— 「电话是多少」后面跟一句 #: 投资风险提示属于挂错话术(`D3.9` §6.2 的例证之一)。 EXIT_CONTACT = "CONTACT" #: **非业务档**出口集合:门禁 `F5` 只对这些出口追加**轻量提示**,其余一律追加完整 #: 投资免责话术。集合取"白名单式"是刻意的 —— 新增出口若忘记登记,默认落到**业务档** #: (多一句披露),失败方向与 `INV-5`(收敛)一致。 NON_BUSINESS_EXIT_CODES = frozenset({ EXIT_CLARIFY, EXIT_CLARIFY_KB, EXIT_CHITCHAT, EXIT_LOGIN, EXIT_CONTACT, }) #: 安全出口的优先级标签(与 `app/core/customer_service_rules.py` 的 #: `SafetyRoute.priority` 同源)。列在这里是为了让"哪些 priority 是安全出口"可被 #: 静态检查与测试引用,而不是散落在注释里。 SAFETY_PRIORITIES = frozenset({ "P0", "P1", "P2", "COMPLIANCE", "PROMPT_INJECTION", "ADVICE_BOUNDARY", })