Files
group_fqcd_jr/docs/44-演示流程.md
T
张胜宇 5d0becb67d 客服 Agent 重构收口:五出口决策链 + 知识库档位隔离 + 前端入参边界(答辩演示版本)
一、客服 Agent 智能增强(正面回应"不智能、动不动就转人工")
- 决策链由 2 个出口扩到 5 个:E1 澄清 / E2 计算型 / E3 知识直返 / E4 证据约束生成 / E5 分级回退
- 转人工从"默认动作"降为最后一档 E5c,只保留 4 类白名单:
  P0 反诈 / P1 账户与个人数据 / P2 写操作与争议 / 用户明确要求人工
- 46 条金标实测(修复前 → 修复后):
  转人工率 43.5% → 10.9%;出口准确率 45.7% → 100%;事实正确率 69.6% → 100%
  禁忌违反 1 → 0;档位越权 / 无出处数字 / 误拒 四项零容忍全 0
- 安全不变量 INV-1~INV-5;零容忍规则未删,改的是挂载点
  (输出侧字面黑名单 → 检索层档位隔离 + 判定层合规词表 + 输出守护)

二、知识库:档位单点化与物理隔离
- 新增 app/core/knowledge_tier.py 作为档位规则唯一落点(G-03),
  knowledge_contracts.py 原定义块改为显式再导出(X as X,非副本)
- 档位过滤由 bool 默认值(fail-open)改为 tiers 必填集合(缺参即 TypeError)
- Milvus 侧四集合按 visibility 分区键物理隔离;双 schema 收敛为一套
- 新增 app/core/actor.py:访客三元组与匿名判定的唯一构造/判定点(G-01/G-01b)
- 新增 app/core/fund_fee_rules.py:费率计算纯函数

三、前端入参边界对齐(本轮 W11 新修,4 处"校验宽于存储")
- message 加 max_length=8000(与浮窗 widget.js 的 maxlength 一致)
- session_id 加 1—64;idempotency_key 上限 128 → 64(对齐列宽 String(64))
- feedback_type 加 max_length=32(对齐列宽 String(32))
- 8 条路径参数补 min_length=1 + max_length=64 + 字符集正则
  ({session_id} / {run_id} / {handover_id})
- 改前超限值会落到 MySQL 才失败(500);改后一律 422 AGENT_INPUT_INVALID + 字段级定位
- 新增 tests/unit/api/test_frontend_boundaries.py(33 例),含"端点表 ↔ OpenAPI 全量对照"

四、投顾模块整体清除(D4.4 / D4.5)
- 删除投顾相关 controller / schema / model / repository / service 及门户页面
- tools/portal_api_check.py 同步作废 AD003/AD005/AD011/A047 四条用例与 advisor_t 登录
  (端点与账号均已不存在,此前稳定报 3 条假红)

五、验证(提交前实测)
- pytest -q:1856 passed / 2 skipped / 0 failed
- ruff check app tools tests:19(= 基线);mypy app:2(= 基线)
- 前端接口契约体检 portal_api_check.py:38 项,通过 34,失败 0,跳过 4
- 全链路冒烟 e2e_smoke_test.py --read-only:31/31
- HTTP 全链路探针 http_probe.py:11/11 succeeded
- 跨文档一致性 _consistency.py:GATE PASS
- 真机边界复验 12 条:12/12 符合预期

