Files
group_fqcd_jr/开发文档/D4.1-客服Agent重构报告-2026-09-16.md
T
张胜宇 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

46 KiB
Raw Blame History

客服 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/...)把意图分成两档——这正是「游客只要公开信息对话」的直接体现:

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 个测试、⑤前端) ⚠️ 重排:
① 「真源」已随模块删除 → 改为「重建时先建真源」(CONTACT_PHONE/CONTACT_HOURS);
② governance.py 部分提前为 P-8(独立前置,清 CUSTOMER_SERVICE_HOTLINE + _mask_mobile 分支);
③ 数据层 tools/seed_compliance_baseline.py:99,101 不变;
④⑤ 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 ⚠️ 改为「新建该文件时即按两套常量写」:
① ZERO_TOLERANCE_WORDS 保持原名与 11 条内容(输入侧零容忍集,名字准确);
② 新增 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 ⚠️ 改为「从留痕文档取回词表后新建」:
① 移植提示词注入 8 条(插在 P0 之后、合规拦截之前);
② 补「推荐」「收益最高」「纠纷」;
③ 补裸词账户问法;
④ _CHITCHAT_MESSAGES/_CHITCHAT_PHRASES 一并重建(原在 customer_service_routing.py,该文件已删)
🔴 词表唯一副本 = 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 ⚠️ 改写:前两个测试已删除 → 随重建一并写。四方向用例一个都不能少:
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 的会签。
🔴 同时补上 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 ⚠️ 底座侧不变;业务侧两文件已删除 → 「新建时即实现」。
🔴 白名单必须限定 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)。
🔴 禁止从 _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 "[手机号已脱敏]"。
理由(取证):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