Files
group_fqcd_jr/开发文档/D4.1-客服Agent重构报告-2026-09-16.md
张胜宇 bc61d5c579 docs: 入库权威文档目录(客服agent/ 24 份 + 开发文档/ 50 份,替换旧命名的过期副本)
## 为什么做这一步

权威文档 74 份此前**只在本机**,评审者 clone 分支后看不到任何设计文档;而仓库里那两份同名目录
是 **2026-09-16 之前的过期副本,连文件名都是旧的**(无体系编号)。本次按「**权威覆盖过期**」入库。

## 入库内容

| 目录 | 文件数 | 体积 | 说明 |
|---|---|---|---|
| `客服agent/` | 24 | 0.77 MB | `D2.1`~`D2.6` 对外交付四件套 + 演示脚本/答辩报告 + `_build` 构建工具 |
| `开发文档/` | 50 | 2.16 MB | `D1.x` 索引与决策、`D3.x` 方案、`D4.x` 清除与重构留痕、`D5.x` 业务流程、`D6.x` 业务事实基座、`D7.x` 交付物、`D8.x` 规范 |

**旧的过期副本整体移除**(`客服Agent执行Todolist.md` → `D2.1-客服Agent执行Todolist.md` 之类
的改名 + 新增 `D2.5`/`D2.6`),入库后目录内容与权威副本**逐文件一致(零差异,已复核)**。

## 入库前的安全扫描(必须留痕)

- 扫描规则:`sk-` 类密钥 / `Bearer` 长串 / `password=`、`api_key=` 赋值 / 会话中出现过的两把明文 key 片段。
- 结论:**真实密钥只出现在 `.env`**(已被 `.gitignore` 命中,未入库);`.env.example` 与
  `config/risk.env.example` 只有**空占位**。
- 文档内唯一命中是 `D3.1` 里一处**截断的示例 JWT**(`Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`),
  末尾带省略号,是接口文档的示意值,**不是可用凭据**。
2026-09-20 15:03:15 +08:00

