一、密钥轮换(新增工具 + 操作手册) - 新增 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。
894 lines
66 KiB
HTML
894 lines
66 KiB
HTML
<!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 个提交反向依赖被清除的模块,裁定「组员新功能 > 本地清除」)。本轮客服侧工作<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>
|