Files
group_fqcd_jr/客服agent/D2.3-客服Agent开发计划.html
T
张胜宇 c91bbcbdc1 feat(ops)+docs: 密钥轮换工具 + 两份文档目录审计收口(D1.1 §23 / D2.1 v6.26)
一、密钥轮换(新增工具 + 操作手册)
- 新增 tools/rotate_api_keys.py:--check 体检 + 交互式轮换;getpass 不回显、
  自动备份 .env.bak-<时间戳>(已被 ignore 命中)、校验不过整体不写入、
  三个 Qwen 变量写同一值 / 两个 DeepSeek 变量写同一值。
  实测 --check:Qwen 三变量同值且非空、DeepSeek 两变量同值且非空。
- 新增 开发文档/D3.8-模型密钥轮换与凭据安全操作手册-2026-09-20.md(CS-OPS-2026-023):
  .env 5 个变量与读取方取证、五步流程、3 个坑、复核清单、回退方式、能力边界。
- 口径确认:model_endpoint_config.secret_ref 存变量名 ⇒ 轮换只改 .env,不动 DB;
  但必须重启 API + Worker。

二、门禁修复:docs/ 编号撞车
- tools/check_authoritative_docs.py(D3.4 N-14 登记的验收命令集之一)实测 FAIL:
  我方 docs/46 docs/47(2026-09-20 建)与投顾组 docs/46-投顾Agent需求文档.md
  docs/47-投顾Agent功能架构文档.md(2026-09-16 建)同号。
- 按「后到者让位」改名:docs/48-可改文件白名单.md / docs/49-底座会签申请单-2026-09-19.md,
  同步 8 处引用。修复后:checked 54 documents, no number collision,exit 0。

三、前端品牌残留(W12 合并静默回退)
- employee-advisor/dashboard/index.html 与 customer/advisor-plans/index.html 的
  <title> 仍是 南方财富(投顾组分支带回)→ 按 DEC-27 改为 南方基金。
- HTTP 实测两页标题已正确;全仓 app/ 复查 南方财富 = 0。

四、文档口径校准(12 处事实漂移)
- D1.1:D2.1 版本 v5.3 → v6.26(§4.0 / §4.1 / §1 / §2 四处长期错误);
  §8 四行遗留项闭合(D-5 / D-6 / D-7 / 仓库副本同步)+ 新增 §22 §23 留痕;
  §10.2「本区不在任何 git 仓库内」更正为已入库;新增两编号 ⇒ 计数 56 → 58 全量同步。
- D2.2:顶栏徽标 v2.4 与元数据 v2.5 自相矛盾 → 统一;「投顾已清除」→ 状态更新
  (模块 2026-09-20 已恢复,但客服范围裁定 §1.7 / RK-10 不变)。
- D2.3:徽标 v1.0 · 7 批次 51 项 → v1.1 · 8 批次 57 项;投顾清除后果 + §7.1 头号风险
  + 风险表 + 不触碰行全部加恢复口径。
- D2.4:v1.3 变更说明 ⑦ / §1.4 Out of scope / Q-09 加投顾恢复口径。
- D2.5:advisor_t 自相矛盾口径改写为账号表一行 + 口径更正;五项自检首选改为
  一键脚本 启动演示.bat / demo.ps1;补 D3.8 与未发布 advisor:* 白名单登记。
- D2.6:门禁数字 1856/2 → 1909/3 skipped、ruff 19 → 20、补 portal_api_check 行;
  §10 两项已闭环(密钥轮换已工具化、A-10 组 3/4 已补签);头部加 W12/W13 状态更新。
- D4.5:顶部状态更新补指向 D4.7。
- 新增 开发文档/D4.7-投顾模块恢复记录-2026-09-20.md(CS-PURGE-2026-014):
  时间线、8 项恢复动作、客服线不变的结论、DEC-19 理由更正、遗留 1 项、失误登记。
- _consistency.py(维护侧):§三 改为「投顾状态口径检查」,合法语境扩为
  清除史 / 恢复史 / 不属本 Agent 范围。

五、回归实测(全绿)
- pytest -q:1909 passed / 3 skipped / 0 failed
- ruff check app tools tests:20(与 W12 持平,未引入新债)
- mypy app:2(= 既有基线)
- tools/check_authoritative_docs.py:54 文档无编号冲突(exit 0)
- tools/e2e_smoke_test.py --read-only:31/31
- tools/portal_api_check.py:40 项 通过 35 / 失败 0 / 跳过 5
- _eval_harness/http_probe.py:11/11 succeeded
- _consistency.py:GATE PASS
- demo.ps1 -SkipStart -NoBrowser:五项自检全过、退出码 0
- 权威副本 ↔ 仓库:逐字节一致(客服agent 24 / 开发文档 52)

六、未做(如实登记)
- 投顾 config_release 工具白名单(advisor:*)仍未发布 ⇒ 投顾 Agent 工具调用 fail closed
  (实测 active_agent_tools 仅 customer_service:* 4 项 + risk:* 4 项)。与客服线无关;
  要演投顾线先跑 tools/publish_advisor_demo_config.py --apply。
- 两把 key 的实际轮换需你在控制台建新 key(无法代做),流程见 D3.8。
2026-09-20 15:27:07 +08:00

