## 为什么做这一步 权威文档 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...`), 末尾带省略号,是接口文档的示意值,**不是可用凭据**。
46 KiB
客服 Agent 重构报告(清除了什么、缺什么、按什么顺序装回去)
体系编号:
D4.1· 域:四、清除与重建留痕 · 编号体系见D1.1§4.0
编号:CS-REFACTOR-2026-010 日期:2026-09-16 性质:重构执行报告(v5.1 级修订) —— 取代
D3.4-客服Agent重构Todolist.mdv5.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 · 顺序:先重建客服,再删投顾 ← 与用户原计划不同
用户原计划是「先删投顾,再重建客服」。建议调整为「先重建客服,投顾留到最后」,三条理由:
- 投顾是当前唯一完好的业务线,是重建客服时改动 6 个底座文件的对照组。任何回归,必须拿另一个 Agent 做 A/B 才能判断「是我改坏的」还是「本来就坏的」。
- 两个模块同时处于「拆了没装回」的状态时,演示一失败,你连它属于哪一层都判断不出来。
- 投顾前端 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 成文时尚不存在):
- 形态A清除的完整记录(34 删 / 17 改 + 备份位置
_cs_purge_backup/)——否则下一轮读文档的人会以为这个模块「从未有过」; - 🔴 安全能力当前为零:提示词注入拦截 8 条 / 凭据披露短语 / 安全关键词路由 + 零容忍词 11 条,在生效路径上不存在;词表唯一副本在
docs/customer-service-routing-legacy-keywords.md; - 🔴 游客链路已断(本报告 §2):无 Agent 接受
visitor角色 +query_knowledge不在发布白名单 + 三个发布脚本已删; governance.py热线副本的处置结果(P-8)与PROFILE_CANDIDATE_AGENT_TYPES的当前值(空集合,重建后填回);- 重建验收基线两份文档不得被清理:
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 | ⚠️ 改写:P-8 清旧副本 → B-02 填值 → 重建时建真源(真源 customer_service_rules.py 已删) |
|
| S-3 | 🟢 已消解: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需要连库实测当前 activeconfig_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 |