六、纪律与文档
- 可改文件白名单 A-09(docs/46)与底座会签申请单 A-10(docs/47,组 1—组 4 全部受理)
- 零 DDL:未新增/修改任何表结构,89 张业务表与基线一致
- 证据留痕:docs/evidence/**(含 46 条金标 score、快照、清除与重建记录)
- 未提交(刻意排除,见提交说明):仓库内 客服agent/ 与 开发文档/ 是 2026-09-16 前的
  过期副本(Todolist 440 行 vs 权威 D2.1 1167 行),权威正本在仓库外;
  _chunks_report.txt 是 tools/build_knowledge_chunks.py 生成的本地产物
2026-09-20 14:33:30 +08:00

455 lines
29 KiB
Markdown
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.
# 演示流程(照着走)
> **读者**:负责演示的人。本文按"操作 → 看到什么 → 这体现什么"三段式写,
> 可以直接照着念。
> **主线时长**:约 8 分钟;含讲解约 15 分钟。
> **全部内容均已实测**;标 ⚠️ 的地方是容易翻车的点。
> 🔴 **状态更正(2026-09-19)**:本文档写于 **2026-09-13/14**。此后客服 Agent 已**整体重构**
> (五出口 `E1`—`E5c`:澄清 / 计算 / 知识直返 / 证据约束生成 / 分级回退 + 转人工白名单),
> 且**投顾模块已整体清除**(`D4.4` / `D4.5`)。因此:
> - **怎么演**:以 `客服agent\D2.5-客服Agent演示脚本与账号速查-2026-09-19.md` 为**现行口径**(含实测台词表与账号表);
> - **已作废、不要念不要点**:§场景 7(投顾工作台)与文末账号表的 `advisor_t` 行 —— 该角色与账号**库里已不存在**;
> - **已更正**:场景 1 的答复口径、场景 4 的转人工口径、§答疑的三档阈值说明(见各行 🔁 标记)。
>
>
> **2026-09-14 复核**:本文档已按当天实测数据更新四处(「今日盈亏」已实现、投顾推荐方案
> 已开放自助审核发布、管理员工作台为八个标签页、角色权限数与已知空数据)。数字类结论都标了
> 实测日期——**换环境或换库后要重查**。
>
> **配套文档**(想看"为什么"而不是"怎么演"就看这几份):
> - `docs/演示用/全功能流程-大白话版.md` —— 从登入讲到六个门户的全部流程,不用术语;
> - `docs/演示用/记忆系统演示文档-2026-09-14.md` —— **记忆系统怎么演**(含三条可复现命令与实测输出);
> - `docs/演示用/记忆架构与Worker工作流程-大白话版.md` —— 记忆链路与后台 Worker 的详细工作流程;
> - `docs/演示用/今日盈亏实现说明-2026-09-14.md` —— 「今日盈亏」的口径、数据坑与复算命令。
---
## 0. 演示前准备
### 0.1 准备演示数据(首次、或换了机器才需要)
```powershell
python tools/seed_demo_data.py
```
10 步:账号权限 → 演示口令 → 客户账户 → **场内行情** → 风控预警样本 → 投顾数据 →
三类发布配置 → 知识库素材。脚本会逐步打印完成/失败。
> ⚠️ 第 2 步**不是幂等的**:重跑等于重设密码(bcrypt 每次加盐不同)。
### 0.2 一条必须知道的硬规则:**行情有效期只有 15 分钟**
下单要求行情快照落在 **15 分钟内**(`app/service/trade_service.py` 的 `MAX_QUOTE_AGE`),
**系统没有任何自动刷新机制** —— 超时后**所有委托直接返回 `503 行情已过期`**。
演示时踩过这个坑:明明上午刷过行情,下午演示第一单就 503。
**怎么办**:`start.ps1` **已经会替你刷新一次**(见 0.3),所以正常不用管。
万一演示中途下单报 503 —— 在新窗口跑一次就行,**不用重启任何服务**(已实测):
```powershell
python tools/sync_market_prices.py
```
它按产品 upsert(幂等),几秒内把**全部产品(当前库里 26 只)**行情刷新,15 分钟窗口重新计时。
> 不想让启动脚本联网刷行情:`start.ps1 -SkipPriceSync`
### 0.3 启动平台
**最省事的办法:双击桌面上的 `启动金融Agent平台.bat`。** 不用开 PowerShell、不用敲命令。
(仓库根目录也有一份 `启动平台.bat`,是同一个东西。)
> ⚠️ **`启动金融Agent平台.bat` 在桌面上,不在仓库里**(2026-09-14 补注):
> 仓库中只有 **`启动平台.bat`** 这一份(`Glob 启动*.bat` 只命中它)。
> 两份内容是同一个东西、由同一个生成器产出:
> ```
> python tools/make_launcher_bat.py # 改完 start.ps1 或想换路径就重跑它,桌面与仓库两份一起更新
> ```
> **不要手写那个 bat** —— 它必须同时满足 **GBK 编码 + CRLF 换行 + 无 BOM**,
> 缺任何一条 `cmd` 都会解析错乱(LF 换行会把 `echo` 的说明文字当命令执行,
> 实测报 `AT 命令已弃用`、`']' 不是内部或外部命令`)。
它按顺序做六件事:**找解释器 → 检查 MySQL/Redis/Milvus → 刷新行情 → 起两个窗口
→ 等 API 真正应答 → 自动开浏览器**,然后打印访问入口与账号。
> **重复双击是安全的**:API 按端口判断、Worker 按进程判断,已经在跑的不会起第二份。
> 演示前如果服务已经开着,直接双击它就等于"刷新行情 + 打开页面"。
想传参数就用命令行(bat 会把参数原样转给 `start.ps1`):
```powershell
powershell -ExecutionPolicy Bypass -File start.ps1 # 等价于双击
powershell -ExecutionPolicy Bypass -File start.ps1 -NoBrowser # 不自动开浏览器
powershell -ExecutionPolicy Bypass -File start.ps1 -Port 8100 # 换端口(8000 被别的程序占了)
```
它**开两个窗口**:
| 窗口 | 作用 | 少了它会怎样 |
|---|---|---|
| API | 所有接口与页面 | 什么都没有 |
| **Worker** | Agent 真正执行、领域事件投递、知识向量同步、记忆抽取与画像投影、会话片段聚合、**场外收件** | 客服对话一直"超时";新知识不进 Milvus(**无任何报错**);记忆不更新;场外收不到邮件 |
> ⚠️ 两个窗口都别关。启动脚本自己的窗口跑完就可以关(服务在独立窗口里)。
>
> 注:**风控定时扫描不在这个 Worker 里** —— 它是独立进程
> (`python -m app.worker.risk_scan_scheduler`)且默认关闭;演示时用的是风控工作台上的
> **「扫描」按钮**(同步执行,最长 60 秒)。
> 详细分工见 `docs/演示用/记忆架构与Worker工作流程-大白话版.md`。
### 0.4 开场前自检(务必做)
两条线互补,都跑一遍:
```powershell
python tools/e2e_smoke_test.py --read-only # 业务链路:登录→下单→成交、风控闭环、客服问答
python tools/portal_api_check.py # 接口契约:按前端的方式核对每个端点的状态码与字段
```
看最后一行:**44/44** 与 **41 项全通过**才开始演示(用例数会随开发增减,
判断健康的准则是 **0 FAIL**,不是绝对条数)。有 FAIL 就按 §4 排查。
> 两者的分工:`e2e_smoke_test.py` 回答"**这条业务走得通吗**",
> `portal_api_check.py` 回答"**接口返回的东西前端能不能正确显示**"。
> 后者是 2026-09-13 那轮"照着接口把前端测一遍"的固化 —— 当时靠它查出 6 个真缺陷
> (知识库页永远显示 0 块、详情页 5 行全是 `--`、配置项首次编辑必然 409、非法场景值冒成 500 等)。
> 加 `--write` 会再测一遍写操作(会产生数据);`--dangerous` 会动生效配置,**演示前不要加**。
### 0.5 打开第一个页面
<http://127.0.0.1:8000/portal/>
---
## 1. 主线场景
### 场景 1 · 访客咨询与知识边界(1.5 min)
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/guest/products/> |
| **操作** | 点右下角客服浮窗 → 输入「场内基金的管理费率是多少」→ 发送 |
| **预期** | 🔁 **2026-09-19 更正**:知识库**没有**场内基金条目 ⇒ 现在走 **`E5b` 部分答 + 引导**(给出能查到的公开费率资料并邀请改写问法),**不会**再报出「0.15%~1.20%/年」那个区间(那是旧实现的答复)。<br>想演「直接答对」请改问 **「什么叫七日年化?」**(实测 `E3` 原文直返)。<br>另:客服浮窗已于 2026-09-19 **重建恢复**(`乙-15`)—— 此前一段时间它处于「已被整体清除」的状态 |
> ⚠️ 知识库文案是**人工审核入库时的快照**(当前 active 知识 24 份、元数据 199 行),
> 里面写的产品数量**可能与产品列表的当前数量不一致**。
> 被问到时如实说:"这是知识库里的内容,知识是人工入库的,产品列表取的是实时数据。"
> 不要现场改知识库文案来对齐数字。
**这体现什么**:客服不是"模型随便聊",而是**检索已发布知识后作答**,答案可追溯;
延迟稳定在 3–4 秒(一次意图分类 + 一次向量检索)。
**接着再问一句(体现安全边界)**:「我的账户里有多少钱?」
→ 预期:回复"该服务需要登录后才能查询您的个人信息",**不编造账户数字**。
> 产品页的数据来自**真实接口**(`GET /api/v1/products`):产品与净值取自 `fin_product`,
> 行情由 `tools/sync_market_prices.py` 从行情源同步,页面底部有来源声明。
>
> ⚠️ 两个**可能被问到、但其实不是故障**的地方:
> - 「最近涨跌」列取的是**行情源直接给出的当日涨跌幅**(腾讯返回,存在
> `fin_market_price.change_pct`),已接入。若某只显示"暂无",说明它走的是净值降级
> 路径(源没给涨跌幅),或行情没同步过 —— 跑一次 `sync_market_prices.py` 即可。
> 我们**不编**这个数:缺数据就显示"暂无",而不是 0(0 会被读成"今天平盘")。
> - 产品详情的**净值走势图现在是真实数据**:`fin_nav_history` 由
> `tools/sync_nav_history.py`(演示数据第 5 步)从东财净值接口同步,近 **120 个交易日**。
> 若图上显示"尚未接入",说明第 5 步没跑 —— 补跑即可,不用重启服务。
### 场景 2 · 客户资产(1 min)
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/customer/login/> |
| **账号** | `cust_t` / `123456` |
| **预期** | 自动进资产总览:总资产约 10 万、可用资金、持仓市值(7 只持仓) |
**这体现什么**:持仓按**真实行情**计价(不是写死的假数);页面底部有数据来源说明。
点顶部导航「我的持仓」「资金流水」「成交明细」「盈亏分析」各看一眼即可。
> ✅ **「今日盈亏」现在可以直接讲**(2026-09-14 起是真实计算,不再是占位 0)。
> 实测口径与数字(当天数据):`cust_t` 看板 **今日盈亏 −132.70(−0.2528%)**;
> 逐只:`510300` −94.50、`510500` −27.30、`159948` −8.80、`511810` −1.80、
> `515450` +2.00、`588890` −2.30、`160128` 0.00。
>
> 被追问口径时一句话答:**"今天收盘值多少钱 − 昨天收盘值多少钱 − 今天买入花的钱 + 今天卖出收回的钱"
> (不含手续费);基准优先用行情(和同页的"最新价/市值"同源,客户能自己核对),
> 行情只有一天的产品回退用净值。** 语义上是"**最近一个交易日**"而不是盘中实时。
> 想现场证明它没算错:`python tools/check_today_profit_loss.py`(纯 SQL 独立复算并逐只比对)。
> 详见 `docs/演示用/今日盈亏实现说明-2026-09-14.md`。
### 场景 3 · 客户下单(1.5 min)⭐ 重点
| | |
|---|---|
| **打开** | 客户页 → 资产总览 → 「去下单」(**页面本身就有下单弹窗**),或直接用下边的接口 |
| **说明** | 弹窗里填基金代码 / 方向 / 数量即可;接口方式更直观,二者等价 |
```powershell
# 演示用:买入 100 份沪深300ETF
$body = '{"product_code":"510300","order_side":"buy","quantity":100}'
$r = Invoke-RestMethod -Uri http://127.0.0.1:8000/api/v1/auth/tokens -Method Post `
-Body '{"username":"cust_t","password":"123456"}' -ContentType application/json
Invoke-RestMethod -Uri http://127.0.0.1:8000/api/v1/users/me/orders -Method Post `
-Headers @{ Authorization = "Bearer $($r.data.access_token)"; "Idempotency-Key" = [guid]::NewGuid().ToString("N") } `
-Body $body -ContentType application/json | ConvertTo-Json -Depth 4
```
**预期**:`status=已成交`;`executed_price` = **当时的最新行情价**(演示前先刷一次行情,
按页面/返回里的真实数字念,**别背固定价**);成交金额 = 100 × 该价格;
再扣一笔手续费(按费率规则),`net_amount` 就是实际扣款。
**这体现什么**:
- **成交价来自外部行情**,不是模拟随机数;
- 下单前会做**适当性校验**(风险等级匹配),不匹配直接拒绝;
- 成交后委托、成交、资金流水、持仓**四处同时落账**(可当场刷新场景 2 的页面看变化)。
> ⚠️ 如果返回 `503 FUND_QUOTE_UNAVAILABLE` → 跑 §0.2 的行情同步。
>
> ⚠️ 幂等键只防"同一次请求被重发",**在页面上连点两下仍会下两笔**(前端每次提交生成新键,
> 属已知前端缺口)。演示时点一次就好。
### 场景 4 · 客服对话与转人工(1 min)
| | |
|---|---|
| **操作** | 客户页右下角浮窗 → 问「基金定投是什么」→ 点「转人工客服」 |
| **预期** | 先得到知识库答案;转人工返回 `202`,工单进入管理员队列(**新建的工单状态是 `pending`**) |
**这体现什么**:客户**主动**请求人工与"答不上来"是两条不同路径 —— 前者建工单(`P2` 写操作类)。
🔁 **2026-09-19 更正(`H-04` 转人工白名单)**:"答不上来"**不再**转人工。转人工只保留 **4 类**——客户显式要求人工 / `P0` 反诈 / `P1` 账户数据 / `P2` 写操作与争议。现在的作答顺序是「先澄清(`E1`)→ 再合并同章节证据作答(`E4`)→ 不成则给部分资料 + 引导(`E5b`)」;46 条金标实测转人工率由旧实现的 **47.8% 降到 10.9%**。
> ✅ **这张工单现在可以在管理员工作台里被真正处置掉**(2026-09-14 补齐):
> 分配 → 接单 → 解决 → 关闭(未解决可取消),每一步都写审计。
> 演完场景 8 时顺手走一遍,就能把"客户请求人工 → 坐席接管 → 结案归档"讲完整。
> 状态机与权限见 §场景 8 的表格。
### 场景 5 · 风控预警处置(2 min)⭐ 重点
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/employee-console/login/> → `risk_t` / `666666` |
| **预期** | 概览:未闭环预警数、高风险数、待处理、已超时(**2026-09-14 实测:未闭环 7 条 / 预警总数 32 条**) |
> 队列空或想现场造一条:页面上的**「扫描」按钮**会同步跑一次风控扫描(最长 60 秒)。
> 也可以跑 `python tools/e2e_smoke_test.py` —— 它会自动造一条待处理预警并跑完整闭环。
**操作顺序(这是核心闭环)**:
1. 点预警队列里的某条 → 打开详情;
2. 详情里有**八类证据**入口:客户、产品、交易、资金流水、持仓、登录记录、预警、通知;
3. 点「确认接收」→ **处置状态不变**,只把"确认状态"置为已确认(记录谁、何时看到了这条预警);
4. 点「进入调查」→ 处置状态:待处理 → 调查中;
5. 点「完成结案」→ 状态变为**已结案**(只有"调查中"的才能结案),并且**按风险等级扣客户行为分**:
低 3 / 中 5 / 高 20(行为分 0–20 封顶)。
> 另有两个动作可以按需插入:**关闭误报**(→ 已排除,必须填理由)与**升级处理**
> (只打"已升级"标记,**不改处置状态**)。
**这体现什么**:预警处置是**有状态机、有留痕、有连带影响**的 ——
不是改个字段。每一步都记审计(谁、何时、做了什么),结案还会影响客户行为分。
> 若队列里没有「待处理」的预警(都已被处置过),跑
> `python tools/e2e_smoke_test.py` —— 它会自动造一条待处理预警并跑完整闭环。
**补一步「风控助手」(30 秒,很值得演)**:点上方「风控助手」标签 → 点预设的
「当前风险概览」按钮 → Agent 调 `get_risk_overview` **只读工具**返回未闭环预警概览,
结尾并声明"以上仅为查询与复核线索,不构成任何已确认、误报、关闭或升级的处置结论,
最终请由风控专员人工复核并留痕"。
**这体现什么**:Agent 只做**只读查询与研判草案**,处置动作一律留给人工。
实测这条问法命中的是 `risk_overview` 意图、置信度 **1.0000** ——
是配置好的意图分派,不是让模型自由发挥。
### 场景 6 · 风控日报(1 min)
| | |
|---|---|
| **操作** | 风控工作台 → 日报 → 生成 |
| **预期** | 文本**流式**逐段出现(SSE),一共 9 段;完成后可填多个邮箱发送 |
**这体现什么**:长任务走 SSE 流式,而不是让页面转圈等 30 秒。
> 结尾会标来源:模型可用时是"大模型生成",不可用时**自动降级为规则模板并如实标注**——
> 降级不是报错,被问到就这么讲。
> ⚠️ **发送邮件默认是"演练"**:本机 SMTP 开关默认关着、dry-run 默认开着,所以流程会走完、
> 状态会返回,但**不会真的投递**。要真发得先配 SMTP。
### 场景 7 · 投顾工作台(1 min)〔🔴 **2026-09-19 起整节作废 —— 投顾模块已整体清除,不要演**〕
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/employee-advisor/dashboard/> |
| **账号** | ~~`advisor_t` / `abc12345`~~ 🔴 **该角色与账号库里已不存在(投顾模块已整体清除,`D4.4`/`D4.5`)—— 不要念、不要点** |
| **预期** | 概览四张卡 + 客户区(**页面内置 4 位演示客户**:张总/李阿姨/王工/陈医生,对应真实客户号 9101–9104)+ 7 个动作按钮 + 「已发布交付物」 |
**这体现什么**:投顾看到的是**自己服务的客户**的交付物。
数据范围按 `sys_customer_assignment` 归属关系判定(不是按 `data_scope` —— 那会把 all 级权限放大成"看全部客户"),
并且有**灰度开关**:没开放的账号直接 403。
选一个客户点「生成推荐方案」,结果区会给出方案与**六步流水线**进度。
若候选池为空,页面会**把原因写清楚**(证据缺失/流动性不够/适当性不匹配/没有客户目标)+ 计数 + 两条出路。
> **审核与发布的口径(2026-09-14 更新,别再照旧话术讲)**:
> - **产品推荐方案**:**投顾可以自助审核 / 驳回 / 发布给客户**(本次已开放),管理员复核队列同样能做;
> - **投资目标方案书**:投顾做到"录入目标 → 确认目标 → 查看方案书"为止,**审核与发布仍归管理员**。
>
> 没有后端也能演:网址加 `?demo=1` 进**离线演示模式**(本地引擎、无令牌、页面明确标注
> "演示模式 · 本地引擎"、方案标"规则模拟,不构成真实推荐")。**用它时务必主动说明这是演示模式。**
### 场景 8 · 管理员治理(1.5 min)
| | |
|---|---|
| **打开** | <http://127.0.0.1:8000/portal/employee-console/workspace/> |
| **账号** | `admin_t` / `88888888` |
| **预期** | 四张指标卡(有效角色 / 生效配置版本 / 可用模型端点 / 待人工复核)+ **八个标签页** |
**八个标签页,挨个点一遍,每处一句话**:
| 标签页 | 讲解要点 |
|---|---|
| 角色与权限 | 五个角色的权限数(**2026-09-14 实测**):admin **62** / advisor **31** / customer **26** / risk_operator **10** / operator **7**;还能按用户 ID 查"服务端实际解析出来的角色与数据范围" |
| 配置与模型 | 工具白名单、提示词**走发布状态机**(草稿 → 待审核 → 已审核 → 生效,可回滚;被取代的版本落 superseded),不是改配置文件;模型路由规则也在这里 |
| 审计记录 | 每一次权限判定、工具调用都有记录;没有"看敏感详情"的权限时,详情由服务端**脱敏** |
| 转人工工单 | 场景 4 建的工单在这里(**当前库里 40+ 张,多数是冒烟测试件**);只回**二次脱敏**摘要,不返回客户标识与原始对话。**可以处置**:分配 → 接单 → 解决 → 关闭(未解决可取消),按状态给按钮、可按状态筛 |
| 画像候选 | 客服从对话里提炼的画像候选,批准后才进正式记忆 |
| 投顾复核 | 待审的投顾交付物(推荐方案 + 投资方案书)→ 审核通过 / 驳回 / 发布给客户 |
| 知识库 | 上传文档(faq / product / policy)→ 自动切分入库并投向量同步;**客服就是靠它作答**(当前 active 知识 24 份、知识元数据 199 行) |
| 画像漂移复核 | 客户重新测评导致画像变化时人工确认是否采纳 |
**这体现什么**:**权限变更必须过审核流程并留痕** —— 这是金融场景的硬要求,
所以平台刻意**不提供**"直接改权限"的接口。
> ⚠️ 三个"前端未接、后端已有"的小缺口,被问到照实说:配置发布页没有"驳回/回滚"按钮、
> 模型端点页是只读的(当前 3 个端点)、审计页没有筛选框(只看最近 20 条)。
---
## 2. 备选场景(时间充足时)
| 场景 | 怎么演 | 体现什么 |
|---|---|---|
| 配置发布四态 | 管理员工作台建一个草稿版本 → 提交校验 → 审核 → 激活 | 改配置是走流程的,可回滚 |
| 五角色权限对比 | 用五个账号分别登录,看入口守卫各跳哪里 | 前端只是体验层,真正的拦截在服务端权限码与数据范围 |
| 知识入库 | `python tools/seed_knowledge_demo.py` 后复测场景 1 | 知识是**可运营**的,不是写死的 |
| 平台体检 | `python tools/e2e_smoke_test.py` | 40 项全绿,交付质量可自证 |
| 记忆链路 | 客户连问几轮 → 看 `memory_unit` 里新增长期记忆 → Worker 投影到 Neo4j/Milvus | "记住客户"是可查的,不是模型自己说记住了。**完整演法(含一键命令与实测输出)见 `docs/演示用/记忆系统演示文档-2026-09-14.md`**;快速跑:`python tools/memory_demo_chain.py` |
| 后台排障 | 停掉 Worker → 客服变"繁忙" → 查 `agent_run.status='queued'` → 起回 Worker 自动恢复 | 异步三段式的可观测性 |
---
## 3. 可能被问到的问题(建议话术)
**Q:这是真实行情吗?**
A:行情取自公开数据源(腾讯行情),成交价用的就是它。但**交易本身是模拟的** ——
平台是"场内基金模拟交易",不下真实单。
**Q:Agent 会不会乱答?**
A:不会。客服只回答**检索到的已发布知识**,且有置信度门槛:
命中分数 ≥0.75 直接答;0.55~0.75 之间还要求领先次优 ≥0.07。
但 🔁 **2026-09-19 更正**:门槛之下**不再等于"推给人工"**(`H-01`/`H-03`/`H-04`)——顺序是「同章节多块 → 合并作答(`E4`)→ 仍不确定 → 澄清一句(`E1`)→ 再不成 → 部分资料 + 引导(`E5b`)」,**只有 4 类白名单才转人工**。"宁可转人工也不答错"的取舍仍然成立,只是转人工不再是第一选择。
**Q:为什么有些问题答不上来?**
A:知识库是人工审核入库的,目前覆盖交易规则、20 只产品、费率与常见问答。
问不到的地方**先给出能查到的部分资料并邀请您换个问法**(`E5b`),确实需要人工时才转 —— **这是设计而不是故障**。
**Q:数据安全怎么保证?**
A:三层 —— 前端按权限隐藏入口;服务端每次请求**重新解析权限与数据范围**(令牌里不带权限);
工单等管理面接口**不返回客户标识与原始对话**。所有判定都有审计。
**Q:为什么不让管理员直接改权限?**
A:金融场景要求权限变更留痕可追溯,所以统一走配置发布流程(草稿→校验→审核→激活)。
**Q:客户看板上的"今日盈亏"是怎么算的?**
A:**2026-09-14 起是真实计算的**(此前是占位 0,已修)。一句话口径:
```
今日盈亏 = 今日市值 − 昨日持仓市值 − 今日买入金额 + 今日卖出金额 (不含交易费用)
昨日持仓数量 = 今日持仓数量 − 今日买入份额 + 今日卖出份额 (由当日成交反推)
```
基准**优先用行情**(最近两个交易日收盘价,与同页"最新价/市值"同源、客户能自己核对),
行情只有一天的产品(演示数据里 `15911` / `159991-159995`)**回退用净值**。
语义是「**最近一个交易日**相对前一交易日」的盈亏,**不是盘中实时** —— 这句一定要讲,
否则会被当成 bug。
**现场可自证**:`python tools/check_today_profit_loss.py`(纯 SQL 独立复算,与接口逐只比对,
不一致会以退出码 1 结束)。实测:`cust_t` = −132.70(−0.2528%)。
细节见 `docs/演示用/今日盈亏实现说明-2026-09-14.md`。
> 💡 当天买卖会让"今日盈亏"包含当日已实现部分(卖出份额仍算"昨收→卖出价",
> 当日买入的份额只算"买入价→今收")。这是刻意的,不是算错。
---
## 4. 出问题怎么办
| 现象 | 原因 | 怎么办 |
|---|---|---|
| 客户下单返回 `503 行情已过期` | 行情快照过期(**有效期仅 15 分钟**) | `python tools/sync_market_prices.py` —— **立即生效,无需重启服务** |
| 客服一直"繁忙/超时" | **Worker 没在跑** | 看 `start.ps1` 起的第二个窗口;或 `python -m app.worker` |
| **所有页面一起变慢/卡住**(不是某一个请求超时,而是客服、风控、看板**同时**迟钝) | 检索层用的是**同步** Milvus 客户端,单次 `search` 卡住会**阻塞整个 API 进程**(连 Worker 心跳一起停)—— 已知项,见 `代码库全面审查报告-2026-09-14.md` **P1-2** | 先在 Docker Desktop 确认 Milvus 在跑;**不用改代码也不用重启** —— Milvus 恢复后自动正常。这条是「演示版本不修、但要能一眼认出」的典型:平时完全无感,只有 Milvus 抖动时才现形 |
| 改了页面看不到效果 | 浏览器缓存 | `Ctrl+F5` 硬刷新 |
| 知识库问答答不上 | 向量没同步 | 确认 Worker 在跑,然后跑 `tools/seed_knowledge_demo.py` |
| 场外发送通知"成功"但对方没收到 | **SMTP 默认是演练模式**(开关关、dry-run 开) | 这是刻意配置:要在页面上演示"发送"就走完了;要真发得先配 SMTP(见 §5 已知空数据) |
| 风控扫描报"正在扫描中/忙" | 手工扫描与定时扫描**共用同一把数据库咨询锁**,互斥 | 等这一轮结束再点(定时扫描默认是关闭的,一般遇不到) |
| 平台起不来 | MySQL 没起 | 看 `start.ps1` 的依赖检查输出 |
| 风控队列没有"待处理"预警 | 演示样本都被处置过了 | `python tools/e2e_smoke_test.py`(自动造一条) |
**一条命令定位**:
```powershell
python tools/e2e_smoke_test.py
```
它逐条打印 6 条线 40 项的结果,FAIL 的那条就是问题所在。
---
## 5. 速查
**账号**
| 角色 | 用户名 | 密码 | 登录入口 |
|---|---|---|---|
| 客户 | `cust_t` | `123456` | `/portal/customer/login/` |
| 风控专员 | `risk_t` | `666666` | `/portal/employee-console/login/` |
| 管理员 | `admin_t` | `88888888` | 同上 |
| ~~投顾~~ 🔴 **已清除** | ~~`advisor_t`~~ | ~~`abc12345`~~ | **该角色与账号库里已不存在,不要念、不要点** |
| 运营 | `offsite_t` | `offsite123` | 同上(→ 运营工作台) |
> ⚠️ **`advisor_t` 已随投顾模块整体清除(上一行作废)**;`offsite_t` 仍有效但不在 RBAC 种子脚本里(2026-09-14 补注):
> `tools/seed_test_rbac.py` 的演示用户只有 **4 个**——`cust_t`(9001)、`risk_t`(9002)、
> `admin_t`(9003)、`review_t`(9004)。`offsite_t`(9006) 由
> **`tools/grant_*.py` 系列 / 投顾线自己的脚本**创建,**重跑种子不会重建它们**。
> 换机器时若这两个账号登录失败,先查 `sys_user` 里到底有没有这两行,而不是查密码。
> (此点 `docs/40-前端验收清单.md` 已有自述。)
>
> 另注:`sys_user` 已改为「存在则更新、不存在才插入」,故**重跑种子不会弄丢演示密码**。
**常用命令**
```powershell
python tools/seed_demo_data.py # 准备演示数据(10 步,首次/换机器)
python tools/sync_market_prices.py # 刷行情(15 分钟有效期,下单 503 时补跑)
powershell -ExecutionPolicy Bypass -File start.ps1 # 起 API + Worker(自动刷行情)
python tools/e2e_smoke_test.py # 全量体检(6 条线 44 项,含工单处置闭环)
python tools/e2e_smoke_test.py --read-only # 只读体检,不动数据
python tools/check_today_profit_loss.py # 「今日盈亏」纯 SQL 复算并与接口逐只比对
python tools/portal_api_check.py # 接口契约体检(按前端的方式调,41 项)
```
**已知的空数据 / 会"看着像坏了"的地方**(演示时别点进去尴尬;**2026-09-14 实测**)
| 位置 | 现状 | 原因 |
|---|---|---|
| 运营 · 场外邮件 | **当前 4 封**(不是 0 了),但**不会有新的** | 收件依赖真实可用的邮箱配置与常驻 Worker;发件还依赖 SMTP |
| 运营 · 通知发送 | 只"演练",不真发 | SMTP 开关默认关闭 + dry-run 默认开启 |
| 管理员 · 画像候选 | 0 条 | 需要客户对话触发候选抽取,且要管理员批准才进正式记忆 |
| 投顾 · 客户列表 | 页面内置 4 位演示客户 | 前端暂无"查我的客户"接口,真实范围在服务端按归属判定 |
| 风控 · 未闭环预警 | **7 条**(总数 32) | 想造新的就点页面上的「扫描」 |
**端口**:API `8000` / MySQL `3306` / Redis `6379` / Milvus `19530` / Neo4j `7687`