419 lines
46 KiB
Markdown
Raw Permalink 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.
# 客服 Agent 重构报告(清除了什么、缺什么、按什么顺序装回去)
> **体系编号**:`D4.1` · 域:四、清除与重建留痕 · 编号体系见 `D1.1` §4.0
> **编号**:CS-REFACTOR-2026-010
> **日期**:2026-09-16
> **性质**:**重构执行报告(v5.1 级修订)** —— 取代 `D3.4-客服Agent重构Todolist.md` v5.0 的 §5 执行看板 / §6 关键路径 / §7 完工判据 / §8 风险登记四节
> **依据**:`客服重建启动前落点复核与建议-2026-09-16.md`(CS-REFACTOR-2026-009,落点逐项取证)+ `D4.3-客服模块清除执行报告-2026-09-16.md`(CS-PURGE-2026-008,清除事实)
> **不变的部分**:v5.0 的方法论、批次结构、19 项决策、§1 底座接触面总表、§0 规范依据(N-1~N-14)**全部继续有效**,本报告不推翻它们
> **取证方法**:逐文件 Glob / Grep / Read,**不采信任何文档表述**
> **能力边界**:⚠️ 本机无 pytest/ruff/mypy 依赖、无 venv → **本报告所有结论均为「文件系统级 + 读码级」,未经运行时验证**
---
## 0. 一句话结论
**v5.0 的方法论是对的,但它写于模块清除之前,20 项任务的落点已不存在。**
**另有一项连带影响需要登记**:`customer_service` 是**唯一**接受 `visitor` 角色的 Agent,因此游客侧的「对话入口」随它一起消失。**但这不是另一条链路,也不需要新建任何「游客 Agent」**——游客与客户**共用同一个 `customer_service` Agent**(见 §2),重建它即自然恢复。
---
## 1. 复核结论总览
对 v5.0 的 46 项逐条取证,结果分三类:
| 类别 | 数量 | 处置 |
|---|---|---|
| ✅ **落点仍有效,可直接开工** | **26 项** | 照 v5.0 执行 |
| ⚠️ **落点已消失,落点须改写为「新建」** | **20 项** | 见 §4,本报告已逐项给出新落点 |
| 🟢 **已完成**(由形态A代为完成) | **1 项**(C-07) | 勾掉 |
| ⚠️ **v5.0 未写清的既有要求** | **4 项(P-6~P-9)** | 见 §2、§5。**不是新增能力,是「重建客服时必须一并做全」的既有属性** |
---
## 2. 游客侧对话入口:随客服 Agent 一起消失(**同一件事,非新链路**)
> ⚠️ **本节曾在 2026-09-16 被误定性为「游客链路已被切断」的独立重大缺陷。经用户纠正 + 复核 `app/static/portal/README.md:30-33`,该定性**错误**,已按下文更正。**
>
> **更正要点**:**游客不需要独立的 Agent**。游客侧只做两件事——① 看公开产品信息(**不经 Agent**,走 `GET /api/v1/products` / P001 → `public_product_service`);② 用客服浮窗问公开信息(**就是 `customer_service` 这一个 Agent**)。因此不存在「游客链路」这个模块,也不存在「游客 Agent」这个待建项。
### 2.1 原始设计(仓库原文,权威)
`app/static/portal/README.md:30-33`:
> 客服浮窗由公开首页、基金产品页和客户工作台统一挂载。访客使用 `/api/v1/visitor-tokens` 获取短期令牌(角色 `visitor`,权限只有 `agent:run` + `knowledge:query`),因此**必须**走 `query_knowledge` 这个工具名;登录客户走 `search_knowledge`。两者都要出现在发布配置的 `agent_tools/customer_service:<intent>` 白名单里,缺哪一条,对应人群就一问即失败。
**关键结构**:
| 项 | 游客 | 登录客户 |
|---|---|---|
| 入口 | 访客令牌(`/api/v1/visitor-tokens`) | 登录 JWT |
| 角色 | `roles=("visitor",)` | `roles=("customer",)` |
| **Agent** | **同一个 `customer_service`** | **同一个 `customer_service`** |
| 工具名 | `query_knowledge` | `search_knowledge` |
| 白名单 key | **同一条** `agent_tools/customer_service:<intent>` | **同一条** |
| 挂载点 | `app-shell.js`(公开首页/产品页/客户工作台统一挂载) | 同左 |
**结论**:游客与客户**共用一个 Agent、一条白名单 key**,靠**令牌角色 + 工具名**区分。这就是 v5.0 的 **D-04**(档位由 `context.roles` 推导)在设计上的由来。
### 2.1b 代码里的第二重印证:游客侧**意图范围更窄**
原 `customer_service.py:49-50`(`_cs_purge_backup/...`)把意图分成两档——这正是「游客只要公开信息对话」的直接体现:
```python
BUSINESS_INTENTS = (INTENT_FAQ, INTENT_PRODUCT, INTENT_POLICY, INTENT_SUITABILITY)
# 公开问答 产品信息 政策解释 适当性裁决
VISITOR_INTENTS = (INTENT_FAQ, INTENT_PRODUCT, INTENT_POLICY, INTENT_CHITCHAT, INTENT_TRANSFER)
# 公开问答 产品信息 政策解释 闲聊 转人工
```
| | 客户侧 | 游客侧 |
|---|---|---|
| 公开问答 `faq` | ✅ | ✅ |
| 产品信息 `product_inquiry` | ✅ | ✅ |
| 政策解释 `policy_explain` | ✅ | ✅ |
| **适当性裁决 `suitability_check`** | ✅ | ❌ **刻意不给**(需登录、需持仓/测评,且属投资建议边界) |
| 闲聊 / 转人工 | ✅ | ✅ |
**⇒ 因此不需要「为游客建 Agent」**:范围收窄是通过 **`VISITOR_INTENTS` 白名单(同一 Agent 内的意图子集)** 实现的,不是通过独立的 Agent 实现的。重建时这个子集**必须原样保留**——它同时是 MVP 第 1 步「游客无投资建议类内容」的落点之一(与 C-09 配合)。
### 2.2 现状(清除后的连带影响,3 处)
| # | 事实 | 取证 | 性质 |
|---|---|---|---|
| 1 | 已注册 7 个 Agent **无一接受 `visitor`**(`fund_query_demo`/`risk`/`offsite_fund`/`promotion_material`/`platform_probe`/`advisor`/`financial_nl2sql`);`customer_service` 是唯一 `allowed_roles=("visitor","customer")` 者 | `implementations/*.py` + `bootstrap.py:423-452`;原定义见 `_cs_purge_backup/.../customer_service.py:193-206` | **客服被删的必然结果**,非独立缺陷 |
| 2 | active `config_release`(id=254)9 条 `agent_tools` 中 **4 条是 `customer_service:*` 死条目**,**`query_knowledge`(游客线工具名)一条都没有** | `docs/evidence/release-state.json`(2026-09-11 快照,中证据) | 同上(白名单 key 是 `customer_service:*`) |
| 3 | `tools/publish_{customer_service_config,profile_tool_whitelist,chitchat_prompt}.py` **三个发布脚本已删** | `tools/` 现存仅 `publish_{advisor_demo,financial_nl2sql,risk_agent}_config.py` | **重建客服的组成部分**(重发配置需要脚本) |
### 2.3 无需做的事 / 必须做对的事
| | 内容 |
|---|---|
| ❌ **无需做** | **不新建「游客 Agent」**、不加 `visitor_id`(决 7)、不为游客单开白名单 key、不新开接口路径。游客的权限域是**凭证不是路径** |
| ✅ **必须做对** | ① 重建的 `customer_service` 的 `AgentDefinition.allowed_roles` **必须含 `"visitor"`**(照抄原定义:`("visitor","customer")`);② `allowed_tools` **必须同时含 `search_knowledge` 与 `query_knowledge`**;③ 重发的 `agent_tools/customer_service:<intent>` 白名单**两个工具名都要在**(缺 `query_knowledge` → 游客一问即失败);④ 客服浮窗随前端重建时重新挂到 `app-shell.js` |
### 2.4 对 MVP 的影响(更正版)
| 游客能力 | 现状 |
|---|---|
| 浏览公开产品信息(`guest/home`、`guest/products`、`guest/product-detail`) | ✅ **可用**,且**不经 Agent**、不受客服删除影响 |
| 取访客令牌 | ✅ **可用** |
| **用客服浮窗对话** | 🔴 不可用(因为 `customer_service` 被删 + 浮窗已删)→ **随客服重建自然恢复** |
> **更正**:本报告初稿写「MVP 第 1 步(游客)演示不了」。**更准确的说法是**:第 1 步若只要求「浏览公开产品且无投资建议类内容」→ **现在就能演示**;若要求「游客能向客服提问」→ 需等客服重建 + 浮窗重新挂载。**这属于客服模块自身的工作量,不构成额外前置项。**
### 2.5(已清除)
> 本节原为「能否不对访客做鉴权」的讨论结论,**已按用户要求清除**,以待「访客与角色分离」的新设计重写。
> 原文归档:`开发文档/归档-访客鉴权与访客分层讨论-已清除-2026-09-16.md`。
> 新的鉴权方案建议:`开发文档/D3.3-访客与角色分离的鉴权方案建议-2026-09-16.md`。
### 2.6 附:7 个已注册 Agent 的角色矩阵(取证留档)
| Agent | `allowed_roles` | 是否接受访客 |
|---|---|---|
| `fund_query_demo` | customer, advisor, operator, admin | ❌ |
| `risk` | risk_operator, admin | ❌ |
| `offsite_fund` | operator, risk_operator, admin | ❌ |
| `promotion_material` | operator, admin, super_admin | ❌ |
| `platform_probe` | admin | ❌ |
| `advisor` | customer, advisor, operator, admin | ❌ |
| `financial_nl2sql` | advisor, operator, admin, super_admin | ❌ |
| ~~`customer_service`~~ | **visitor, customer** | 🗑️ **唯一一个**(重建时必须原样恢复) |
取证:`app/service/agent/implementations/*.py` 的 `AgentDefinition.allowed_roles`;`app/service/agent/bootstrap.py:423-452` 的 7 处 `factory.register(...)`;原客服定义见 `_cs_purge_backup/app/service/agent/implementations/customer_service.py:193-206`。
**这张表的用途**:它说明「游客的对话能力」**不属于任何独立模块**,而属于 `customer_service` 这一个 Agent 的 `allowed_roles` 与发布白名单。**重建时漏掉 `"visitor"`,游客对话就会静默失效(症状为「请登录」)**——这是 P-9 的全部理由。
### 2.7(已清除)
> 本节原为「把访客做到服务层」的落点取证与三项新发现,**已按用户要求清除**,以待「访客与角色分离」的新设计重写。
> 原文归档:`开发文档/归档-访客鉴权与访客分层讨论-已清除-2026-09-16.md`。
> 新的鉴权方案建议:`开发文档/D3.3-访客与角色分离的鉴权方案建议-2026-09-16.md`。
---
## 3. 执行纪律(本报告新增,6 条)
这 6 条是我对本次重构的判断,落为纪律。**第 3、5 条与 v5.0 的技术判断不同,请优先看。**
### 纪律 1 · 先修订落点,再开工
v5.0 有 20 项落点指向已删除的文件。**不修订就开工,最坏情况是有人照着 v5.0 从 git 历史恢复旧实现**——那会把 6 个已核实的缺陷(详见 MODE 记忆与 CS-REFACTOR-2026-006)原样带回来,其中 `_visibility_filter` 的静默降级和恒置 `transfer_required=True` 都是**不报错的**缺陷。
> ⚠️ **`F-01` 尤其危险**:v5.0 写的是「修改 `_load_mysql_prior_context()` L98-116 增加主体过滤」。该方法已被清除,而**备份里的那份正是缺陷本体**(只按 `session_id` 取历史、跨主体可读)。正确动作是「**在新受理链路里重新实现含主体过滤的版本**」,不是「把备份里的搬回来再加过滤」。
### 纪律 2 · 做 D 批次之前,先把依赖装上、跑通 A-02
**这是最被低估的风险。** 现在全项目的验证能力只有「610 个 `.py` 语法 0 错误 + 悬空 import 0 条」。**语法正确 ≠ 能跑。**
而 `D-02` 的失败模式恰恰是**静默降级**:过滤器表达式改错后 → 缺 `visibility` 的集合被 Milvus 报错 → 被 `except` 吞成 `failures` → `degraded=True` → **客服一律转人工且不报错**。**这种失败,静态扫描 100% 抓不到。**
v5.0 每一项都有「验证」列,但 `pytest`/`ruff`/`mypy` 现在全部跑不了 → **那些验证列目前是空头支票**。不补依赖,§7 完工判据第 7 条(门禁干净)无法验收。
### 纪律 3 · 顺序:先重建客服,再删投顾 ← 与用户原计划不同
用户原计划是「先删投顾,再重建客服」。**建议调整为「先重建客服,投顾留到最后」**,三条理由:
1. **投顾是当前唯一完好的业务线**,是重建客服时改动 6 个底座文件的**对照组**。任何回归,必须拿另一个 Agent 做 A/B 才能判断「是我改坏的」还是「本来就坏的」。
2. **两个模块同时处于「拆了没装回」的状态时,演示一失败,你连它属于哪一层都判断不出来。**
3. 投顾前端 2045 行,`客服与投顾模块重构前代码清理建议-2026-09-15.md` §四.7 **本就写着「建议单独立项」**——它不该和客服塞在同一批。
> 若因答辩时间必须并行:**至少保证投顾的 9 步主干在客服重建期间是绿的**,不要动它。
### 纪律 4 · 重建路径选「丙」:备份当参考实现,不当基线代码
| 方案 | 好处 | 代价 | 结论 |
|---|---|---|---|
| 甲 · 全量恢复 `_cs_purge_backup/` 再改 | 起步快 | 把 6 个已核实缺陷原样带回 | ❌ |
| 乙 · 完全从零写 | 干净 | 丢掉 748 行里大量「**为什么这么写**」的决策注释——**那是资产**;且要重新踩坑 | ❌ |
| **丙 · 先做 D 批次护栏 → 再写业务层,写时对照备份** | 护栏先有、注释可搬、**缺陷不回流** | 需人工甄别「哪些注释该搬」 | ✅ **建议** |
**具体做法**:每搬一段代码前问一句——「这段是不是那 6 个已核实缺陷之一?」(`_visibility_filter` 无差别应用 / 恒置 `transfer_required=True` / 访客随机 id 写进 `customer_id` / 工单 `priority` 无赋值 / `_references()` 死代码 / 访客侧「不得提供投资建议」零实现)。
### 纪律 5 · 立刻做两件「不依赖任何代码」的事
| 动作 | 为什么现在就能做 |
|---|---|
| **`F-06` 产品证据链 dry-run** | 🔴 **当前唯一硬阻断点**(方案里无产品 → 演示第⑥⑦步跑不出 `candidate_count > 0`)。脚本 `tools/sync_nanfang_official_product_governance.py` **已确认存在**,dry-run 不依赖任何重建。**越早跑越有余量走降级阶梯** |
| **`B-01` + `B-02` 语料修正** | 纯数据层(`knowledge/` 源文件),**不碰代码、不依赖会签**。语料里 308 块自称「南方科技」、66 块含 `400-XXX-XXXX` —— 这些是**客户可见**的错,且重建后要重灌库,**必须先修再灌**(串行约束 S-1) |
### 纪律 6 · 本报告不新增任务编号,用前置项 P-6~P-9 承载
v5.0 有「附录A 编号映射」的纪律(编号变动会让既有引用失效)。因此新增事项**不插进 A~F 批次**,而是作为**全局前提 P-6~P-9**(承接 v5.0 的 P-1~P-5),在 §5 定义。
---
## 4. 修订后的执行清单(v5.1)
### 4.1 批次 A · 冻结与准备(10 项)
| ID | 任务 | v5.0 落点 | **v5.1 落点 / 状态** | 变更说明 |
|---|---|---|---|---|
| **A-01** | 安全用例集基线复现 | 对**现存客服 Agent**跑 RT-001~010 | ⚠️ **改为「只登记预期、不跑实测」** | **被测对象已不存在**,无从执行。改为:以 `docs/客服Agent一期_合规红队与业务评测集_v1.md` 为准登记「预期结果」,把**实测**整体后移到 `C-06` 之后(重建出 Agent 才有对象) |
| **A-02** | 门禁数字基线 | 仓库根 | ✅ **不变**,但**必须先装依赖**(纪律 2) | 5 条命令:`ruff` / `mypy app` / `pytest tests/unit tests/contract` / `pytest tests/integration` / `tools/audit_schema.py`(预期 89 表 = 零 DDL 对照) |
| **A-03** | 配置与环境快照 | `customer_service:*` 的 `agent_tools` 白名单全文 | ⚠️ **快照对象改写**:① 现存 9 条 `agent_tools`(含 4 条 `customer_service:*` 死条目);② **确认 `query_knowledge` 是否在白名单**(当前证据:不在);③ **确认投顾线靠哪条 key 在跑**;④ 三集合字段名与行数;⑤ embedding 模型名 + 维度(预期 1024) | 客服 `agent_type` 已注销 → 其白名单当前**不存在**。快照改为「记录现状 + 标出缺口」,为 `P-7` 重发配置提供对照 |
| **A-04** | 业务一致性三查基线 | 预期 `15936583816` 代码 **2 处**(`customer_service_rules.py:35` + `governance.py:46`) | ✅ 保留,**但预期值校正为「代码 1 处」**:仅 `app/service/agent/governance.py:47` | 另一处随模块删除。**若不校正,基线数字对不上会被误判成「改坏了」** |
| **A-05** | 演示前检查清单固化 | `docs/44-演示流程.md` 同款 | ✅ 不变 | 产物是文档;执行在 F-04 |
| **A-06** | 分支与 PR 策略 | `qyqy_develop` → `cs-refactor` | ✅ 不变(⚠️ 本机 git 写入受沙箱限制,需用户在正常终端执行) | 底座会签项单独一个 PR |
| **A-07** | 🔴 访客链路可用性实测 | 访客令牌 → `agent-runs` → `query_knowledge` | ⚠️ **改为「实测前置条件 + 待执行标记」**:① 本项的四条观测点(白名单是否含 `query_knowledge`、是否抛 `ForbiddenAgentError`、`context.user_id` 形态、日志能否区分「配置错」与「正常引导登录」)**全部保留**;② 但**实测时机后移**——`customer_service` 重建前无对象可测(游客对话 100% 失败,且原因是「Agent 未注册」,测不出别的);③ 本项在 A 批次内的产出改为:**确认 §2.2 的 3 处现状 + 把四条观测点登记为 P-7/P-9 的验收条目** | 观测点保留、时机后移。同时记录「哪些日志能区分『配置错』与『正常引导登录』」(承接 E-02) |
| **A-08** | 前端约束与配合点复核 | `docs/41-...前端开发约束_v1.md`、`widget.js`、`app-shell.js` | ⚠️ **改写**:前两个文件**已删除**;核对面收窄为 `app-shell.js` + `guest/**` 页面 + **`visitor-token.js`**;新增产出「**重建前端时须重新产出一份约束文档**」 | 结论可以是「0 项」,但必须是**核对后**的 0 项 |
| **A-09** | 🔴 《可改文件白名单》落文 | 第②类列了 `customer_service.py` / `customer_service_rules.py` / `session_memory_service` / 转人工三件套 / `customer-service-widget/**` | ⚠️ **改写**:第②类**清空** → 前 4 项已删除,**全部并入第①类「纯新增」**;转人工三件套**移入第④类「禁止修改」**(形态A有意保留的共享资产);第④类**新增** `docs/customer-service-routing-legacy-keywords.md`(重建验收基线,不得删) | 不清理会让白名单指向不存在的文件 |
| **A-10** | 🔴 底座会签申请单落文 | §1.1 的 6 处 + §1.2 的 1 处条件触发 | ✅ **不变**(6 处落点已逐处确认仍在) | ⚠️ **新增第 4 项受影响 Agent**:`_mask_mobile` 白名单分支(见 P-8) |
### 4.2 批次 B · 语料与话术(5 项,串行)
| ID | 任务 | v5.0 落点 | **v5.1 落点 / 状态** | 变更说明 |
|---|---|---|---|---|
| **B-01** | 🔴 语料入库门禁(四查) | `tools/build_knowledge_chunks.py` | ✅ **不变** | 四条件:占位符 / 品牌白名单(`南方财富`+`南方基金`+`nanfangwm.com`)/ 禁止类目 / **每块 `visibility` 必须显式声明**(承接 D-07 降级) |
| **B-02** | 🔴 语料一次性修正 | `knowledge/` 5 源文件 + `SOURCES` | ✅ **不变** | 品牌名 / 热线 `400-826-9518` / 服务时间 / HNW 第二章下线 / 新增 `docs/43`+`docs/45` 入 SOURCES |
| **B-03** | `P1_REPLY` 界面指代修正 | `customer_service_rules.py` + `test_customer_service_rules.py:154` | ⚠️ **改为「新建时即写对」**:两文件均已删除 → 本项并入「重建 `customer_service_rules.py`」的动作,DoD 增加「话术里的页面名必须存在于 `app-shell.js` 链接表」的**新断言** | 从「改」变「写」 |
| **B-04** | 🔴 重跑灌库 + 四查验收 | `build_knowledge_chunks.py` → `load_knowledge_milvus.py` | ✅ **不变**(六条验收标准全部保留) | ⚠️ 跑前**先停 Worker** |
| **B-05** | 热线与服务时间单点化 | 五组落点(①真源 `customer_service_rules.py`、②`governance.py`、③数据层种子、④3 个测试、⑤前端) | ⚠️ **重排**:<br>① **「真源」已随模块删除 → 改为「重建时先建真源」**(`CONTACT_PHONE`/`CONTACT_HOURS`);<br>② **`governance.py` 部分提前为 `P-8`**(独立前置,清 `CUSTOMER_SERVICE_HOTLINE` + `_mask_mobile` 分支);<br>③ 数据层 `tools/seed_compliance_baseline.py:99,101` **不变**;<br>④⑤ 3 个测试 + 前端文案**随重建一并写对** | ⚠️ **串行约束 S-2 改写**:原为「B-05 热线定值 → B-02 填值」。现真源未建 → S-2 改为「**P-8 清旧副本 → B-02 填值 → 重建时建真源**」 |
### 4.3 批次 C · 规则重构与安全(9 项)
| ID | 任务 | v5.0 落点 | **v5.1 落点 / 状态** | 变更说明 |
|---|---|---|---|---|
| **C-01** | 拆输入/输出两套常量 | `customer_service_rules.py:47-62` | ⚠️ **改为「新建该文件时即按两套常量写」**:<br>① `ZERO_TOLERANCE_WORDS` **保持原名与 11 条内容**(输入侧零容忍集,名字准确);<br>② 新增 `INPUT_PROMISE_WORDS`(8 条字面承诺词)+ `INPUT_PROMISE_PATTERNS`(句式),由 `hits_zero_tolerance()` 一并消费 | 语义校正(v5.0 §9 校正 5)**继续有效**;只是从「改名/改内容」变成「一开始就写对」 |
| **C-02** | 输入侧改句式判定 | `customer_service_rules.py` | ⚠️ **改为「新建时即写对」**:三条句式规则(收益率问法限定时 / `安全`+承诺词 / 保留 `YIELD_TRAP_PATTERNS` 四条不动) | 同上 |
| **C-03** | 🔴 输入侧补安全缺口 | `customer_service_rules.py` | ⚠️ **改为「从留痕文档取回词表后新建」**:<br>① 移植提示词注入 **8 条**(插在 P0 之后、合规拦截之前);<br>② 补「推荐」「收益最高」「纠纷」;<br>③ 补裸词账户问法;<br>④ `_CHITCHAT_MESSAGES`/`_CHITCHAT_PHRASES` 一并重建(原在 `customer_service_routing.py`,该文件已删)<br>🔴 **词表唯一副本 = `group_fqcd_jr/docs/customer-service-routing-legacy-keywords.md` §二,禁止凭记忆重写** | **这是本批次最高风险项**。不取回 → 红队 RT-009/010 必然失败 |
| **C-04** | 🔴 输出侧补豁免线索〔需会签〕 | `app/core/compliance_context.py:43-48` | ✅ **落点完全有效**(已取证:`NEGATION_CUES` 确不含 `不保本`/`不保收益`/`非保本`) | 补多字短否定式 + 指标名白名单(同句生效);**禁止加裸「不」** |
| **C-05** | 🔴 输出侧收益比较类规则〔数据层〕 | DB `agent_negative_word` 种子 | ✅ **不变**(零代码)。三条硬约束照旧 | 跑 `test_compliance_seed_mysql.py` 对账 |
| **C-06** | 🔴 双向验证(放行闸门) | 红队集 + `test_customer_service_rules.py` + `test_compliance_context.py` | ⚠️ **改写**:前两个测试**已删除** → 随重建一并写。**四方向用例一个都不能少**:<br>A 放开(3 条可答)/ B 收紧(5 条诱导性问法仍拒答)/ C 访客(问「推荐一只基金」不产生推介)/ **D 反例守卫**(`本产品不保本` 不判违规;`严禁承诺保本。但这只基金保本` **仍判违规**) | + **A-01 的红队实测在此落地** |
| **C-07** | 删除一期确定性路由 + 兼容桩 | `customer_service_routing.py`、`customer_service_agent.py` | 🟢 **已完成**(由形态A执行) | `customer_service_routing.py` 在 `_cs_delete_list.txt` 内;`customer_service_agent.py` 已无踪迹。**词表已留痕**(`docs/customer-service-routing-legacy-keywords.md`) |
| **C-09** | 🔴 访客侧推介边界 | `customer_service_rules.py`、`customer_service.py`、`compliance_context.py` | ⚠️ **改写**:前两个文件已删 → 业务侧「**新建时即实现**」;`compliance_context.py` 部分**归入 C-04 的会签**。<br>🔴 **同时补上 v5.0 漏掉的一环**:访客线**前提是 P-9**(Agent 必须接受 visitor)——**P-9 不做,本项无从验证** | 输入侧识别访客推介句式 + 输出侧按 `subject_type` 分化;客户侧行为不变(回归验证) |
| **C-10** | `source_references` 定向落地或降级 | 〔甲〕`tool_executor.py` + `governance.py`;〔乙〕`docs/` + 护栏断言 | ✅ **不变**(v5.0 §10 已定稿:请底座方提供,否则降级乙) | ⚠️ **串行约束 S-8 保留**:禁止在知识出口启用 `_references()`,否则整个 run 失败 |
### 4.4 批次 D · 架构护栏(7 项)—— **整批落点有效,未受清除影响**
> ✅ 已取证:`knowledge_search_service.py` 的 `_visibility_filter`(L138)、`include_internal: bool = False`(L162)、`expression` 应用点(L202)、`_hit_from_row`(L488)**全部原样在位**;`knowledge_tool.py:25` 的 `del context` **仍在**,`search()` 调用**确无 `tiers` 参数**。
| ID | 任务 | 落点 | 状态 |
|---|---|---|---|
| **D-01** | 检索签名 fail-closed〔需会签〕 | `knowledge_search_service.py:138-163` | ✅ 不变(`include_internal` → `tiers: frozenset[str]`;**不改 `knowledge_contracts.py`**) |
| **D-02** | 🔴 按集合逐个拼过滤表达式〔需会签〕 | 同上 L138-154 / L202-217 / L488 | ✅ 不变(`filter=expression if schema.has("visibility") else None`;修 L153 不符的注释;`_hit_from_row` 缺字段**不再回落 public**;补独立计数) |
| **D-03** | over-fetch + 独立指标 | 同上 | ✅ 不变 |
| **D-04** | 档位判定纯函数 + handler 内取身份〔需会签〕 | `knowledge_tool.py:25` | ✅ 不变(删 `del context`;`context.roles` 推导档位;`visitor → {public}`)**← 与 P-9 直接相关** |
| **D-05** | 缓存键含档位 | 检索/embedding 缓存 | ✅ 不变 |
| **D-06** | 访客主体口径统一〔需会签〕 | `agent_run_application_service.py` + `agent_persistence_service.py:93` | ✅ 不变(访客 `customer_id = NULL`,零 DDL) |
| **D-07** | API 入库路径档位(已降级) | 不碰 `milvus_knowledge_writer.py`;落点为 B-01 第四查 + `docs/` 登记 | ✅ 不变 |
### 4.5 批次 E · 业务闭环与出口(8 项)
| ID | 任务 | v5.0 落点 | **v5.1 落点 / 状态** | 变更说明 |
|---|---|---|---|---|
| **E-01** | 🔴 工单建单 + priority + reason_code〔需会签〕 | `agent_persistence_service.py:82-153`(底座,✅有效)+ `customer_service.py:685-700` + `customer_service_rules.py` | ⚠️ **底座侧不变**;业务侧两文件**已删除** → 「新建时即实现」。<br>🔴 **白名单必须限定 `run.agent_type == "customer_service"`**(否则改到风控/投顾) | 底座 `complete_run` 未被触碰,取证通过 |
| **E-02** | `auth_required` 运行时意图 | `customer_service.py` | ⚠️ 改为「新建时即实现」 | 复用既有出口 `_guide_to_login(reason)`;按 reason 区分日志 |
| **E-03** | 工单可见方落文 | `docs`(无需新增端点/权限码) | ✅ 不变 | — |
| **E-04** | 适当性「不匹配告知 + 主动确认」 | `customer_service.py` `_answer_suitability` else 分支 | ⚠️ 改为「新建时即实现」 | 留痕落 `conversation_message.tool_calls` + `interaction_audit`(零 DDL) |
| **E-05** | `_topic_of` 文本反解收口 | `customer_service.py` 出口结构 | ⚠️ 改为「新建时即实现」:出口**显式声明主题** | 该机制已咬 5 次 |
| **E-06** | 长答案截断提示 | `customer_service.py:339` | ⚠️ 改为「新建时即实现」(`POL-AST-009` 长 2828 字符且高频命中) | 加提示或走「整节 + 可追问」 |
| **E-07** | ⏸ `registered` 档位内容标注 | `build_knowledge_chunks.py` 的 `SOURCES` | ⏸ **保持挂起**(决 10) | 唯一候选已因合规下线 |
| **E-08** | 🔴 三条红线客服侧对照验证 | `customer_service.py`、`customer_service_rules.py`、`docs/` 留痕 | ⚠️ 落点改为「新建的产物 + 留痕」 | 缺口按「**补拦截**」而非「补话术」处理 |
> 📌 **E-02/E-04/E-05/E-06/E-08 同文件**——它们现在不是「改同一个文件」,而是「**写同一个文件**」。**建议合并为一次成型**(写 `customer_service.py` 时一次把 5 项都做进去),而不是分 5 次改。
### 4.6 批次 F · 演示与收口(7 项)
| ID | 任务 | v5.0 落点 | **v5.1 落点 / 状态** | 变更说明 |
|---|---|---|---|---|
| **F-01** | MySQL 回退路径主体隔离〔需会签〕 | `agent_run_application_service._load_mysql_prior_context()` L98-116 | 🔴 **改写**:该方法**已被形态A删除**。→ 「**在新受理链路里重新实现**含主体过滤的版本」,主体判据**必须调用 `app/core/memory_scope.py`**(N-9)。<br>🔴 **禁止从 `_cs_purge_backup/` 恢复旧实现**——那份**正是缺陷本体**(只按 `session_id` 取、跨主体可读) | **本报告最需要提醒的一项** |
| **F-02** | 访客专项端到端验证 | 手工清单 + 脚本 | ✅ 保留,**前置 = P-7 + P-9**(两者缺一,五条全跑不了) | 越权 / 限流 / 会话隔离 / 登录后上下文继承 / **推荐类请求不产生投资建议** |
| **F-03** | 🔴 MVP 九步演示脚本与彩排 | `docs/44-演示流程.md` 同款 | ✅ 不变 | ⚠️ 第 1 步「游客浏览无投资建议类内容」**若只到「浏览」则不依赖客服**;**若含「向客服提问」则依赖 P-7 + P-9 + 浮窗重新挂载** |
| **F-04** | 演示前五项自检 + 账号速查 | `tools/seed_*.py` | ✅ 保留;⚠️ `tools/seed_demo_data.py` 已移除客服两步,**账号速查表可能缺客服账号** → 需确认 | 用现有 seed 生成客户/访客/管理员三类账号 |
| **F-05** | 文档回写 v2.3 | `D3.1-客服Agent需求开发文档与设计方案.html` + `docs/**` | ✅ 保留,**新增 5 项必写内容**(见下) | — |
| **F-06** | 🔴 产品证据链(决 13 · 甲) | `tools/sync_nanfang_official_product_governance.py` | ✅ **不变,且脚本已确认存在** → **立即可跑 dry-run**(纪律 5) | 唯一硬阻断点 |
| **F-07** | `D7.1-需求文档.html` 24 个失效锚点 | `D7.1-需求文档.html` | ✅ 不变(只改 `href`,不动正文 `id`) | — |
**F-05 新增的 5 项必写内容**(v5.0 成文时尚不存在):
1. **形态A清除的完整记录**(34 删 / 17 改 + 备份位置 `_cs_purge_backup/`)——否则下一轮读文档的人会以为这个模块「从未有过」;
2. 🔴 **安全能力当前为零**:提示词注入拦截 8 条 / 凭据披露短语 / 安全关键词路由 + 零容忍词 11 条,**在生效路径上不存在**;词表唯一副本在 `docs/customer-service-routing-legacy-keywords.md`;
3. 🔴 **游客链路已断**(本报告 §2):无 Agent 接受 `visitor` 角色 + `query_knowledge` 不在发布白名单 + 三个发布脚本已删;
4. **`governance.py` 热线副本的处置结果**(P-8)与 `PROFILE_CANDIDATE_AGENT_TYPES` 的当前值(空集合,重建后填回);
5. **重建验收基线两份文档不得被清理**:`docs/客服Agent一期_合规红队与业务评测集_v1.md`、`docs/customer-service-routing-legacy-keywords.md`。
---
## 5. 需补写清的事项 P-6 ~ P-9(承接 v5.0 的 P-1~P-5)
> **定性说明(2026-09-16 更正)**:这 4 项**不是新增能力、也不是独立模块**,而是「**重建 `customer_service` 时必须一并做全的既有属性**」。它们此前散落在 v5.0 的 A-03 / E-02 / F-04 里没说清,或随模块删除而失去载体(P-6/P-7 的脚本被删)。
> **特别地:不存在「游客 Agent」这个待建项**——游客与客户共用同一个 `customer_service`,见 §2。
| # | 事项 | 落点 | DoD | 何时做 | 风险 |
|---|---|---|---|---|---|
| **P-6** | **重建客服发布脚本** | `tools/` 新增(模板:`tools/publish_risk_agent_config.py`,其文件头注明「与 `publish_customer_service_config.py` 同源」) | ① 发布 `customer_service:<intent>` 工具白名单;② 发布画像工具白名单;③ 发布闲聊提示词。**三个被删脚本的功能必须补回**,且遵从 `AGENTS.md:163` 的「同 key 继承项必须被本次定义覆盖」(该教训原本就挂在被删的 `publish_customer_service_config.py` 上) | **批次 A 之后、D 之前** | 中(缺它则 active release 无法重发) |
| **P-7** | **重发 `config_release`** | 环境数据(`config_release` / `platform_config_item`) | 新版配置须:① 移除 4 条 `customer_service:*` 死条目(或随重建替换);② 🔴 **同一条 `customer_service:<intent>` 白名单里必须同时含 `search_knowledge`(客户)与 `query_knowledge`(游客)**——这是 `README.md:30-33` 的明文要求,也是 N-7 的真实原因;③ 确认投顾线 key(`advisor:*` 在 9 条快照里一条都没有,需实测澄清);④ 发布后 A-03 快照对比 | **A-07 之前** | 中高(缺 `query_knowledge` → **游客一问即失败**;且失败表现为「请登录」) |
| **P-8** | **清除 `governance.py` 的热线孤岛**(原 B-05 ②,提前) | `app/service/agent/governance.py:47` + `_mask_mobile` 分支 L335-342 | 删 `CUSTOMER_SERVICE_HOTLINE = "15936583816"`;`_mask_mobile` 内联为 `return "[手机号已脱敏]"`。<br>**理由(取证)**:`customer_service_rules.CONTACT_PHONE` 已删 → 此常量现为**全仓唯一热线副本**,且分支只对旧号码生效。若重建话术改用新号 `400-826-9518`:① 分支成**不可达死代码**(新号不匹配 `1[3-9]\d{9}`);② **旧号码若出现在输出里会被原样放行、不脱敏** | **重建之前(独立小改动,可立即做)** | 低(无单测断言该常量;脱敏口径只可能更严) |
| **P-9** | 🔴 **重建的 `customer_service` 必须保持 `allowed_roles=("visitor","customer")`**(**保持原定义,非新增**) | 新建的 `app/service/agent/implementations/customer_service.py` 的 `AgentDefinition` | 照抄原定义(`_cs_purge_backup/.../customer_service.py:193-206`):① `agent_type="customer_service"`;② **`allowed_roles=("visitor","customer")`**;③ `allowed_portals=("api",)`;④ `allowed_tools` 含 `search_knowledge` + `query_knowledge` + `check_suitability` + `query_customer_profile`;⑤ `supported_intents` 六项齐全(faq / product_inquiry / policy_explain / suitability_check / chitchat / transfer_human);⑥ `recalls_customer_memory=False`(客服不隐式召回长期画像——原值,重建须保留);⑦ **`VISITOR_INTENTS` 子集原样保留**(FAQ/PRODUCT/POLICY/CHITCHAT/TRANSFER,**不含 `suitability_check`**,见 §2.1b) | **随客服 Agent 重建时生效** | 🔴 **高**——漏 `"visitor"`,游客对话**静默失效**(症状为「请登录」,与设计如此无法区分) |
> **P-9 的取证依据**:`_cs_purge_backup/app/service/agent/implementations/customer_service.py:193-206`(原 `AgentDefinition` 全文)+ 现存 7 个 Agent 的 `allowed_roles` 逐一无 `visitor`(§2.6 矩阵)。
---
## 6. 关键路径与串行约束(v5.1 修订)
### 6.1 关键路径
```
[会签窗口:A-10 提交 → 底座方受理 §1.1 的 6 处] ──┐
↓
P-8 → A-02 → A-07 → D-01 → D-02 → D-04 → D-06 → E-01 → E-03 → F-03 → 演示跑通
↑
B-01 → B-02 → B-05 → B-04 ───────────────┤
│
C-01 → C-03 → C-09 → C-06 ───────────────┘
独立支线(不阻塞主干,但阻塞「游客能对话」这条演示要求):
P-6 → P-7 → A-07(访客实测)
P-9(随客服 Agent 重建一并生效;F-02 / F-03 第 1 步若含对话则依赖它)
```
**最长链仍为 9 步**(`A-02 → A-07 → D-01 → D-02 → D-04 → D-06 → E-01 → E-03 → F-03`)。
**新增**:`P-8` 是**可立即独立完成**的小改动(建议今天就做);`P-6/P-7` 是**重发配置所需的脚本 + 配置本身**(客服重建的组成部分,不是额外模块)。
### 6.2 串行约束修订(v5.0 九条 → 保留 8 条 + 改写 1 条 + 新增 1 条)
| # | 约束 | 变化 |
|---|---|---|
| S-1 | B-01 门禁 → B-02 改语料 → B-04 重跑 | ✅ 不变 |
| S-2 | ~~B-05 热线定值 → B-02 填值~~ | ⚠️ **改写**:**P-8 清旧副本 → B-02 填值 → 重建时建真源**(真源 `customer_service_rules.py` 已删) |
| S-3 | ~~C-06 验证 → C-07 删除~~ | 🟢 **已消解**:C-07 由形态A完成、词表已留痕。**新约束**:**C-03 必须先取回留痕词表再写**(`docs/customer-service-routing-legacy-keywords.md` §二) |
| S-4 | D-02 修过滤表达式 → 任何集合加 `visibility`/改字段名 | ✅ 不变 |
| S-5 | D-01 fail-closed → D-04 传身份 | ✅ 不变 |
| S-6 | D-03 over-fetch 与 D-01/D-02 同批 | ✅ 不变 |
| S-7 | 集合 schema 变更后必须重启 API + Worker | ✅ 不变(`get_knowledge_search_service()` 是 `@lru_cache` 单例) |
| S-8 | 禁止在知识出口启用 `_references()` | ✅ 不变 |
| S-9 | 🔴 底座 6 处未获会签前不得动手 | ✅ 不变 |
| **S-10** | 🆕 **`allowed_roles` 未含 `"visitor"` 或白名单未含 `query_knowledge` → 不得宣称「游客线可演示」** | **新增**(游客对话的两处开关,对应 P-7 + P-9) |
---
## 7. 整体完工判据(v5.1 修订)
v5.0 的 10 条**全部保留**,其中 4 条需要调整或补充:
| # | 判据 | v5.1 调整 |
|---|---|---|
| 1 | 业务一致性三查全绿 | ✅ 不变(0 个 `XXX` / 0 个「南方科技」/ 0 个 `nanfangtech` / 0 个 `400-XXX-XXXX`;`app/ tests/ tools/ knowledge/` 内无 `15936583816`) |
| 2 | 安全只增不减 | ✅ 不变(红队 RT-001~010 与 A-01 登记基线逐条对比,**无一条变松**) |
| 3 | 规则三个方向都对 | ✅ 不变 |
| 4 | 护栏生效 | ✅ 不变(访客检索 `registered` 返回空、客户正常;公开问题召回量与基线一致;混合 schema 与字段名差异两类替身测试通过) |
| 5 | 访客主体一致 | ✅ 不变(`conversation_message.customer_id IS NULL` = 访客) |
| 6 | 工单分类正确 | ✅ 不变(**其它 Agent 的建单行为未变**) |
| 7 | 门禁干净 | ⚠️ **补充**:`ruff` / `mypy` / `pytest` **必须先装依赖才能验**(纪律 2);判据是 **0 failed**(绝对用例数会随重建增减) |
| 8 | 演示跑通 | ⚠️ **改为条件式**:第 1 步若只要求「浏览公开产品且无投资建议类内容」→ **不依赖客服重建即可演示**;若要求「游客能向客服提问」→ 需 **P-7 + P-9 已完成**,另需客服浮窗重新挂载 |
| 9 | 红线可举证 | ✅ 不变 |
| 10 | 🔴 底座纪律达标 | ✅ 不变,**补 1 项证据**:`P-8` 的热线副本已清除(或明确登记为已知未修) |
| **11** | 🆕 **游客对话已通**(原写「游客链路已恢复」) | **新增**:① `customer_service.Allowed_roles` 含 `"visitor"`(P-9);② `agent_tools/customer_service:<intent>` 白名单含 `query_knowledge`(P-7);③ 访客令牌 → `agent-runs` 返回 202(非 `ForbiddenAgentError`);④ 访客问「我的持仓」被拒、问公开知识可答;⑤ 访客检索只命中 `public` 档 |
---
## 8. 风险登记(v5.1,重点盯防 8 项)
| 排名 | 项 | 最高风险点 | 对策 |
|---|---|---|---|
| **1** | 🔴 **P-9** 恢复 `allowed_roles` 里的 `"visitor"` | 重建时照抄现存 7 个 Agent 的写法(都只有 `customer`)→ 漏掉 `"visitor"` → 游客对话**静默失效**,症状为「请登录」,与设计如此**无法区分** | 把 `allowed_roles` 与 `allowed_tools` **原文照抄原定义**写进 A-09 验收清单;`F-02` 第一条即验 |
| 2 | **D-02** 过滤器按集合逐个拼 | 改错 → 客服**全员转人工且不报错** | 先补两类替身测试再改实现;上线后验「公开问题召回量不变」;保留「缺字段跳过」独立计数 |
| **3** | 🆕 **C-03** 安全词表取回 | 凭记忆重写 → **词条缩水**,且缩水不报错 | **必须从 `docs/customer-service-routing-legacy-keywords.md` §二 逐条抄**;C-06 与 A-01 基线对比 |
| 4 | **C-06** 四向验证 | 收窄输入侧放过诱导性问法;访客方向漏;C-04 补宽放过真实承诺 | 四方向用例都必须有;反例守卫(`严禁承诺保本。但这只基金保本` 仍判违规) |
| 5 | **E-01** 建单白名单 | 不限定 `agent_type` → **改动其它 Agent 的建单行为** | DoD 明确「仅对 `agent_type=="customer_service"` 生效」;把风控/投顾既有建单用例纳入回归 |
| 6 | 🆕 **P-7** 重发配置(两个工具名都要在) | 只发 `search_knowledge` → **客户可问、游客一问即失败**;且失败表现为「请登录」 | 发布后立刻跑 `A-03` 快照对比;`E-02` 按 reason 区分日志 |
| 7 | **D-01 / D-04** 档位隔离 | 业务层自行过滤 → 形成第二套实现,其中一套必漏 | **明确否决**在客服模块内新建检索实现(N-2/N-4/N-6) |
| 8 | **B-04 / F-06** 两次连外部服务 | 环境问题伪装成功能缺陷 | 先跑 A-03/A-05 快照与自检;B-04 前停 Worker;F-06 先 dry-run |
---
## 9. 已拍板(7 项 · 2026-09-17 全部按建议采纳)
| # | 事项 | 我的建议 | 备选 |
|---|---|---|---|
| **1** | 是否采用本报告作为 v5.1 执行依据(取代 v5.0 的 §5/§6/§7/§8) | **采用**(20 项落点非改不可) | 直接在 v5.0 上边做边改 |
| **2** | 是否**先装依赖**跑 A-02 | **装**(否则验证列全悬空、判据 7 无法验收) | 继续只在语法层验证 |
| **3** | 顺序:客服 ↔ 投顾 | **先重建客服、投顾留到最后**(纪律 3) | 按原计划先删投顾 |
| **4** | `P-8`(`governance.py` 热线孤岛) | **立即做**(独立小改动,越晚越容易漏)——⚠️ **但见下方澄清** | 随 B-05 一起做 |
| **5** | 重建路径 | **丙**(先做 D 批次护栏 → 再写业务层,**搬注释不搬逻辑**) | 甲(全量恢复)/ 乙(从零) |
| **6** | 🆕 **访客权威单点化**(`security.py:92-97` ↔ `runtime.py:179-184` 两份逐字副本) | **并入「访客与角色分离」方案一并定**——见 `开发文档/D3.3-访客与角色分离的鉴权方案建议-2026-09-16.md`(原取证见 `归档-访客鉴权与访客分层讨论-已清除-2026-09-16.md`)。两文件均**不在 A-09 白名单**,改动须单独授权;不做则**访客权限无法实时收紧**、受理与执行身份可能不一致 | 保持两份副本,靠注释 + code review 守 |
| **7** | 🆕 **`registered` 档测试块**(证据见归档文件 §2.7-五) | **随 C-06 一并造**——否则 617 块全 `public`,「三档已接通」**无法证伪** | 不造,验收只看公开问答召回量不变 |
> ✅ **决策记录(2026-09-17)**:用户答复「**按照你的建议**」→ 上表 7 项**全部按「我的建议」列采纳**,不再单选。
>
> ⚠️ **关于第 4 项(`P-8`)「立即做」的准确含义**:`P-8` 与 Todolist §1.1 第 4 项(`B-05` 底座侧)**是同一处改动**,
> 而该文件(`app/service/agent/governance.py`)**属于组 1、须会签**。因此「立即做」的准确动作是
> **「立即提案」**——把 `P-8` 单独写进会签申请单先提交,而**不是**直接改 `governance.py`。
> 这是本次唯一需要修正的措辞:`P-8` 在流程上**不独立于会签门**(P-5 / S-9)。
>
> 其中第 6 项(访客权威单点化)已从「待授权」升级为**可执行任务**,并**主动扩张了底座接触面**:
> 它要触碰的文件(`app/core/security.py`、`app/worker/runtime.py`、`app/api/dependencies/auth.py`、`app/service/agent/base.py`)
> **其中 3 个原本在 Todolist §1.5「零改动清单」内**。因此 Todo 侧已把它独立为**批次 G(组 2 会签)**,
> 与 §1.1 的组 1 分开提案、分开 PR。详见《客服 Agent 重构 Todolist》v5.1 §1.6 与批次 G。
>
> **另外一件事需要你确认**:`A-03` 需要**连库实测**当前 active `config_release`(本报告 §2.2 的数据来自 `docs/evidence/release-state.json` 的 **2026-09-11 快照**,属中证据)。若你能提供本机 MySQL/Milvus/Redis 可连的环境,我可以在下一步把 A-03 一次跑完,把 §2.2 的「读码级证据」升级为「运行时强证据」,同时查清「投顾线当前靠哪条配置在跑」。
---
## 10. 本次复核的能力边界(诚实声明)
| 已做(强/中证据) | 未做 |
|---|---|
| **文件系统级取证**:46 项落点是否仍存在 → **强证据** | **未运行任何代码**(无依赖、无 venv) |
| **角色矩阵取证**:7 个 Agent 的 `allowed_roles` 逐一无 `visitor` → **强证据** | 未连 Milvus / MySQL / Redis 核实数据侧 |
| **原始设计取证**:`static/portal/README.md:30-33` 明文写「游客走 `query_knowledge`、客户走 `search_knowledge`、**同一条 `customer_service:<intent>` 白名单**」→ **强证据**(仓库原文,非我推断) | 未验证运行时行为(如访客请求实际错误码、白名单实际内容) |
| **配置快照取证**:`release-state.json` 9 条 `agent_tools` → **中证据**(2026-09-11 快照,非实时) | 未验证「投顾当前靠哪条配置在跑」(A-03 待测) |
| **代码级核对**:`NEGATION_CUES` / `del context` / `_visibility_filter` / `CUSTOMER_SERVICE_HOTLINE` 逐字确认 → **强证据** | — |
**结论**:§1(落点)、§2.1(原始设计)、§2.6(角色矩阵)、§5 P-8/P-9(取证)是**强证据**;§2.2(配置快照)与「某项判断是否仍成立」是**中证据**。§9 表末的 A-03 连库实测通过后,中证据可升级为强证据。**(原 §2.5 / §2.7 已按用户要求清除并归档,其取证同样为强证据,见归档文件。)**
---
## 11. 勘误记录(本报告自身)
| 日期 | 位置 | 原表述 | 更正后 | 原因 |
|---|---|---|---|---|
| 2026-09-16 | §0、§2、§5、§7-11、§8-1/6、§4 的 A-07/F-02/F-03 | 定性为「**游客链路已被切断**」,并称「本次重构 = 重建客服 + 修复被切断的游客链路」;P-9 列为「新增前置项」 | 定性为「**游客侧对话入口随客服 Agent 一起消失**(同一件事,非新链路)」;明确「**游客不需要独立 Agent,与客户共用同一个 `customer_service`**」;P-9 改为「**保持原定义**」 | **用户纠正**:游客只需「公开产品信息 + 一个客服对话」。复核 `static/portal/README.md:30-33` 证实:两者共用同一条 `customer_service:<intent>` 白名单 key,靠令牌角色 + 工具名区分 |
| 2026-09-17 | §9「待你拍板」→「已拍板」;新增批次 G 指引;§4 清单增列 G 组 | 仍以「待拍板 7 项」表述 | 改为「已拍板(7 项全部按建议采纳)」;访客权威单点化升为**可执行任务**,并**显式披露底座接触面扩张**(其 4 个文件中 3 个原在 Todolist §1.5「零改动清单」内) | 用户 2026-09-17 答复「按照你的建议」,并要求落进需求文档 / Todolist / 本报告三份文件;需求文档同步升 v2.3、Todolist 升 v5.1 |