894 lines
66 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>南方基金·智能服务系统 — 智能客服 Agent 开发计划</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Noto+Serif+SC:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
:root {
--navy-900: #0a1628;
--navy-800: #121e33;
--navy-700: #1a2a44;
--navy-600: #243552;
--navy-500: #2d4160;
--gold-500: #c9a84c;
--gold-400: #d4b966;
--gold-300: #e0cc85;
--gold-600: #a8873a;
--bg: #f4f1eb;
--bg-white: #ffffff;
--text: #1e293b;
--text-muted: #64748b;
--border: #e2ddd5;
--code-bg: #0d1117;
--sidebar-w: 288px;
--topbar-h: 56px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
font-family: 'Inter', -apple-system, sans-serif;
color: var(--text);
background: var(--bg);
line-height: 1.75;
font-size: 15px;
}
#progress-bar {
position: fixed; top: 0; left: 0; height: 3px; z-index: 9999;
background: linear-gradient(90deg, var(--gold-600), var(--gold-400), var(--gold-500));
width: 0%; transition: width 0.15s ease-out;
}
#sidebar {
position: fixed; top: 0; left: 0; width: var(--sidebar-w); height: 100vh;
background: linear-gradient(180deg, var(--navy-900), var(--navy-800));
z-index: 1000; display: flex; flex-direction: column;
border-right: 1px solid rgba(201,168,76,0.15);
}
.sidebar-header { padding: 22px 20px 14px; border-bottom: 1px solid rgba(201,168,76,0.12); }
.sidebar-brand {
display: flex; align-items: center; gap: 10px;
font-family: 'Noto Serif SC', serif; font-weight: 700;
font-size: 16px; color: var(--gold-400); letter-spacing: 0.5px;
}
.sidebar-brand .brand-icon {
width: 32px; height: 32px; border-radius: 8px;
background: linear-gradient(135deg, var(--gold-500), var(--gold-600));
display: flex; align-items: center; justify-content: center;
font-size: 16px; color: var(--navy-900); flex-shrink: 0;
}
.sidebar-subtitle {
font-size: 11px; color: rgba(255,255,255,0.4);
margin-top: 6px; letter-spacing: 1px;
}
#toc-search {
margin: 12px 16px; padding: 8px 12px;
background: rgba(255,255,255,0.06);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 6px; color: #fff;
font-size: 13px; font-family: 'Inter', sans-serif;
outline: none; transition: border-color 0.2s;
}
#toc-search::placeholder { color: rgba(255,255,255,0.3); }
#toc-search:focus { border-color: var(--gold-500); }
#toc-container {
flex: 1; overflow-y: auto; padding: 8px 0 32px;
scrollbar-width: thin; scrollbar-color: rgba(201,168,76,0.3) transparent;
}
#toc-container::-webkit-scrollbar { width: 4px; }
#toc-container::-webkit-scrollbar-thumb { background: rgba(201,168,76,0.3); border-radius: 2px; }
.toc-item {
display: block; padding: 5px 20px;
color: rgba(255,255,255,0.6); font-size: 12.5px;
text-decoration: none; border-left: 3px solid transparent;
transition: all 0.15s; line-height: 1.5;
}
.toc-item:hover { color: var(--gold-400); background: rgba(201,168,76,0.06); }
.toc-item.active { color: var(--gold-400); border-left-color: var(--gold-500); background: rgba(201,168,76,0.08); font-weight: 500; }
.toc-group { margin-bottom: 2px; }
.toc-toggle {
display: flex; align-items: flex-start; gap: 6px;
width: 100%; padding: 6px 16px;
color: rgba(255,255,255,0.85);
font-weight: 600; font-size: 13px;
font-family: inherit; background: none; border: none;
cursor: pointer; text-align: left; line-height: 1.45;
border-left: 3px solid transparent; transition: all 0.15s ease;
}
.toc-toggle:hover { color: var(--gold-400); background: rgba(201,168,76,0.06); }
.toc-toggle.active { color: var(--gold-400); border-left-color: var(--gold-500); background: rgba(201,168,76,0.1); }
.toc-toggle .arr { display: inline-block; width: 12px; flex-shrink: 0; font-size: 9px; color: rgba(255,255,255,0.25); transition: transform 0.2s ease; padding-top: 4px; }
.toc-toggle .arr.open { transform: rotate(90deg); }
.toc-kids { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; }
.toc-kids.open { max-height: 2400px; }
.toc-kids .toc-item { padding-left: 34px; }
#topbar {
position: fixed; top: 0; left: var(--sidebar-w); right: 0;
height: var(--topbar-h); z-index: 999;
background: rgba(244,241,235,0.88); backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-between;
padding: 0 32px;
}
#topbar-title { font-family: 'Noto Serif SC', serif; font-size: 14px; font-weight: 600; color: var(--navy-800); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#topbar-badge {
font-size: 11px; padding: 3px 10px;
background: linear-gradient(135deg, var(--gold-500), var(--gold-600));
color: var(--navy-900); border-radius: 12px; font-weight: 600;
letter-spacing: 0.5px; white-space: nowrap;
}
#main { margin-left: var(--sidebar-w); margin-top: var(--topbar-h); min-height: calc(100vh - var(--topbar-h)); }
.doc-container { max-width: 900px; margin: 0 auto; padding: 40px 40px 90px; }
.doc-header { margin-bottom: 44px; padding-bottom: 30px; border-bottom: 2px solid var(--gold-500); }
.doc-eyebrow { font-size: 12px; text-transform: uppercase; letter-spacing: 2px; color: var(--gold-600); font-weight: 600; margin-bottom: 12px; }
.doc-title { font-family: 'Noto Serif SC', serif; font-size: 31px; font-weight: 700; color: var(--navy-900); line-height: 1.35; margin-bottom: 20px; }
.doc-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.doc-meta-item { display: flex; flex-direction: column; padding: 12px 16px; background: var(--bg-white); border-radius: 8px; border: 1px solid var(--border); }
.doc-meta-label { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); font-weight: 500; }
.doc-meta-value { font-size: 14px; font-weight: 500; color: var(--navy-800); margin-top: 2px; }
.doc-container h1 {
font-family: 'Noto Serif SC', serif; font-size: 25px; font-weight: 700;
color: var(--navy-900); margin: 50px 0 16px; padding-top: 18px;
border-top: 1px solid var(--border);
}
.doc-container h1:first-of-type { border-top: none; margin-top: 0; padding-top: 0; }
.doc-container h2 { font-family: 'Noto Serif SC', serif; font-size: 20px; font-weight: 600; color: var(--navy-800); margin: 38px 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.doc-container h3 { font-family: 'Noto Serif SC', serif; font-size: 16.5px; font-weight: 600; color: var(--navy-700); margin: 26px 0 10px; }
.doc-container h4 { font-family: 'Noto Serif SC', serif; font-size: 15px; font-weight: 600; color: var(--navy-600); margin: 20px 0 8px; }
.mermaid { background: var(--bg-white); border: 1px solid var(--border); border-radius: 8px; padding: 20px; margin: 20px 0 26px; text-align: center; overflow-x: auto; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
table { width: 100%; border-collapse: collapse; margin: 16px 0 24px; font-size: 13.5px; background: var(--bg-white); border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.04); border: 1px solid var(--border); }
thead th { background: var(--navy-800); color: var(--gold-400); font-weight: 600; font-size: 12.5px; padding: 11px 14px; text-align: left; letter-spacing: 0.3px; }
tbody td { padding: 9px 14px; border-top: 1px solid var(--border); vertical-align: top; }
tbody tr:hover { background: rgba(201,168,76,0.045); }
pre { background: var(--code-bg); color: #e6edf3; padding: 18px; border-radius: 8px; overflow-x: auto; margin: 16px 0 24px; font-family: 'JetBrains Mono', monospace; font-size: 12.5px; line-height: 1.62; }
.code-block { margin: 16px 0 24px; border-radius: 8px; overflow: hidden; border: 1px solid rgba(201,168,76,0.12); background: var(--code-bg); }
.code-header { padding: 6px 14px; background: rgba(201,168,76,0.06); border-bottom: 1px solid rgba(201,168,76,0.1); }
.code-lang { font-size: 11px; font-weight: 600; color: var(--gold-400); font-family: 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 1px; }
.code-block pre { margin: 0; border-radius: 0; border: none; padding: 16px 18px; }
:not(pre) > code { font-family: 'JetBrains Mono', monospace; background: rgba(26,42,68,0.08); color: var(--navy-700); padding: 2px 6px; border-radius: 4px; font-size: 0.87em; }
blockquote {
background: linear-gradient(135deg, rgba(201,168,76,0.12), rgba(36,53,82,0.06));
border-left: 4px solid var(--gold-500);
color: var(--navy-800);
padding: 14px 20px; border-radius: 0 8px 8px 0; margin: 16px 0 24px; font-size: 14px;
}
blockquote.callout-warn { border-left-color: #c62828; background: linear-gradient(135deg, rgba(198,40,40,0.08), rgba(198,40,40,0.02)); }
blockquote.callout-info { border-left-color: #0288d1; background: linear-gradient(135deg, rgba(2,136,209,0.08), rgba(2,136,209,0.02)); }
blockquote.callout-design { border-left-color: #6a1b9a; background: linear-gradient(135deg, rgba(106,27,154,0.08), rgba(106,27,154,0.02)); }
blockquote p { margin: 0; }
blockquote p + p { margin-top: 8px; }
.doc-container ul, .doc-container ol { padding-left: 24px; margin: 12px 0; }
.doc-container li { margin: 6px 0; line-height: 1.7; }
.doc-container ul { list-style: none; padding-left: 0; }
.doc-container ul li { padding-left: 20px; position: relative; }
.doc-container ul li::before { content: ''; position: absolute; left: 0; top: 10px; width: 6px; height: 6px; border-radius: 50%; background: var(--gold-500); }
.doc-container ol li::marker { color: var(--gold-600); font-weight: 600; }
.intent-tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-family: 'JetBrains Mono', monospace; font-weight: 500; }
.intent-tag.high { background: #ffebee; color: #c62828; }
.intent-tag.mid { background: #fff3e0; color: #e65100; }
.intent-tag.low { background: #e8f5e9; color: #2e7d32; }
.pill { display: inline-block; padding: 1px 7px; border-radius: 10px; font-size: 11px; font-weight: 600; letter-spacing: .3px; }
.pill.p0 { background: #ffebee; color: #c62828; }
.pill.p1 { background: #fff3e0; color: #e65100; }
.pill.p2 { background: #e8f5e9; color: #2e7d32; }
hr { border: none; height: 1px; background: linear-gradient(90deg, transparent, var(--gold-500), transparent); margin: 34px 0; }
.doc-container p { margin: 10px 0; line-height: 1.82; color: #1f2937; }
.doc-container strong { font-weight: 600; color: var(--navy-900); }
.doc-container a { color: var(--gold-600); text-decoration: none; border-bottom: 1px solid var(--gold-300); }
.req-card { background: var(--bg-white); border-radius: 12px; border: 1px solid var(--border); padding: 22px 26px; margin: 18px 0 24px; position: relative; box-shadow: 0 1px 3px rgba(0,0,0,0.04); }
.req-card::before { content: ''; position: absolute; top: 0; left: 0; width: 4px; height: 100%; border-radius: 12px 0 0 12px; background: linear-gradient(180deg, #4fc3f7, #0288d1); }
.tag-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
@media print {
#sidebar, #topbar, #progress-bar { display: none !important; }
#main { margin-left: 0; margin-top: 0; }
.doc-container { padding: 20px; max-width: 100%; }
.req-card { break-inside: avoid; box-shadow: none; }
table { break-inside: avoid; }
}
@media (max-width: 1024px) {
#sidebar { display: none; }
#topbar { left: 0; }
#main { margin-left: 0; }
.doc-container { padding: 24px 20px 60px; }
.doc-title { font-size: 23px; }
}
.doc-container h1[id], .doc-container h2[id], .doc-container h3[id], .doc-container h4[id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
</style>
</head>
<body>
<div id="progress-bar"></div>
<nav id="sidebar">
<div class="sidebar-header">
<div class="sidebar-brand">
<div class="brand-icon">🎧</div>
<span>南方基金</span>
</div>
<div class="sidebar-subtitle">客服 Agent 开发计划 v1.0 · 7 批次</div>
</div>
<input type="text" id="toc-search" placeholder="搜索目录...">
<div id="toc-container">
<div class="toc-group">
<button class="toc-toggle active" data-group="1"><span class="arr open">▶</span><span>0. 文档定位与前提</span></button>
<div class="toc-kids open">
<a class="toc-item" href="#0.1-目的与范围">0.1 目的与范围</a>
<a class="toc-item" href="#0.2-与其他三份文档的关系">0.2 与其他三份文档的关系</a>
<a class="toc-item" href="#0.3-当前状态与两处重大变化">0.3 当前状态与两处重大变化</a>
</div></div>
<div class="toc-group">
<button class="toc-toggle" data-group="2"><span class="arr">▶</span><span>1. 前置条件与准入</span></button>
<div class="toc-kids">
<a class="toc-item" href="#1.1-开工前必须闭环的五项">1.1 开工前必须闭环的五项</a>
<a class="toc-item" href="#1.2-测试环境就位">1.2 测试环境就位(G-00)</a>
<a class="toc-item" href="#1.3-纪律基线">1.3 纪律基线</a>
</div></div>
<div class="toc-group">
<button class="toc-toggle" data-group="3"><span class="arr">▶</span><span>2. 工程落位</span></button>
<div class="toc-kids">
<a class="toc-item" href="#2.1-三类落位的纪律">2.1 三类落位的纪律</a>
<a class="toc-item" href="#2.2-目录落位">2.2 目录落位</a>
</div></div>
<div class="toc-group">
<button class="toc-toggle" data-group="4"><span class="arr">▶</span><span>3. 执行计划</span></button>
<div class="toc-kids">
<a class="toc-item" href="#3.1-批次总览">3.1 批次总览(7 批次 51 项)</a>
<a class="toc-item" href="#3.2-批次A-冻结与准备">3.2 批次 A · 冻结与准备</a>
<a class="toc-item" href="#3.3-批次B至F摘要">3.3 批次 B ~ F 摘要</a>
<a class="toc-item" href="#3.4-批次G-访客与鉴权">3.4 批次 G · 访客与鉴权</a>
<a class="toc-item" href="#3.5-里程碑">3.5 里程碑</a>
</div></div>
<div class="toc-group">
<button class="toc-toggle" data-group="5"><span class="arr">▶</span><span>4. 底座接触面与会签</span></button>
<div class="toc-kids">
<a class="toc-item" href="#4.1-组-1-六文件八处">4.1 组 1 · 六文件八处</a>
<a class="toc-item" href="#4.2-组-2-访客与鉴权四文件">4.2 组 2 · 访客与鉴权四文件</a>
<a class="toc-item" href="#4.3-会签流程">4.3 会签流程</a>
</div></div>
<div class="toc-group">
<button class="toc-toggle" data-group="6"><span class="arr">▶</span><span>5. 关键路径与约束</span></button>
<div class="toc-kids">
<a class="toc-item" href="#5.1-关键路径">5.1 关键路径(12 步)</a>
<a class="toc-item" href="#5.2-十二条硬串行约束">5.2 十二条硬串行约束</a>
<a class="toc-item" href="#5.3-并行通道">5.3 并行通道</a>
</div></div>
<div class="toc-group">
<button class="toc-toggle" data-group="7"><span class="arr">▶</span><span>6. 质量门禁与完工判据</span></button>
<div class="toc-kids">
<a class="toc-item" href="#6.1-质量门禁">6.1 质量门禁</a>
<a class="toc-item" href="#6.2-整体完工判据">6.2 整体完工判据(13 条)</a>
</div></div>
<div class="toc-group">
<button class="toc-toggle" data-group="8"><span class="arr">▶</span><span>7. 风险与分工</span></button>
<div class="toc-kids">
<a class="toc-item" href="#7.1-头号风险">7.1 头号风险</a>
<a class="toc-item" href="#7.2-风险重排">7.2 风险重排</a>
<a class="toc-item" href="#7.3-分工与交付物">7.3 分工与交付物</a>
</div></div>
</div>
</nav>
<header id="topbar">
<div id="topbar-title">智能服务系统 — 智能客服 Agent 开发计划</div>
<span id="topbar-badge">v1.1 · 8 批次 57 项 · 12 步关键路径</span>
</header>
<main id="main">
<div class="doc-container">
<div class="doc-header">
<div class="doc-eyebrow">专项开发计划 · 四份交付文档之一</div>
<h1 class="doc-title">南方基金·智能服务系统<br>智能客服 Agent 开发计划</h1>
<div class="doc-meta">
<div class="doc-meta-item">
<span class="doc-meta-label">体系编号</span>
<span class="doc-meta-value">D2.3 · 域:二、对外交付 · 编号体系见 开发文档/D1.1-文档索引与权威声明.md §4.0</span>
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">文档版本</span>
<span class="doc-meta-value">v1.1(8 批次 57 项 · 12 步关键路径)</span>
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">文档定位</span>
<span class="doc-meta-value">四份交付文档之一 · 计划分册</span>
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">执行入口</span>
<span class="doc-meta-value">《客服 Agent 执行 Todolist》(开工只看那一份)</span>
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">底座接触面</span>
<span class="doc-meta-value">组 1 六文件八处 + 组 2 四文件(主动扩张)</span>
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">会签要求</span>
<span class="doc-meta-value">两组分别提案、分别 PR</span>
</div>
<div class="doc-meta-item">
<span class="doc-meta-label">文档密级</span>
<span class="doc-meta-value">内部公开</span>
</div>
</div>
</div>
<!-- ==================== 0 ==================== -->
<h1 id="0.-文档定位与前提">0. 文档定位与前提</h1>
<h2 id="0.1-目的与范围">0.1 目的与范围</h2>
<p>本文档回答「<strong>分几步做、按什么顺序、谁做什么、做到什么算完成</strong>」。它是四份交付文档中的<strong>「计划」分册</strong>:上游接《客服 Agent 需求文档》(做什么),下游发《客服 Agent 执行 Todolist》(逐项任务与验收动作)。</p>
<table>
<thead><tr><th>覆盖</th><th>不覆盖(归属)</th></tr></thead>
<tbody>
<tr><td>前置条件与准入、测试环境要求、纪律基线</td><td>需求条目与验收标准 → 《需求文档》§1.4/§1.5/§2.2</td></tr>
<tr><td>工程落位(原位改造 / 最小新增 / 最小挂载)</td><td>逐项任务的 DoD 与验证命令 → 《执行 Todolist》</td></tr>
<tr><td>批次划分、里程碑、关键路径、串行约束、并行通道</td><td>集合划分、分块、检索参数 → 《知识库设计方案》</td></tr>
<tr><td>底座接触面与两组会签、质量门禁、完工判据、交付物</td><td>溯源取证与理由 → <code>D4.1-客服Agent重构报告-2026-09-16.md</code></td></tr>
</tbody>
</table>
<h2 id="0.2-与其他三份文档的关系">0.2 与其他三份文档的关系</h2>
<table>
<thead><tr><th>文档</th><th>在这套体系中的角色</th><th>与本文档的接口</th></tr></thead>
<tbody>
<tr><td><strong>《需求文档》</strong></td><td>要做什么、边界在哪、验收标准</td><td>本文档的每个批次都指向它的一组 FR / NFR / 红线</td></tr>
<tr><td><strong>本文档</strong></td><td><strong>分几步、什么顺序、谁做、何时算完</strong></td><td>—</td></tr>
<tr><td><strong>《执行 Todolist》</strong></td><td>逐项任务与验收动作(开工只看那一份)</td><td>本文档定批次与顺序,Todolist 定每项的 DoD</td></tr>
<tr><td><strong>《知识库设计方案》</strong></td><td>知识库专项设计</td><td>D 批次(检索护栏)与 B 批次(语料)的规格来源</td></tr>
</tbody>
</table>
<blockquote class="callout-info"><p><strong>📌 四份交付文档的配套关系</strong><br>① <strong><code>D2.2-客服Agent需求文档.html</code></strong>——需求与验收标准:<strong>功能需求 48 条(FR-CS-001~048)+ 非功能需求 21 条(NFR-CS-001~021)</strong>,含 <strong>三条业务红线</strong>(风险等级唯一来源=问卷测评 / 先风险揭示后客户确认 / 不生成交易指令)与范围边界;<br>② <strong><code>D2.3-客服Agent开发计划.html</code></strong>(本文档)——<strong>51 项 / 7 个批次 / 12 步关键路径</strong>;<br>③ <strong><code>D2.1-客服Agent执行Todolist.md</code></strong>——逐项 DoD 与验证动作(<strong>开工只看那一份</strong>);<br>④ <strong><code>D2.4-客服Agent知识库设计方案.html</code></strong>——知识库专项。</p></blockquote>
<h2 id="0.3-当前状态与两处重大变化">0.3 当前状态与两处重大变化</h2>
<table>
<thead><tr><th>项</th><th>状态</th></tr></thead>
<tbody>
<tr><td>客服 Agent 模块</td><td><strong>已按形态 A 整体清除</strong>(2026-09-16,删 34 文件 + 摘 17 个共享文件引用,回退副本 <code>_cs_purge_backup/</code>)——下一步是<strong>重建</strong></td></tr>
<tr><td><strong>投顾模块</strong></td><td><strong>已于 2026-09-17 整体清除</strong>(后端 21 文件 + 前端 <code>employee-advisor/</code> 9 文件 + <code>/api/v1/advisor</code> 全部端点 + 5 个脚本 + 14 个测试 + 相关权限码)。<strong>验证:0 语法错误、0 悬空 import</strong>;备份 <code>_advisor_purge_backup/</code><br>⚠️ <strong>2026-09-20 状态更新</strong>:投顾模块<strong>已随合并恢复</strong>(<code>W12</code>;远端组员 3 个提交反向依赖被清除的模块,裁定「组员新功能 &gt; 本地清除」)。本轮客服侧工作<strong>不受影响</strong>:本计划的批次、接触面与门禁均针对客服 Agent,投顾恢复只是把「第二条回归业务线」还了回来。见 <code>D1.6</code> §4.37 / <code>D4.7</code>。</td></tr>
<tr><td><strong>身份与鉴权方向</strong></td><td>已拍板:<strong>「方案乙(收敛)现在做 → 方案甲(拆轴)MVP 后」</strong></td></tr>
<tr><td>工程纪律</td><td>19 项决策已全部定案;底座接触面已逐文件取证</td></tr>
</tbody>
</table>
<blockquote class="callout-warn"><p><strong>⚠️ 投顾清除带来一个必须正视的后果</strong>:投顾原是<strong>第二条回归业务线</strong>,用于验证 6 个底座公共件的改动。清除后,底座改动的回归验证<strong>只剩客服一条线</strong>。因此本计划把「<strong>测试环境就位(G-00)</strong>」列为<strong>最优先的前置</strong>——没有可运行的门禁时改底座,等于盲改。<br>⚠️ <strong>2026-09-20 状态更新</strong>:投顾<strong>已随合并恢复</strong> ⇒ <strong>对照组回归</strong>,本条降级为「历史上曾存在的风险」;<code>G-00</code> 的优先级结论<strong>不变</strong>(本轮实测全量回归 1909 passed,已由事实替代推断)。</p></blockquote>
<hr>
<!-- ==================== 1 ==================== -->
<h1 id="1.-前置条件与准入">1. 前置条件与准入</h1>
<h2 id="1.1-开工前必须闭环的五项">1.1 开工前必须闭环的五项</h2>
<table>
<thead><tr><th>#</th><th>事项</th><th>不闭环的后果</th><th>谁确认</th></tr></thead>
<tbody>
<tr><td><strong>T-01</strong></td><td>Embedding 模型与维度(底座实况)</td><td><strong>维度不一致 → 检索结果无意义(静默错误)</strong>;建集合的动作需重做</td><td>后端架构师</td></tr>
<tr><td><strong>T-02</strong></td><td>三集合是否已存在及其 Schema</td><td>决定「首次建集合」还是「改造既有」——影响 D 批次全部落点</td><td>后端架构师</td></tr>
<tr><td><strong>T-03</strong></td><td>RAG 基础设施的落位与签名</td><td><strong>影响面最大</strong>:决定检索是「改造既有签名」还是「新建入口」;新建入口=产生第二条可能漏过滤的路径</td><td>后端架构师</td></tr>
<tr><td><strong>T-04</strong></td><td>一期安全路由是否被二期完整覆盖(含关键词 diff)</td><td><strong>唯一带安全性的删除</strong>:若覆盖不全,删一期路由会造成安全回归</td><td>安全/合规负责人</td></tr>
<tr><td><strong>T-05</strong></td><td><strong>底座接触面授权</strong>(组 2 的 4 个文件)</td><td>不做则<strong>访客权限无法实时收紧</strong>、受理与执行身份可能不一致(RK-20)</td><td>项目负责人 + 底座 owner</td></tr>
</tbody>
</table>
<h2 id="1.2-测试环境就位">1.2 测试环境就位(G-00 · 最优先)</h2>
<blockquote class="callout-warn"><p><strong>⚠️ G-00 不是形式要求</strong>:当前本机<strong>依赖零安装、无 venv</strong>,只能做语法层验证(<code>py_compile</code> + AST 悬空 import 扫描)。而 G-01 要改<strong>鉴权入口</strong>与 <strong>Worker 执行路径</strong>、D 批次要改<strong>检索层</strong>——<strong>没有可运行的测试套件时,这些改动等于盲改</strong>。</p></blockquote>
<table>
<thead><tr><th>动作</th><th>DoD</th></tr></thead>
<tbody>
<tr><td>依赖与 venv 就位</td><td><code>pip install -e .</code> 成功;<code>python -c "import fastapi, sqlalchemy, asyncmy, pydantic"</code> 通过(<strong>须实测能 import 依赖,不能只看 <code>--version</code></strong>)</td></tr>
<tr><td>门禁基线</td><td>按 <code>docs/14</code> 命令集跑通并记录原始数字:<code>ruff check app tests tools alembic</code> / <code>mypy app</code> / <code>pytest tests/unit tests/contract</code> / <code>pytest tests/integration</code> / <code>python tools/audit_schema.py</code>(<strong>预期 90 张表 = 89 业务表 + alembic_version</strong>)</td></tr>
<tr><td>跑前纪律</td><td><strong>跑验收脚本前先停常驻 Worker</strong>(否则抢队列);集成测试前先 <code>seed_test_rbac.py</code> + <code>set_user_password.py</code></td></tr>
</tbody>
</table>
<h2 id="1.3-纪律基线">1.3 纪律基线</h2>
<table>
<thead><tr><th>#</th><th>纪律</th><th>说明</th></tr></thead>
<tbody>
<tr><td>1</td><td><strong>零 DDL</strong></td><td>本期不改表结构、不加字段;`tools/audit_schema.py` 前后一致是硬证据</td></tr>
<tr><td>2</td><td><strong>分层判据</strong></td><td>「被多个 Agent 使用 / 在 <code>ToolRegistry</code> 注册为公共只读工具 / 承载鉴权·记忆·意图·模型·工具·合规·审计·持久化 8 项中任一项」→ <strong>底座层,须会签</strong>;否则业务层,自主重构</td></tr>
<tr><td>3</td><td><strong>会签门(先提案后动手)</strong></td><td>未获受理的项按降级方案执行,<strong>不得静默绕过</strong>;也不得为「绕过会签」而在业务层重造同一能力</td></tr>
<tr><td>4</td><td><strong>禁止新建检索入口</strong></td><td>两个入口=两条可能漏过滤的路径;只能改造既有签名</td></tr>
<tr><td>5</td><td><strong>证据留痕</strong></td><td>每项任务须给出「改了哪些文件 / 为什么 / 验证方式 / 未验部分」</td></tr>
</tbody>
</table>
<hr>
<!-- ==================== 2 ==================== -->
<h1 id="2.-工程落位">2. 工程落位</h1>
<h2 id="2.1-三类落位的纪律">2.1 三类落位的纪律</h2>
<p>本次为<strong>重构既有实现</strong>,且底座<strong>原则上不可修改</strong>,因此遵循「<strong>原位改造 + 最小新增 + 最小挂载</strong>」。</p>
<table>
<thead><tr><th>类型</th><th>内容</th><th>纪律</th></tr></thead>
<tbody>
<tr><td><strong>原位改造</strong></td><td>客服 Agent 主体、会话记忆、转人工三件套</td><td>不改文件名、不改对外签名、不迁移目录;改造前先确认既有行为的验证方式</td></tr>
<tr><td><strong>最小新增</strong></td><td>档位过滤、档位标注器、主体判定、访客令牌、访客限流、合规词库、<code>actor.py</code>、<code>knowledge_tier.py</code></td><td>不新增架构层;文件名与既有命名风格一致</td></tr>
<tr><td><strong>最小挂载</strong></td><td>路由注册、Agent 工厂分支、配置项追加</td><td>只追加不改既有逻辑;每处追加在评审中单独说明理由</td></tr>
<tr><td><strong>不触碰</strong></td><td>投顾模块(2026-09-17 曾清除、<strong>2026-09-20 已恢复</strong>,但<strong>本轮仍不触碰</strong>);其余公共基座<strong>只调用不改写</strong></td><td>例外见 §4.2(组 2 的本轮扩张)</td></tr>
</tbody>
</table>
<h2 id="2.2-目录落位">2.2 目录落位(按既有真实结构)</h2>
<div class="code-block">
<div class="code-header"><span class="code-lang">text · 客服 Agent 重建的文件落位(既有 / 新增)</span></div>
<pre><code>app/
├── api/
│ ├── controllers/
│ │ ├── agent_runs.py # 既有 · 扩展:承载客服对话路由
│ │ ├── conversations.py # 既有 · 复用:会话消息与反馈
│ │ └── knowledge.py # 既有 · 复用:知识库 upload/list/search/delete
│ └── dependencies/
│ ├── auth.py # ★组2 · 改:跳过身份解析的条件语义化(G-01b)
│ └── rate_limit.py # 既有 · 复用:登录与访客令牌的 IP 闸门
├── service/
│ ├── agent/
│ │ ├── base.py # ★组2 · 改:访客不召回的「谓词」替换(G-01b)
│ │ ├── authorizer.py # 既有 · 复用(方案甲落地时才改)
│ │ ├── bootstrap.py # 既有 · 扩展:注册重发后的客服 Agent 与工具
│ │ └── implementations/
│ │ └── customer_service.py # ★ 重建主体(原 748 行已随形态A清除)
│ ├── agent_run_application_service.py # 既有 · 扩展:补身份装配
│ ├── customer_service_session_memory_service.py # 既有 · 复用 + 扩展访客会话
│ ├── customer_service_handover_{context,admin,action}_service.py # 既有 · 复用(486 行)
│ ├── knowledge_search_service.py # 既有 · 改造:检索签名 fail-closed(D-01)
│ ├── knowledge_tool.py # 既有 · 改造:删 `del context`(D-04)
│ ├── tool_executor.py # 既有 · 复用(方案甲落地时才改)
│ └── visibility_tagger.py # 新增:档位标注器(含 internal 剔除)
├── core/
│ ├── config.py # 既有 · 追加配置项
│ ├── compliance_context.py # 既有 · 改造:补短否定式(C-04)
│ ├── customer_service_rules.py # 既有 · 复用:唯一安全路由 route_message()
│ ├── knowledge_schema.py # 既有 · 复用:运行时字段探测(禁字面量)
│ ├── memory_scope.py # 既有 · 复用:记忆范围唯一口径
│ ├── actor.py # ★新增(G-01):访客判定与三元组唯一来源
│ ├── knowledge_tier.py # ★新增(G-03):档位推导,调 actor
│ └── security.py # ★组2 · 改:访客分支改调 actor(G-01)
├── worker/
│ └── runtime.py # ★组2 · 改:三元组第二份副本改调 actor(G-01)
└── static/portal/common/customer-service-widget/ # 既有 · 复用:客服挂件</code></pre>
</div>
<blockquote class="callout-warn"><p><strong>⚠️ 标 ★组2 的 4 个文件是本轮唯一主动扩张的底座接触面</strong>(<code>core/security.py</code>、<code>worker/runtime.py</code>、<code>api/dependencies/auth.py</code>、<code>service/agent/base.py</code>)。其中 3 个原本写在「底座零改动清单」里。<strong>必须单独会签、单独 PR</strong>,不得与组 1 混提交。</p></blockquote>
<hr>
<!-- ==================== 3 ==================== -->
<h1 id="3.-执行计划">3. 执行计划</h1>
<h2 id="3.1-批次总览">3.1 批次总览(8 个批次 · 57 项)</h2>
<table>
<thead><tr><th>批次</th><th>主题</th><th>项数</th><th>目标</th><th>是否碰底座</th></tr></thead>
<tbody>
<tr><td><strong>A</strong></td><td>冻结与准备</td><td>10</td><td>把「当前是什么样」钉死,后续任何「红了」都能区分是<strong>我改坏的</strong>还是<strong>本来就有的</strong></td><td>否(产出会签材料)</td></tr>
<tr><td><strong>B</strong></td><td>语料与话术</td><td>5</td><td>把客户可见的跨层不一致一次清干净;<strong>串行,只重跑一次灌库</strong></td><td>1 处(<code>governance.py</code>)</td></tr>
<tr><td><strong>C</strong></td><td>规则重构与安全</td><td>9</td><td>补齐安全缺口与访客侧推介边界;<strong>一次定稿关键词表、一次双向验证</strong></td><td>2 处(<code>compliance_context</code> / <code>governance</code>)</td></tr>
<tr><td><strong>D</strong></td><td>架构护栏</td><td>7</td><td>为「加档位 / 加身份」备前置;<strong>D-02 是静默降级,静态扫描抓不到</strong></td><td>2 处(<code>knowledge_search_service</code> / <code>knowledge_tool</code>)</td></tr>
<tr><td><strong>E</strong></td><td>业务闭环与出口</td><td>8</td><td>建单白名单、priority、reason_code 枚举化(含 1 项挂起)</td><td>1 处(<code>agent_persistence_service</code>)</td></tr>
<tr><td><strong>F</strong></td><td>演示与收口</td><td>7</td><td>文档回写、演示准备、dry-run</td><td>否</td></tr>
<tr><td><strong>G</strong></td><td><strong>访客与鉴权</strong>(新增)</td><td>5</td><td>把「访客与角色分离」落为可执行任务</td><td><strong>4 处(主动扩张)</strong></td></tr>
<tr><td><strong>H</strong></td><td><strong>智能增强</strong>(<strong>v1.1 新增</strong>)</td><td>6</td><td>把出口由 2 个扩为 5 个,消除「动辄转人工」;建立金标评测门禁</td><td>否(<code>H-05</code> 碰建表脚本与检索模块,须会签)</td></tr>
</tbody>
</table>
<h2 id="3.2-批次A-冻结与准备">3.2 批次 A · 冻结与准备(10 项,立即开动)</h2>
<table>
<thead><tr><th>ID</th><th>任务</th><th>关键 DoD</th><th>风险</th></tr></thead>
<tbody>
<tr><td>A-01</td><td>安全用例集基线复现</td><td>逐条记录通过/失败;<strong>改为「只登记预期、不跑实测」</strong>——被测对象已不存在,实测后移至 C-06 之后</td><td>LOW</td></tr>
<tr><td>A-02</td><td>门禁数字基线</td><td>5 项命令 + 日期 + 解释器路径 + SQLAlchemy 补丁版;<strong>跑前先停 Worker</strong></td><td>LOW</td></tr>
<tr><td>A-03</td><td>配置与环境快照</td><td>active 版本 id + 条数 + <strong><code>agent_tools</code> 白名单全文</strong>(须同时含「客户侧」与「游客侧」知识工具);三集合字段名 + 行数;embedding 模型名 + 维度</td><td>LOW</td></tr>
<tr><td>A-04</td><td>业务一致性三查基线</td><td>品牌名 / 占位符 / 界面名差集 + <strong>旧热线号码全部位置</strong>(清单里只要求 4 份清单)</td><td>LOW</td></tr>
<tr><td>A-05</td><td>演示前检查清单固化</td><td>五项:Docker Desktop → Milvus → Worker → 行情未过期 → <code>MILVUS_LOCAL_URI</code> 为空</td><td>LOW</td></tr>
<tr><td>A-06</td><td>分支与 PR 策略</td><td>每批次一个 PR;<strong>底座会签项单独一个 PR</strong>(便于 owner 只审那一份 diff)</td><td>LOW</td></tr>
<tr><td>A-07</td><td><strong>访客链路可用性实测</strong></td><td>实跑一次访客请求;确认「游客侧知识工具」在白名单内;<strong>记录访客 <code>context.user_id</code> 形态</strong>(须为数字串)</td><td><strong>中</strong></td></tr>
<tr><td>A-08</td><td>前端约束与配合点复核</td><td>产出「需前端配合项清单」;结论可以是 0 项,但必须是核对后的 0 项</td><td>LOW</td></tr>
<tr><td>A-09</td><td><strong>《可改文件白名单》落文</strong></td><td>四类:纯新增 / 允许修改 / 提案后由底座方修改 / <strong>禁止修改</strong>;附「零 DDL」声明与基线截图</td><td>LOW</td></tr>
<tr><td>A-10</td><td><strong>底座会签申请单落文</strong></td><td>对<strong>组 1 六处 + 组 2 四处</strong>逐项写清六件事:改什么 / 为什么是公共缺陷 / 最小化边界 / 规范依据 / 影响面 / 降级方案</td><td>LOW</td></tr>
</tbody>
</table>
<h2 id="3.3-批次B至F摘要">3.3 批次 B ~ F 摘要</h2>
<table>
<thead><tr><th>批次</th><th>关键任务</th><th>必须记住的一条</th></tr></thead>
<tbody>
<tr><td><strong>B</strong>(5)</td><td>B-01 语料入库门禁四查 → B-02 语料一次性修正 → B-03 界面指代修正 → <strong>B-04 重跑灌库(唯一一次)</strong> → B-05 热线与服务时间单点化(五组落点)</td><td><strong>顺序不可调</strong>:门禁 → 改语料 → 定值 → 重跑 + 三查。B-05 含 1 处底座改动须会签;验收口径为 <code>app/ tests/ tools/ knowledge/</code> 范围内 grep 旧值 = 0(<code>docs/**</code> 历史记载<strong>不改</strong>)</td></tr>
<tr><td><strong>C</strong>(9)</td><td>C-01 零容忍集语义纠错 → C-03 安全词表取回 → C-04 补短否定式 → C-06 四向验证 → C-07 删除一期路由</td><td><strong>C-03 必须从留痕文档逐条抄,不许凭记忆重写</strong>(词条缩水不报错);<strong>C-06 → C-07</strong>,直接删一期路由=提示词注入拦截消失</td></tr>
<tr><td><strong>D</strong>(7)</td><td>D-01 检索签名 fail-closed → D-02 按集合逐个拼过滤表达式 → D-03 取回口径 +「过滤后为空」独立指标(v2.5 取消 over-fetch)→ <strong>D-04 删 <code>del context</code>、按身份推导档位</strong> → D-06 访客主体口径</td><td><strong>D-02 的落点有三处</strong>(主检索 + 产品名召回 + 父块召回),<strong>必须同批改</strong>;漏改后两处 → 概括型问句继续静默转人工</td></tr>
<tr><td><strong>E</strong>(8)</td><td>E-01 建单白名单 + priority + <code>reason_code</code> 枚举化 → E-02 日志按 reason 区分 → E-03 出口 → E-04 审计(含 1 项挂起)</td><td><strong>建单白名单必须限定 <code>run.agent_type == "customer_service"</code></strong>——否则改动其它 Agent 的既有建单行为</td></tr>
<tr><td><strong>F</strong>(7)</td><td>F-01 重建含主体过滤的历史读取 → F-05 文档回写 → F-06 dry-run</td><td><strong>F-01 禁止从备份恢复旧实现</strong>——那份正是「只按 <code>session_id</code> 取历史、跨主体可读」的缺陷本体;须在新链路重写并调用 <code>memory_scope</code></td></tr>
</tbody>
</table>
<h2 id="3.4-批次G-访客与鉴权">3.4 批次 G · 访客与鉴权(5 项 · 独立会签组)</h2>
<table>
<thead><tr><th>ID</th><th>任务</th><th>DoD 要点</th><th>依赖</th><th>风险</th></tr></thead>
<tbody>
<tr><td><strong>G-00</strong></td><td>测试环境就位</td><td>门禁可运行并记录基线(见 §1.2)</td><td>—</td><td>LOW</td></tr>
<tr><td><strong>G-01</strong></td><td><strong>访客权威单点化</strong>(方案乙)</td><td>① 三元组<strong>只有一个构造来源</strong>;② <code>security.py</code> 与 <code>runtime.py</code> 均调用它;③ <strong>对外行为零变化</strong>(问答、限流、令牌 TTL 全部不变);④ 新增<strong>接缝单测</strong>:同一输入下两侧产出必须相等(这正是当前缺失的测试)</td><td>G-00</td><td><strong>中</strong></td></tr>
<tr><td><strong>G-01b</strong></td><td>判定口径单点化</td><td>6 处访客判断全部改走统一谓词;⚠️ <strong>Agent 基类那处的「位置与层级」不得变</strong>(仍留在基类、仍不依赖 Agent 声明位)</td><td>G-01</td><td><strong>中</strong></td></tr>
<tr><td><strong>G-02</strong>(挂起)</td><td>身份轴(方案甲)</td><td><code>roles</code> 回归纯 RBAC;20 余处 <code>allowed_roles</code> 中的 <code>"visitor"</code> 迁至身份轴声明</td><td>G-01b、MVP 演示跑通</td><td><strong>高</strong></td></tr>
<tr><td><strong>G-03</strong></td><td>档位推导单点化</td><td>档位由统一谓词推导,<strong>不由工具层自行判身份字段</strong>;⇒ 将来 G-02 落地时只需改 1 个文件</td><td>G-01b、D-01</td><td>LOW</td></tr>
</tbody>
</table>
<h2 id="3.4b-批次H-智能增强">3.4b 批次 H · 智能增强(6 项 · <strong>v1.1 新增</strong>)</h2>
<blockquote class="callout-warn"><p><strong>本批解决什么</strong>:答辩反馈「客服 Agent 不智能、很多问题强制转人工」。根因不是知识库或模型,而是<strong>决策链上只有两个出口</strong>——命中够分就原文直返,其余一律转人工(旧实现里 <strong>10 处</strong>失败方向全部指向转人工)。本批把出口扩到 <strong>5 个</strong>:<code>E1</code> 澄清 / <code>E2</code> 计算 / <code>E3</code> 直返(既有)/ <code>E4</code> 证据约束生成 / <code>E5</code> 分级回退。<strong>详细 DoD 见《执行 Todolist》批次 H</strong>,本表只列计划视角。</p></blockquote>
<table>
<thead><tr><th>ID</th><th>任务</th><th>DoD 要点</th><th>依赖</th><th>风险</th></tr></thead>
<tbody>
<tr><td><strong>H-01</strong></td><td>澄清出口 <code>E1</code></td><td>消费 <code>needs_clarification</code>(<strong>当前全仓无消费分支</strong>);一次只问一个问题、给 2—3 个候选;<strong>候选限当前档位可见</strong>;同话题上限 2 轮</td><td>G-03、D-01</td><td>中</td></tr>
<tr><td><strong>H-02</strong></td><td>计算型出口 <code>E2</code></td><td>参数只取当前档位可见的结构化参数位;<strong>纯函数、不调模型</strong>;未指定产品时只给算法与区间;访客档<strong>分项开放</strong></td><td>H-05、<code>D6.2.1</code> §六补 <code>public</code> 参数位</td><td>中</td></tr>
<tr><td><strong>H-03</strong></td><td>证据约束生成 <code>E4</code></td><td>输入证据包、输出契约固定;三条硬约束<strong>同时落在 prompt 与输出校验</strong>;<strong>输出数字一致性校验</strong>不可解析即回退</td><td>语料侧 <code>family_id</code></td><td><strong>高</strong></td></tr>
<tr><td><strong>H-04</strong></td><td>分级回退 <code>E5</code> + 转人工白名单收口</td><td>回退链澄清 → <strong>部分答 + 引导</strong> → 转人工;转人工<strong>收敛为 4 类白名单</strong>(<strong>删除「连续 2 轮兜底」</strong>);<strong>回退不得跨档位</strong></td><td>H-01、E-01</td><td>中</td></tr>
<tr><td><strong>H-05</strong></td><td>档位分区隔离 + 双 schema 收敛</td><td><code>visibility</code> 作分区键且 <code>NOT NULL</code>;按档位分区裁剪;<strong>取消 <code>over-fetch ×3</code></strong>;<strong>两套建表脚本收敛为一套</strong></td><td>会签(底座公共件)、B-04</td><td><strong>高</strong></td></tr>
<tr><td><strong>H-06</strong></td><td>评测门禁落地</td><td><code>D3.7</code> 46 条金标跑通;<strong>四项零容忍 = 0</strong>;<strong>如实记录修复前基线</strong></td><td>H-01~H-05</td><td>低</td></tr>
</tbody>
</table>
<blockquote class="callout-warn"><p><strong>🔴 前置(S0,缺一不可)</strong>:<code>D3.7</code> §1 的 <code>B-1</code>~<code>B-4</code>——① 重生成 <code>knowledge/_chunks.jsonl</code>(现版 2026-09-16,滞后于镜像源,含旧品牌「南方科技」308 行与已下线产品);② FAQ 镜像<strong>补齐至 64 条</strong>(现 44 条)并逐条打档位;③ 档位字段真正进索引(实测 617 块 <strong>100% 为 <code>public</code></strong>);④ 两套建表脚本收敛。</p>
<p><strong>裁剪顺序(工期紧张时)</strong>:<code>H-01</code> → <code>H-04</code> → <code>H-02</code> → <code>H-06</code> → <code>H-03</code> → <code>H-05</code>。<strong><code>H-01</code> 单点收益最大,不得首先砍掉。</strong></p>
<p><strong>与里程碑的关系</strong>:批次 H 可在 <strong>M4</strong>(身份与护栏就位)后并行推进;<strong>M5 前</strong>至少完成 <code>H-01</code> / <code>H-02</code> / <code>H-04</code>;<strong>M6(最终验收)前</strong>须完成 <code>H-06</code>——金标门禁已并入 §6.2 第 13 条完工判据。</p></blockquote>
<h2 id="3.5-里程碑">3.5 里程碑</h2>
<div class="mermaid">
flowchart LR
M1["M1 冻结完成<br/>批次 A + G-00 完成"] --> M2["M2 语料与话术一致<br/>批次 B 完成"]
M2 --> M3["M3 安全与规则就绪<br/>批次 C 完成"]
M3 --> M4["M4 身份与护栏就位<br/>批次 G-01/G-01b/G-03 + D 批次完成"]
M4 --> M5["M5 业务闭环<br/>批次 E 完成"]
M5 --> M6["M6 演示跑通<br/>批次 F 完成(最终验收)"]
style M1 fill:#e3f2fd,stroke:#1976d2
style M4 fill:#fff3e0,stroke:#f57c00
style M6 fill:#c8e6c9,stroke:#388e3c
</div>
<hr>
<!-- ==================== 4 ==================== -->
<h1 id="4.-底座接触面与两组会签">4. 底座接触面与两组会签</h1>
<h2 id="4.1-组-1-六文件八处">4.1 组 1 · 六文件八处(原计划内,须会签)</h2>
<table>
<thead><tr><th>#</th><th>文件</th><th>触碰项</th><th>改什么</th></tr></thead>
<tbody>
<tr><td>1</td><td><code>service/knowledge_search_service.py</code></td><td><strong>D-01</strong></td><td><code>include_internal: bool</code> → <strong><code>tiers: frozenset[str]</code>(必填、无默认值)</strong>;<code>_visibility_filter</code> 同步按 <code>tiers</code> 拼表达式;非法值收敛为 <code>{"public"}</code></td></tr>
<tr><td>2</td><td>同上</td><td><strong>D-02</strong></td><td>表达式<strong>按集合逐个拼</strong>(<code>filter=expression if schema.has("visibility") else None</code>);缺字段时<strong>不再回落 public</strong>;增加「因缺字段跳过过滤」的独立计数</td></tr>
<tr><td>3</td><td><code>service/knowledge_tool.py</code></td><td><strong>D-04</strong></td><td>删 <code>del context</code>;按身份推导档位后传入检索</td></tr>
<tr><td>4</td><td><code>core/compliance_context.py</code></td><td><strong>C-04</strong></td><td><code>NEGATION_CUES</code> 补短否定式:<code>不保本</code> / <code>不保收益</code> / <code>非保本</code> / <code>并非保本</code> / <code>不确保</code> / <code>不担保</code></td></tr>
<tr><td>5</td><td><code>service/agent/governance.py</code></td><td><strong>B-05</strong></td><td>删旧热线常量与只对旧号生效的脱敏白名单分支 → 内联为固定脱敏返回(<strong>该分支已成为不可达死代码</strong>)</td></tr>
<tr><td>6</td><td><code>service/agent_run_application_service.py</code></td><td><strong>D-06 + F-01</strong></td><td>① 访客消息的 <code>customer_id</code> 按身份取值(访客为空);② 重写历史读取并<strong>调用记忆范围模块</strong>做主体过滤</td></tr>
<tr><td>7</td><td><code>service/agent_persistence_service.py</code></td><td><strong>E-01</strong></td><td>建单白名单(<strong>限定客服 Agent</strong>)+ 赋 <code>priority</code> + <code>reason_code</code> 枚举化</td></tr>
</tbody>
</table>
<h2 id="4.2-组-2-访客与鉴权四文件">4.2 组 2 · 访客与鉴权四文件(<strong>本轮主动扩张</strong>)</h2>
<blockquote class="callout-warn"><p><strong>⚠️ 为什么必须单列</strong>:这 4 个文件<strong>不在组 1 名单内</strong>,其中 3 个还写在「底座零改动清单」里。单列的目的是让「<strong>本次主动扩张了底座接触面</strong>」这件事<strong>可见、可审、可单独回滚</strong>,而不是藏在某处顺手改掉。</p></blockquote>
<table>
<thead><tr><th>#</th><th>文件</th><th>触碰项</th><th>改什么</th><th>最小化边界</th></tr></thead>
<tbody>
<tr><td>2-1</td><td><code>core/security.py</code></td><td><strong>G-01</strong></td><td>访客分支改为调用新增的 <code>actor.anonymous_context()</code>(<strong>行为逐字不变</strong>,只改「值从哪来」)</td><td>不动 <code>jwt.decode</code> 参数、不动 <code>sub</code> 校验、不动 <code>visitor</code> claim 语义</td></tr>
<tr><td>2-2</td><td><code>worker/runtime.py</code></td><td><strong>G-01</strong></td><td>同上——<strong>第二份副本</strong>改为调用同一构造点</td><td>不动 <code>restore_context()</code> 签名、不动身份解析分支</td></tr>
<tr><td>2-3</td><td><code>api/dependencies/auth.py</code></td><td><strong>G-01b</strong></td><td>「跳过身份解析」的条件改为语义化谓词</td><td>不动鉴权入口结构、不动 401 口径、<strong>不新增任何路由</strong></td></tr>
<tr><td>2-4</td><td><code>service/agent/base.py</code></td><td><strong>G-01b</strong></td><td>访客不召回的<strong>谓词</strong>替换</td><td>⚠️ <strong>仍留在基类、仍不依赖 Agent 声明位</strong>——这是底线,不是可选项(不变量②)</td></tr>
</tbody>
</table>
<p><strong>与「零改动清单」的关系</strong>:原清单把 <code>app/api/**</code>、<code>app/worker/**</code>、<code>service/agent/base.py</code> 列为「本次明确不碰」。<strong>本轮明确修订该声明</strong>——上述 4 项由批次 G 触碰,依据是《需求文档》FR-CS-043 / FR-CS-044。<strong>除这 4 个文件外,零改动清单的其余条目继续有效。</strong></p>
<blockquote class="callout-info"><p><strong>📌 额外复核项</strong>:<code>docs/33</code>/<code>docs/34</code> 对 <code>security.py</code> 做过<strong>逐行实证</strong>(涉及 <code>sub</code> 生成、<code>decode</code> 参数、<code>sub</code> 校验、访客分支位置等若干行)。本组改动会<strong>使这些行号漂移</strong>,因此 G-01 完成后<strong>必须复核那些逐行结论是否仍成立</strong>,并在会签单里说明。</p></blockquote>
<h2 id="4.3-会签流程">4.3 会签流程</h2>
<table>
<thead><tr><th>步</th><th>动作</th><th>产物</th></tr></thead>
<tbody>
<tr><td>1</td><td>A-09 落文《可改文件白名单》(四类)</td><td>纪律凭据 + 零 DDL 声明</td></tr>
<tr><td>2</td><td>A-10 落文会签申请单(<strong>组 1 与组 2 各一份</strong>)</td><td>可抄送底座 owner 的正式申请</td></tr>
<tr><td>3</td><td>提交 → 等待受理(<strong>唯一的外部队列</strong>)</td><td>受理记录</td></tr>
<tr><td>4</td><td>受理后由底座方修改(或授权后自改)</td><td><strong>单独 PR</strong>,只含该组 diff</td></tr>
<tr><td>5</td><td>未获受理 → 走降级方案并文档标注</td><td>降级登记</td></tr>
</tbody>
</table>
<hr>
<!-- ==================== 5 ==================== -->
<h1 id="5.-关键路径与约束">5. 关键路径与约束</h1>
<h2 id="5.1-关键路径">5.1 关键路径(12 步 + 1 个会签等待窗口)</h2>
<div class="code-block">
<div class="code-header"><span class="code-lang">text · 最长链</span></div>
<pre><code>[会签窗口:A-10 提交 → 底座方受理 组1 六处 + 组2 四处] ──┐
↓
A-02 → A-07 → G-01 → G-01b → D-01 → D-02 → G-03 → 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 ───┘</code></pre>
</div>
<p><strong>最长链 = 12 步</strong>(含 G 链)。<strong>会签往返不影响步数,但是唯一的外部队列——越早提交越早有回音,故 A-10 排在 A 批次内优先执行。</strong></p>
<h2 id="5.2-十二条硬串行约束">5.2 十二条硬串行约束(违反即返工)</h2>
<table>
<thead><tr><th>#</th><th>约束</th><th>违反后果</th></tr></thead>
<tbody>
<tr><td>S-1</td><td><strong>B-01 门禁 → B-02 改语料 → B-04 重跑</strong></td><td>顺序颠倒会把问题再灌一遍</td></tr>
<tr><td>S-2</td><td><strong>B-05 热线定值 → B-02 填值</strong></td><td>语料里会填进旧的/占位的号码</td></tr>
<tr><td>S-3</td><td><strong>C-06 验证 → C-07 删除</strong></td><td>直接删一期路由 <strong>→ 提示词注入拦截消失</strong>(唯一带安全性的删除)</td></tr>
<tr><td>S-4</td><td><strong>D-02 修过滤表达式 → 任何集合加 <code>visibility</code> / 改字段名</strong></td><td>缺字段的集合被同一表达式打挂 → 被吞成 failure → <code>degraded=True</code> → <strong>客服全员「引导人工」且不报错</strong></td></tr>
<tr><td>S-5</td><td><strong>D-01 fail-closed → D-04 传身份</strong></td><td>身份传进没有 fail-closed 语义的签名 = 把「记得过滤」交回调用方</td></tr>
<tr><td>S-6</td><td><strong>D-03 与 D-01/D-02 同批</strong></td><td>⚠️ <strong>v2.5 口径修正</strong>:原「只有过滤没有 over-fetch → 过滤后 TopK 不足」的理由已<strong>失效</strong>(<code>over-fetch ×3</code> 取消、改集合内分区裁剪,见 <code>FR-CS-033</code>);保留的是「D-01 / D-02 / D-03 三件必须同批落地」</td></tr>
<tr><td>S-7</td><td><strong>集合 schema 变更后必须重启 API + Worker</strong></td><td>检索服务是进程级单例,schema 缓存永不失效</td></tr>
<tr><td>S-8</td><td><strong>禁止在知识出口启用未完成的来源引用链路</strong></td><td>治理层只认 memory/tool 来源 → 返回 knowledge 引用会让<strong>整个 run 失败</strong></td></tr>
<tr><td>S-9</td><td><strong>底座两组未获会签前不得动手</strong></td><td>绕过 = 形成第二套实现,其中一套必然漏掉隔离</td></tr>
<tr><td>S-10</td><td><strong>访客侧开关未做严禁开始出口与演示第 1 步</strong></td><td>服务访客的 Agent 声明缺失 → 游客线<strong>永久失效</strong>,且失败表现为「请登录」,<strong>与设计如此无法区分</strong></td></tr>
<tr><td><strong>S-11</strong></td><td><strong>G-00 → G-01 → G-01b → G-03 → D-04</strong></td><td>① 无测试环境改鉴权链路=盲改;② G-01 不做就改 D-04,档位会依赖两份可能不一致的身份定义;③ D-04 若自行判身份字段,则 G-02 落地时必须改两处,<strong>分离收益归零</strong></td></tr>
<tr><td><strong>S-12</strong></td><td><strong>三处交叉点顺序固定:G-01b → D-06 → F-01</strong>(同一文件不同段)</td><td>倒序会互相覆盖;且访客主体口径依赖身份判定已单点化</td></tr>
</tbody>
</table>
<h2 id="5.3-并行通道">5.3 若多人并行:按「文件不相交」切四路</h2>
<table>
<thead><tr><th>通道</th><th>负责批次</th><th>独占文件</th></tr></thead>
<tbody>
<tr><td>通道 1 · 语料</td><td>批次 B 全部 + F-06</td><td><code>knowledge/*</code>、语料构建脚本</td></tr>
<tr><td>通道 2 · 规则与数据层</td><td>批次 C 全部 + E-01(业务侧)</td><td><code>core/customer_service_rules.py</code>、禁用词种子</td></tr>
<tr><td>通道 3 · 检索护栏</td><td>批次 D 全部 + F-01</td><td><code>knowledge_search_service.py</code>、<code>knowledge_tool.py</code>、<code>core/knowledge_tier.py</code></td></tr>
<tr><td>通道 4 · 出口与文档</td><td>批次 A + E-02/E-04 + F 文档类</td><td><code>implementations/customer_service.py</code>、<code>docs/*</code></td></tr>
<tr><td><strong>通道 0 · 底座会签</strong></td><td>组 1 六处 + <strong>组 2 四处</strong></td><td>各自<strong>单独一个 PR</strong>,由底座 owner 审</td></tr>
</tbody>
</table>
<p><strong>三处交叉点需串行</strong>:① <code>agent_run_application_service.py</code> 由通道 3(D-06)与通道 4(F-01)先后改,且现在又加入 G-01b——<strong>顺序固定 G-01b → D-06 → F-01</strong>;② <code>agent_persistence_service.py</code> 只由通道 2(E-01)改;③ <code>tool_executor.py</code> 仅由条件触发项碰,且排在其后。</p>
<hr>
<!-- ==================== 6 ==================== -->
<h1 id="6.-质量门禁与完工判据">6. 质量门禁与完工判据</h1>
<h2 id="6.1-质量门禁">6.1 质量门禁</h2>
<table>
<thead><tr><th>门禁</th><th>命令 / 判据</th></tr></thead>
<tbody>
<tr><td>代码检查</td><td><code>ruff check app tests tools alembic</code> → 0 错</td></tr>
<tr><td>类型检查</td><td><code>mypy app</code> → 0 错。<strong>注意 SQLAlchemy 补丁版差异会造成量级差异</strong>(曾出现「一边上百个错、另一边 0 错」)</td></tr>
<tr><td>单测 + 契约测</td><td><code>pytest tests/unit tests/contract</code> → <strong>0 failed</strong>(绝对用例数会随开发增减,判据是 0 failed)</td></tr>
<tr><td>集成测</td><td><code>pytest tests/integration</code> → 0 failed。前置:先跑 RBAC 种子与口令脚本</td></tr>
<tr><td>零 DDL</td><td><code>python tools/audit_schema.py</code> → <strong>90 张表(89 业务表 + alembic_version)</strong>,前后一致</td></tr>
<tr><td>文档守卫</td><td><code>python tools/check_authoritative_docs.py</code> 通过</td></tr>
<tr><td>离线兜底</td><td>无依赖环境下的替代验证:全量 <code>py_compile</code> 0 错 + AST 悬空 import 0 条</td></tr>
</tbody>
</table>
<h2 id="6.2-整体完工判据">6.2 整体完工判据(13 条)</h2>
<table>
<thead><tr><th>#</th><th>判据</th><th>怎么验</th></tr></thead>
<tbody>
<tr><td>1</td><td>业务一致性三查全绿</td><td>语料 0 占位符、0 旧品牌;热线/服务时间代码与语料一致;界面名全在站内链接表内</td></tr>
<tr><td>2</td><td><strong>安全只增不减</strong></td><td>红队用例与 A-01 基线逐条对比,<strong>无一条变松</strong></td></tr>
<tr><td>3</td><td>规则三个方向都对</td><td>概念题可答 + 诱导性问法仍拒答 + 访客推荐类请求不产生推介</td></tr>
<tr><td>4</td><td><strong>护栏生效</strong></td><td>访客检索 <code>registered</code> 返回空、客户同期正常;公开问题召回量与基线一致;「缺字段的集合」有独立计数、不再污染降级标记</td></tr>
<tr><td>5</td><td>访客主体一致</td><td>访客消息 <code>customer_id</code> 为空;工单侧同为 NULL;历史读取走记忆范围模块</td></tr>
<tr><td>6</td><td>工单分类正确</td><td>「答不上来」不建单;反诈工单 <code>P0</code>;投诉 <code>P1</code>;主动要人工 <code>P2</code>;<strong>其它 Agent 建单行为未变</strong></td></tr>
<tr><td>7</td><td>门禁干净</td><td>§6.1 全部通过</td></tr>
<tr><td>8</td><td><strong>演示跑通</strong></td><td>MVP 演示步骤全部可见,含「无投资建议类内容」</td></tr>
<tr><td>9</td><td>红线可举证</td><td>适当性不匹配场景可在消息表与审计表中还原「揭示 → 确认」顺序</td></tr>
<tr><td>10</td><td><strong>底座纪律达标</strong></td><td>实际改动文件 ⊆ A-09 白名单;两组每一处都有会签记录(或走降级并文档标注);零 DDL 证据;未绕过统一鉴权/工具/合规/审计流程</td></tr>
<tr><td>11</td><td><strong>访客与角色已分离</strong></td><td>访客三元组只有一个构造来源;访客链路端到端行为与动手前逐项一致;身份判定已全部走单点模块;档位由档位模块推导;三条不变量逐条可举证</td></tr>
<tr><td>12</td><td><strong>文档一致</strong></td><td>四份文档交叉引用无冲突;<code>docs/**</code> 历史记载已按 F-05 回写标记</td></tr>
</tbody>
</table>
<hr>
<!-- ==================== 7 ==================== -->
<h1 id="7.-风险与分工">7. 风险与分工</h1>
<h2 id="7.1-头号风险">7.1 头号风险:投顾清除后失去第二条回归业务线(⚠️ 2026-09-20 已随合并恢复 ⇒ 本条降级)</h2>
<blockquote class="callout-warn"><p><strong>⚠️ 本条必须置顶</strong>:投顾原本是 <strong>6 个底座公共件改动时的对照组</strong>——它是唯一完好的业务线,能在改底座时暴露跨模块回归。它在 2026-09-17 被整体清除后:<strong>底座改动的回归验证只剩客服一条线</strong>。<br><strong>应对三条</strong>:① <strong>G-00(测试环境就位)优先级上调为最前置</strong>;② 底座两组改动<strong>必须全量跑 §6.1 门禁</strong>,不得只跑单测;③ 组 2 单独 PR,便于<strong>独立回滚</strong>。</p></blockquote>
<h2 id="7.2-风险重排">7.2 风险重排(前 8 项)</h2>
<table>
<thead><tr><th>排名</th><th>风险</th><th>最高风险点</th><th>对策</th></tr></thead>
<tbody>
<tr><td>1</td><td><strong>失去对照组</strong>(投顾已清除 → <strong>2026-09-20 已恢复,对照组回归</strong>)</td><td>底座改动无第二条线兜底</td><td>G-00 前置 + 全量门禁 + 组 2 单独 PR</td></tr>
<tr><td>2</td><td><strong>D-02</strong> 过滤器按集合逐个拼</td><td>改错会让客服<strong>全员转人工且不报错</strong></td><td>先补两类替身测试(部分集合有/部分没有、字段名不同)再改实现;上线后立刻验召回量不变;保留独立计数</td></tr>
<tr><td>3</td><td><strong>G-01/G-01b</strong> 身份单点化</td><td>改鉴权入口与 Worker 执行路径,<strong>无测试环境时是盲改</strong></td><td>G-00 必须先完成;新增<strong>接缝单测</strong>(两侧产出必须相等);对外行为零变化的验收方式</td></tr>
<tr><td>4</td><td><strong>C-06</strong> 四向验证 / <strong>C-03</strong> 词表取回</td><td>词表缩水不报错;补偿式修改会放过真实承诺</td><td>从留痕文档<strong>逐条抄</strong>;四个方向用例都必须有;反例守卫</td></tr>
<tr><td>5</td><td><strong>C-09</strong> 访客推介边界</td><td>按主体分化输出规则时可能误伤客户侧(或反之)</td><td>客户侧必须做<strong>回归验证</strong>;<strong>按身份分支而非合并词表</strong></td></tr>
<tr><td>6</td><td><strong>E-01</strong> 建单白名单</td><td>若不限定 <code>agent_type</code>,会改动其它 Agent 的建单行为</td><td>DoD 明确「仅对客服 Agent 生效」;把其它 Agent 的既有建单用例纳入回归</td></tr>
<tr><td>7</td><td><strong>A-07</strong> 访客链路实测</td><td>若游客侧工具未在白名单,<strong>全线失败表现为「请登录」</strong></td><td>实测 + 日志按 reason 区分;A-03 快照留白名单全文</td></tr>
<tr><td>8</td><td><strong>B-04 / F-06</strong> 两次连外部服务</td><td>环境问题伪装成功能缺陷</td><td>先跑 A-03/A-05 快照与自检;B-04 前停 Worker;F-06 先 dry-run</td></tr>
</tbody>
</table>
<h2 id="7.3-分工与交付物">7.3 分工与交付物</h2>
<table>
<thead><tr><th>角色</th><th>职责</th><th>对应批次</th></tr></thead>
<tbody>
<tr><td>后端架构师</td><td>骨架、鉴权与身份、DB、统一响应、知识库接口、会话记忆、合规子模块、转人工、工单</td><td>A / G / E / 部分 B·C</td></tr>
<tr><td>AI 工程师</td><td>文档解析、向量化、集合与检索、意图分类、主流程、事件</td><td>D / 部分 B·C</td></tr>
<tr><td>业务开发(咨询)</td><td>合规词库、适当性规则、客群分层口径</td><td>评审 C / E</td></tr>
<tr><td>全员</td><td>演示准备与最终验收</td><td>F</td></tr>
</tbody>
</table>
<table>
<thead><tr><th>#</th><th>交付物</th><th>形态</th><th>验收人</th></tr></thead>
<tbody>
<tr><td>1</td><td>四份文档(需求 / 开发计划 / 执行 Todolist / 知识库设计方案)</td><td>HTML ×3 + Markdown ×1</td><td>团队 + 讲师</td></tr>
<tr><td>2</td><td>客服 Agent 源码</td><td><code>.py</code></td><td>代码评审人</td></tr>
<tr><td>3</td><td>接口文档(自动生成 + 补充说明)</td><td>在线 + Markdown</td><td>全栈/集成</td></tr>
<tr><td>4</td><td>知识库入库清单与来源映射表</td><td>Markdown</td><td>AI 工程师</td></tr>
<tr><td>5</td><td>测试报告(按 §6.1 门禁实际执行结果)</td><td>Markdown</td><td>全员</td></tr>
<tr><td>6</td><td>演示脚本</td><td>Markdown</td><td>主讲人</td></tr>
<tr><td>7</td><td>开发过程会议纪要(≥ 5 份)</td><td>Markdown</td><td>团队</td></tr>
<tr><td>8</td><td><strong>会签材料</strong>(可改文件白名单 + 会签申请单 ×2)</td><td>Markdown</td><td>底座 owner</td></tr>
<tr><td>9</td><td><strong>清除留痕</strong>(客服形态A + 投顾清除的执行报告与回退备份说明)</td><td>Markdown</td><td>项目负责人</td></tr>
</tbody>
</table>
<hr>
<blockquote class="callout-info"><p><strong>文档结束</strong> —— 本文档为《南方基金·智能服务系统》智能客服 Agent 的<strong>开发计划 v1.0</strong>,是四份交付文档的「计划」分册。开工时以《客服 Agent 执行 Todolist》为唯一入口;本文档定顺序与约束,Todolist 定每项的 DoD 与验证动作。</p></blockquote>
</div><!-- end doc-container -->
</main>
<script>
mermaid.initialize({
startOnLoad: true,
theme: 'base',
themeVariables: {
primaryColor: '#e3f2fd',
primaryTextColor: '#1e293b',
primaryBorderColor: '#1976d2',
lineColor: '#64748b',
secondaryColor: '#fff3e0',
tertiaryColor: '#f4f1eb',
fontFamily: 'Inter, sans-serif',
fontSize: '14px',
},
flowchart: { curve: 'basis', padding: 15 },
securityLevel: 'loose',
});
// ── Progress bar ──
const bar = document.getElementById('progress-bar');
function updateProgress() {
const h = document.documentElement;
const pct = ((h.scrollTop) / (h.scrollHeight - h.clientHeight)) * 100;
bar.style.width = Math.min(100, pct) + '%';
}
window.addEventListener('scroll', updateProgress);
// ── Topbar title ──
const topbarTitle = document.getElementById('topbar-title');
const headings = document.querySelectorAll('.doc-container h1[id], .doc-container h2[id], .doc-container h3[id], .doc-container h4[id]');
function updateTopbar() {
let current = '';
headings.forEach(h => {
if (h.getBoundingClientRect().top <= 120) current = h.textContent;
});
if (current) topbarTitle.textContent = current;
}
window.addEventListener('scroll', updateTopbar);
// ── TOC active state ──
const tocLinks = document.querySelectorAll('.toc-item');
function updateToc() {
let activeId = '';
headings.forEach(h => {
if (h.getBoundingClientRect().top <= 120) activeId = h.id;
});
tocLinks.forEach(link => {
link.classList.toggle('active', link.getAttribute('href') === '#' + activeId);
});
}
window.addEventListener('scroll', updateToc);
// ── Collapsible TOC groups ──
document.querySelectorAll('.toc-toggle').forEach(btn => {
btn.addEventListener('click', () => {
const kids = btn.nextElementSibling;
const arr = btn.querySelector('.arr');
btn.classList.toggle('active');
kids.classList.toggle('open');
arr.classList.toggle('open');
});
});
// ── TOC search ──
const tocSearch = document.getElementById('toc-search');
tocSearch.addEventListener('input', () => {
const q = tocSearch.value.toLowerCase();
document.querySelectorAll('.toc-item, .toc-toggle').forEach(item => {
item.style.display = (!q || item.textContent.toLowerCase().includes(q)) ? '' : 'none';
});
});
// ── Initial ──
updateProgress();
updateTopbar();
updateToc();
</script>
</body>
</html>