模块 1 · 问数入口
问数工作台 vs 分析对话
两套门,别走错
数据分析 Agent 走平台鉴权(get_platform_auth_context),
和对话线不同——不要带 X-Agent-Type。
像进档案室查账:只出示工牌(JWT),不用说明「我是来聊天还是来问数」。
前端两条菜单
问数工作台
/app/analytics/query → AnalystQueryPage
真·NL→SQL:表格 + SQL + 解读,调 POST /api/analyst/chat
分析对话(占位)
/app/analytics/chat → AnalystChatShell
横幅写明「轻量对话占位;查数请用问数工作台」——还没接独立问数图
analyst.py) / NL→SQL 编排(analyst_agent.py);
要聊天体验才碰 analytics/chat 占位页,别和问数混成一个接口。
domain=self,
SQL 安全校验(sql_guard.py)强制 SQL 带本人 customer_id;免责声明追加 CUSTOMER_AI_RISK_NOTE。
后端「四件套」路由
全部挂在 问数 REST(app/api/analyst.py),前缀 /api/analyst:
群聊:分析员点「提问」之后
STAFF-20001 在问数工作台输入「客户总数是多少」——简化版:
await apiFetch('/api/analyst/chat', {
method: 'POST',
token, // Bearer JWT only
body: JSON.stringify({ question, session_id }),
})
问数和读持仓一样:登录令牌够了,不用声明 Agent 类型。
后端用角色(analyst/advisor/customer…)决定能查哪片数据域,不靠 X-Agent-Type。
问数工作台调 POST /api/analyst/chat,请求头要带 X-Agent-Type: analyst 吗?
模块 2 · NL→SQL 管线
一句话怎么变成
可审计的表格?
NL→SQL 编排(app/service/analyst_agent.py)把用户问题拆成可测试步骤。
像翻译官 + 安检 + 记账员:先弄清指标含义,再生成 SQL,层层校验后才执行,最后解读并留痕。
七步流水线(动画)
点击「下一步」看 NL→SQL 在代码里怎么走:
校验失败 → 403 或 degrade(只给表格不给瞎编解读)
SQL 安全校验(sql_guard.py)在拦什么
只读白名单
拒绝 INSERT/UPDATE/DELETE 等写操作关键字
表白名单
core_* + risk_alert 等,超出即 403
行级归属
customer 域强制 customer_id = 本人;advisor 只能名下客户
粒度控制
ops 聚合域禁止下钻到单个客户明细
amb = self._detect_ambiguity(question)
if amb: return self._clarify(amb, trace_id)
sql_text, usage = self._generate_sql(...)
vres = validate(sql_text, domain, scope) # sql_guard
exec_result = self.repo.execute_readonly(sql_text)
answer, guard = self._generate_verified_answer(...)
self._persist(...) # analytics_query_log
指标说不清就先反问,不瞎猜 SQL。
生成后必须过 SQL 安全校验(sql_guard.py),执行只走只读连接。
AI 解读若数字对不上表,降级为「请看表格」;全程写入查询日志备查。
数字护栏(guardrail)校验失败时,用户还能看到查询结果表格吗?
模块 3 · Agent 与页面
AnalystAgent.run
+ 看板与资产 API
问数主脑是 AnalystAgent.run;页面侧 AnalystQueryPage(P2)
同时拉 GET /dashboard 卡片和 POST /chat 结果。
口径沉淀走 POST /assets(仅 analyst 角色可写)。
AnalystAgent.run 要点
鉴权与域
assert_analyst_query_access → domain;resolve_analyst_scope → customer_id 白名单。
消歧与生成
_detect_ambiguity → clarify;否则 模板填参 或 _generate_sql。
执行与缓存
validate → execute_readonly;Redis 权限指纹 + SQL + 表世代;写交易/预警/L3 后 bump。
解读与留痕
_generate_verified_answer + guardrail;_persist 写 analytics_query_log。
template_hit, cache_hit, cost_est)+ disclaimer + status(success / degrade / clarify)。
dashboard / assets API
GET /dashboard
按角色返回 cards 文案 + metrics:analyst 全库统计、customer 本人持仓、advisor 名下 AUM 等。
POST /assets
kind=dict/few_shot/template;仅 analyst 角色,写入口径资产表。
GET /ops/metrics
运营侧查询总量与被拦截次数,与 dashboard 卡片分离。
useEffect(() => getAnalystDashboard(token))
setDashMetrics(d.metrics)
setDashCards(d.cards)
// 用户点查询
const resp = await postAnalystChat(token, question)
setResult(resp) // Table + Collapse(SQL)
进页面先拉看板 MetricCard,数字来自 /dashboard 不是 chat。
提问才走 AnalystAgent.run,结果区展示解读、表格、可折叠 SQL。
apiFetch 只带 token,与 risk.ts 的 X-Agent-Type 头无关。
理财师 STAFF-10086 能调用 POST /api/analyst/assets 沉淀口径吗?
模块 4 · 红线与排障
问数只统计不处置
还有几个常见空结果坑
问数 Agent 能出表格和解读,但不能关预警、不能当投顾、不能改画像。 另外「消歧总空」「改占位 Chat 没用」是 vibe coder 高频踩坑——这模块专门讲清。
问数三条红线(合规 §4.3)
不处置预警 — 不能调用 POST /api/risk/alerts/{id}/handle;台账只读统计。
不当投顾 — 输出带免责声明;禁止生成可执行买卖/处置指令。
不改 L0/L1 画像 — SQL 只读;customer 域仅 self 趋势统计,不下钻他人。
/api/analyst/chat + SQL 安全校验(sql_guard.py),不要混接口。
排障:消歧为什么总空?
口径字典在 MySQL,本机须执行一次 scripts/agent/seed-analyst-metric-dict.sql。没灌种子 → NL2SQL 消歧永远不命中,不是 LLM 「坏了」。
AnalystChatShell → 占位 UI,未接问数 API
AnalystQueryPage → POST /api/analyst/chat
改 Shell 不会出 SQL 表格
菜单里两个「分析」入口可能让人改错文件——问数逻辑在 NL→SQL 编排(analyst_agent.py) + 问数页。
Redis 问数缓存键含权限指纹,别只 hash SQL 字符串,否则可能串权。
问数 401,AI 建议给 analyst.ts 加 X-Agent-Type: analyst,你怎么回?
模块 5 · D-06
模板填参 + 结果缓存
写侧 bump 失效
问数两层「快路径」:① 同形态问题命中 published 模板,跳过 LLM 写 SQL(template_service.py)
② 执行结果进 Redis,键里带表世代——交易/预警/L3 写后 bump,不等 TTL 过期。
两层 D-06 各管什么
| 层 | 入口 | 答辩句 |
|---|---|---|
| 模板缓存 | match_template → 填 :days 等 |
「客户+总数」→ template_hit: true,省 LLM 写 SQL |
| 结果缓存 | cache_service.py 权限指纹 + SQL hash + 世代 |
「同 SQL 同权限」秒回;写侧 bump 后旧键自然 miss |
| 写侧失效 | analyst_cache_invalidate 交易/预警/L3 后 |
「演示里刚模拟交易,问数不会还显示旧汇总」 |
meta.template_hit · cache_hit)。
clarify? → return suggestions
template match? → fill SQL → validate → execute
else LLM generate_sql → validate → execute (cache?)
guardrail → persist analytics_query_log
像预制菜 + 冰箱贴保质期:常问题型直接拿模板填天数;跑完的结果贴冰箱,但 Core 有新交易就撕掉旧标签(bump)。
种子:scripts/agent/seed-analyst-query-templates.sql · 一键演示:prepare_all.ps1。