Files
group_xinghuo_jinrong/docs/项目框架设计/数据分析Agent-合并说明.md
zhanghongyu_0626 bcd6175d4e feat(analyst): Implement data analysis agent with authentication and query handling
- Introduced `analyst_auth_adapter.py` for managing authentication context and access control for the data analysis agent.
- Added new API endpoints in `analyst.py` for chat, dashboard, asset management, and metrics, utilizing the new authentication context.
- Created Pydantic models in `analyst_schemas.py` for request and response structures, ensuring consistent data handling.
- Updated SQL guard logic in `sql_guard.py` to enforce access restrictions based on user roles and contexts.
- Implemented migration scripts for new database tables related to the data analysis agent, enhancing data management capabilities.
- Removed legacy authentication code from `auth.py`, streamlining the authentication process.

This update significantly enhances the data analysis capabilities, providing a robust framework for querying and managing data securely.
2026-09-09 21:02:11 +08:00

359 lines
19 KiB
Markdown
Raw Permalink 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.
# 数据分析 Agent · 从 `data-analysis-agent-work` 合并说明
> 日期:2026-09-09
> 远程分支:`xinghuo/data-analysis-agent-work` @ `b19a241`(zyi)
> 原则:**只增不盖**;与 `merger` 同路径冲突时 **以 merger 为准**(不覆盖 `main.py` / `deps.py` / `chat.py` / `agent_service.py` 宿主骨架)。
> 状态:**接缝设计稿(未 merge、未接线)**
---
## 0. 一句话定位
数据分析 Agent = **NL2SQL 只读查数线**:自然语言 → 安全 SQL → 表格 + 解读 + 留痕;与宿主 **通用对话线**(`agent_service` 轻提示词 Chat)是两条不同产物,接缝上 **并行挂载**,不替换 `agent_service.py`。
---
## 1. 合并什么(新增文件清单)
从 `data-analysis-agent-work` **整包迁入**(无冲突或仅新增路径):
| 类别 | 路径 | 作用 |
| --- | --- | --- |
| 路由 | `app/api/analyst.py` | `/api/analyst/chat` · `/dashboard` · `/assets` · `/ops/metrics` |
| 编排 | `app/service/analyst_agent.py` | LangGraph StateGraph(input_guard → … → audit_persist) |
| 安全 | `app/service/sql_guard.py` `guardrail.py` | 五层 SQL 白名单 + 数字护栏 |
| 支撑 | `analytics_repo.py` `dict_service.py` `cache_service.py` `schema_meta.py` | 留痕/口径/缓存/元数据 |
| LLM | `app/service/llm.py` | DeepSeek 封装(分析线专用;与 merger `embedding.py` 不冲突) |
| 模型 | `app/model/schemas/analyst.py` | 输出四件套 Pydantic(answer/table/sql/meta) |
| 实体 | `app/model/entities/analytics.py`(若远程有) | analytics_* ORM 映射 |
| 工具 | `app/tool/sql_tool.py`(若远程有) | 只读执行 + 行数/超时 |
| 文档 | `数据分析Agent架构说明书.md` `数据分析Agent开发清单.md` `03-mysql-analyst专用.sql` | 架构 / 任务 / DDL |
| 测试 | 见 §6 | 重命名后迁入,避免覆盖 merger 730 基线 |
**不迁入(冲突 · 保留 merger):**
| 文件 | 原因 |
| --- | --- |
| `app/main.py` | AL-09 全量宿主入口;仅 **追加** `include_router(analyst_router)` |
| `app/api/deps.py` | merger 完整 JWT + 归属 + 矩阵(382 行);远程版被砍成 22 行 stub |
| `app/api/chat.py` `app/service/agent_service.py` | 四 Agent 通用对话线已集成 |
| `app/config/settings.py` | merger 含 risk/customer/visitor/JWT 全量;**追加** `analyst_*` 字段 |
| `app/model/schemas.py` | 保留单文件;分析专用模型放 **`schemas/analyst.py` 子模块**(见 §3.3) |
| `app/repository/core_ro.py` | merger 版含 `check_suitability` 等平台能力;分析线只 **调用** 不覆盖 |
| `app/utils/auth.py` | 远程第二套鉴权(`subject_id`);**废弃**,改 deps 适配(§4) |
| `docs/memory/*`、前端 `web/`、风控/平台 API | 全部保留 merger |
---
## 2. 数据库(需手工执行)
### 2.1 已有(merger canonical)
`docs/项目框架设计/表设计/02-mysql-agent专用.sql` 已含 **`analytics_query_log`**(NL2SQL 留痕)。merger `entities.py` 已有 ORM。**无需重复建表**(若本机已跑过 02 迁移)。
### 2.2 新增(分析资产三表 · D-07 / D-11)
远程 `03-mysql-analyst专用.sql` → 独立迁移脚本(不修改 canonical 02 文件):
```text
scripts/agent/migrate-analyst-d07-d11.sql
→ analytics_metric_dict 口径字典
→ analytics_few_shot 问题→SQL 示例
→ analytics_query_template 参数化模板
```
执行时机:merge 代码后、跑分析线单测/E2E 前;库 `jinrong_agent`,前置 01/02 共用底座已存在。
### 2.3 Redis
复用 merger Docker Redis(`:6380`)。分析线 `cache_service` 使用独立 key 前缀(对齐 `02-redis-keys.md` 分析段);与客服/游客/风控 key **命名空间隔离**,同实例不冲突。
---
## 3. S3 接缝设计(推荐方案 · 待你确认 §8)
### 3.1 入口策略:**并行双轨**(推荐)
| 入口 | 行为 | 前端页面 |
| --- | --- | --- |
| **`POST /api/analyst/chat`** | `AnalystAgent.run()` → JSON 四件套(table/sql/meta/disclaimer) | **`/app/analytics/query` 问数工作台**(主链路) |
| `GET /api/analyst/dashboard` | D-12 智能看数卡片 | `/app/analyst/home` 可接 metrics |
| `POST /api/analyst/assets` | D-11 资产沉淀 | 问数页「沉淀」按钮(P1 后做) |
| `GET /api/analyst/ops/metrics` | N-08 运营指标 | 分析员运维面板(P1 后做) |
| `POST /api/chat` + `X-Agent-Type: analyst` | **仍走 `agent_service`**(轻量对话 stub,无 NL2SQL) | **`/app/analytics/chat` 分析对话**(可选保留) |
**为何不把 analyst 并入 `chat.py` 分流?**
- 问数响应含 **结构化 table + sql + meta**,与 Chat 的 `{reply, has_disclaimer}` 形状不同;硬塞进 SSE 需额外 adapter 层,收益低。
- 架构说明书 §13 已定义独立 `/api/analyst/*`;与客服 S2「customer 分流」场景不同(customer 仍是 Chat 形态)。
**`main.py` 改动(唯一宿主挂载点):**
```python
from app.api.analyst import router as analyst_router
app.include_router(analyst_router) # prefix 已在 analyst.py 内:/api/analyst
```
### 3.2 鉴权接缝:**deps 为准 + 薄适配**
远程代码混用 `deps.get_auth_context` 与 `utils.auth.AuthContext`(字段 `subject_id` vs `actor_id`)。合并后:
1. **删除**迁入后的 `app/utils/auth.py`(或保留空 shim 仅 re-export,最终删除)。
2. 新增 `app/api/analyst_auth_adapter.py`(或扩展现有 `auth_adapter.py`):
```python
def analyst_auth_from_deps(ctx: deps.AuthContext) -> AnalystAuthView:
"""deps.AuthContext → 分析线内部视图(仅字段映射,不做权限判定)。"""
return AnalystAuthView(
subject_id=ctx.actor_id, # 分析 repo 写 staff_id 用
roles=ctx.roles,
permissions=ctx.permissions,
token_type=ctx.token_type,
trace_id=current_trace() or "",
agent_type="analyst",
)
```
3. `analyst.py` 路由层:`auth = Depends(get_auth_context)` → 适配后传入 `AnalystAgent.run()`。
4. 远程 `assert_analyst_access(auth)` 逻辑 **迁入** `deps.py` 或 `app/utils/authz.py`(与 `assert_customer_access` 并列),使用 `actor_id` 命名。
5. **`X-Agent-Type`**:`/api/analyst/*` **不要求**该头(路由即 analyst);JWT 矩阵仍校验 token 角色含 `analyst`/`compliance`/`advisor`/`risk_officer`/`ops`(按远程 `assert_analyst_access` 域划分)。
### 3.3 Schemas:**单文件 + 子模块共存**
merger 当前 `app/model/schemas.py` 为单文件。合并策略:
- **新增** `app/model/schemas/analyst.py`(仅分析请求/响应)。
- **不**把 `schemas.py` 改成包(避免大面积 import 破坏);`analyst.py` 路由 `from app.model.schemas.analyst import ...` 直引子路径。
- 若 Python 包解析与 `schemas.py` 文件冲突:将子模块改为 `app/model/analyst_schemas.py`(备选,merge 时二选一)。
### 3.4 settings 追加字段(合并冲突手工解)
在 merger `settings.py` **追加**(取自远程,命名对齐架构说明书):
| 字段 | 用途 |
| --- | --- |
| `analyst_llm_model` / `analyst_llm_timeout` | DeepSeek NL2SQL |
| `analyst_sql_row_limit` / `analyst_sql_timeout_sec` | 只读执行上限 |
| `analyst_cache_ttl_sec` | 结果缓存 |
| `analyst_rate_limit_per_min` | 问数限流 |
`.env.example` 同步追加;**不**覆盖 merger 已有 `jwt_*` / `risk_*` / `customer_*`。
### 3.5 requirements
merger 已有 `langgraph`;**追加** `sqlglot>=25.0.0`(远程新增,SQL AST 白名单硬依赖)。
### 3.6 core_ro / llm 边界
| 模块 | 策略 |
| --- | --- |
| `core_ro.py` | 保留 merger;`analytics_repo.execute_readonly` 走 **agent 库只读账号** 或现有 Core RO 连接,不 duplicate |
| `llm.py` | 迁入分析线专用;`AnalystAgent` 只 import 此模块 |
| `input_guard` | 分析线复用 merger `app/service/input_guard.py`(限流/注入),不保留远程 duplicate `utils/input_guard.py` |
---
## 4. 前端接缝(merger 现状 → 目标)
### 4.1 现状
| 路由 | 现状 |
| --- | --- |
| `/app/analytics/query` | **PlaceholderPage**(待 NL2SQL) |
| `/app/analytics/chat` | `ChatPanel` + `agentType="analyst"` → `POST /api/chat/stream`(无 table/sql) |
### 4.2 推荐(与 §3.1 双轨一致)
| 页面 | API | UI |
| --- | --- | --- |
| **问数工作台** `/app/analytics/query` | `POST /api/analyst/chat` | 新问题组件:`AnalystQueryPanel`(问题框 + 解读 + Ant Design Table + SQL 折叠 + disclaimer) |
| **分析对话** `/app/analytics/chat` | 暂保留 `ChatPanel` SSE | 页顶 Banner 提示:「复杂查数请用问数工作台」;P2 可下线或改跳转 |
新增 `web/src/api/analyst.ts`:
```typescript
export interface AnalystChatResponse {
answer: string
table: { columns: string[]; rows: unknown[][] }
sql: string
meta: { exec_ms: number; row_count: number; cache_hit: boolean; data_as_of?: string }
disclaimer: string
status: string
trace_id?: string
}
```
**不**强行让 `ChatPanel` 解析四件套(职责分离)。
### 4.3 首页看板
`AnalystMarketDashboard` QuickAction「问数工作台」已指向 `/app/analytics/query`;merge 后可接 `GET /api/analyst/dashboard` 填充 metrics 卡片(P1)。
---
## 5. 需求 ID 对齐
| 需求 | 合并后落点 | 阶段 |
| --- | --- | --- |
| D-01~D-04 | `analyst_agent` + `sql_guard` + `analytics_query_log` | merge 后冒烟 |
| D-05 | 多表只读聚合(sql_generate 节点) | 同上 |
| D-06 | `cache_service` 双层缓存 | 同上 |
| D-07 | `dict_service` + `analytics_metric_dict` | 需跑 migrate-analyst |
| D-08~D-10 | 拒答分支 + `guardrail` | 同上 |
| D-11 | `/assets` + 三资产表 | migrate + 前端 P1 |
| D-12 | `/dashboard` | 首页 P1 |
| N-01~N-08 | 见架构说明书 §4/§6 | 单测覆盖 |
---
## 6. 测试策略
### 6.1 远程测试文件 → 迁入命名
| 远程 | 迁入后 | 说明 |
| --- | --- | --- |
| `tests/test_agent.py` | `tests/test_wave6_analyst_agent.py` | 改 import:`utils.auth` → `analyst_auth_adapter` |
| `tests/test_sql_guard.py` | `tests/test_wave6_sql_guard.py` | 无冲突,直迁 |
| `tests/test_guardrail.py` | `tests/test_wave6_guardrail.py` | 若 merger 已有同名则合并 case |
| `tests/test_dict_service.py` | `tests/test_wave6_dict_service.py` | |
| `tests/test_cache_service.py` | `tests/test_wave6_cache_service.py` | merger 已有 `test_cache_service.py` → **合并或 rename 远程** |
| `tests/test_llm.py` | `tests/test_wave6_analyst_llm.py` | 避免与 merger LLM 测试混淆 |
### 6.2 基线要求
- merge + 适配完成后:`python -m pytest` → **730 + N passed**(N = 新增 wave6 条数)。
- 禁止恢复远程对 `deps.py` / `conftest.py` 的删减。
### 6.3 冒烟清单(Scope B · merge 后)
1. `POST /api/analyst/chat` + analyst JWT → 200 + 四件套 JSON
2. 顾问 token 查他人客户 → 403 + `analytics_query_log(blocked)`
3. `GET /api/analyst/dashboard` → 200 + cards
4. 前端 `/app/analytics/query` 真跑一条问数
5. `POST /api/chat/stream` + analyst → 仍 200(stub 未回归)
---
## 7. Merge 操作顺序(执行 SOP · 尚未做)
```text
1. git worktree add ../JinRong-analyst-merge data-analysis-agent-work # 可选隔离
2. git merge data-analysis-agent-work --no-commit # 或 cherry-pick 新增文件
3. 冲突文件按 §1「不迁入」表逐项保留 merger 版
4. 手工:analyst_auth_adapter · main.py include_router · settings 追加 · requirements sqlglot
5. 删除 utils/auth.py · 改 analyst.py / analyst_agent.py import
6. 执行 scripts/agent/migrate-analyst-d07-d11.sql
7. pytest 全绿 → 前端 AnalystQueryPanel → Scope B 冒烟
8. 更新 docs/memory/TODO.md · FRAMEWORK 实现状态表
```
---
## 8. 已确认决策(2026-09-09)
| # | 决策 | 结论 |
| --- | --- | --- |
| **Q1** | 问数主入口 | **A · 独立 `POST /api/analyst/chat`**;各角色在权限范围内自助查数(见 §9) |
| **Q2** | `/app/analytics/chat` | **A · 保留** ChatPanel stub + Banner 引导问数工作台 |
| **Q3** | schemas 路径 | **A · `app/model/schemas/analyst.py`**(merge 时若与 `schemas.py` 包冲突再改 `analyst_schemas.py`) |
| **Q4** | merge 方式 | **A · `git merge` + 手工解冲突**(默认,未单独拍板则按此执行) |
---
## 9. 鉴权接缝 · 角色矩阵与缺口(**merge 前必须补**)
### 9.1 目标口径(产品确认)
> **每个 Demo 账户角色都能调用数据分析模块,只看其权限范围内的数据。**
前端已把「数据分析」放进 **客户 / 理财师 / 风控** 的 `sharedPlatform` 菜单(`/app/analytics/query`);后端远程实现 **尚未覆盖 customer**,且与 merger `deps` 矩阵不一致,需在 S3 接缝一并补齐。
### 9.2 目标角色 × 数据域(接缝后应达到)
> Demo 仅四角色:**customer · advisor · analyst · risk_officer**(无 compliance / risk_manager 独立账号;问数线不单独开域)。
| 角色 | token | 数据域 key | 可见范围 | sql_guard 要点 |
| --- | --- | --- | --- | --- |
| **customer** | customer | **`self`(待实现)** | 仅本人可读数据(持仓/流水/风评/净值趋势等) | 禁止其他 `CUST-*`;强制 `customer_id = auth.customer_id`;**见 §9.6 产品约束** |
| **advisor** | staff | `assigned` | 名下客户(`core_customer_advisor` active) | 远程已有;`inject_ownership` + 白名单 |
| **analyst** | staff | `full` | 全量 + 敏感列策略 | 远程已有 |
| **risk_officer** | staff | `risk` | 预警台账全量 + 客户/持仓/交易只读(脱敏列) | 远程已有;**表域评估见 §9.7** |
| **ops** | staff | `aggregate` | 无客户维度,仅聚合 | 远程已有(非 Demo 角色,预留) |
### 9.6 客户问数 · 产品约束(2026-09-09 确认)
客户走 **`self` 域**,能力边界如下(merge 时在 `analyst_agent` 解读阶段 + prompt 硬约束):
| 项 | 口径 |
| --- | --- |
| **允许** | 基于本人可读数据的**趋势总结、分布描述、数量统计**(如持仓结构、近 N 日交易笔数、盈亏区间描述) |
| **禁止** | **投资建议、收益承诺、买卖时点、产品推荐**;命中则拒答或降级为「仅展示表格」 |
| **尾部声明** | 在标准 `disclaimer` 之外,客户域回复**追加**:「**AI 分析有风险,仅供参考。**」 |
| **留痕** | `analytics_query_log.actor_id = customer_id`,`actor_role = customer` |
实现落点(merge 时):
- `scope_resolve`:`token_type=customer` → `domain=self`,`scope=[auth.customer_id]`
- `sql_guard.validate(domain=self)`:同 advisor 归属逻辑,但 scope 固定单人
- `_generate_sql` / `ANSWER_SYSTEM`:注入「仅描述数据、不给建议」
- `answer_compose`:`domain=self` 时 append 客户专用尾注
### 9.7 风控专员 · 表域是否够用(2026-09-09 评估)
**结论:够用。** 风控 Agent **自动监督触发的结果**(规则引擎出单、AML 命中、适当性拦截、集中度/代理人行为链等)**权威落点都在现有白名单内**;风控专员问数线是**只读查台账与关联上下文**,不通过 NL2SQL 触发处置或扫名单。
| 监督链路(风控 Agent / 引擎) | 落库/可读表 | 已在 sql_guard 白名单 |
| --- | --- | --- |
| RISK-001~006 交易/持仓规则 | `risk_alert`(`alert_type` + `payload`/`triggered_rules`) | ✅ |
| RISK-006 集中度 | `risk_alert`(`pattern` + `payload.alert_subtype=concentration`)+ `core_holding` | ✅ |
| RISK-008 代理人行为链 | `risk_alert`(`pattern` + `payload.alert_subtype=agent_behavior`) | ✅ |
| AML 命中 | `risk_alert`(`alert_type=aml`)+ `customer_profile_l3` 标记 | ✅ |
| 适当性拦截 | `risk_alert`(`alert_type=suitability`) | ✅ |
| 客户监测画像 | `customer_profile_l1/l2/l3` | ✅ |
| 关联上下文(客户/持仓/交易/产品) | `core_*` 系列 | ✅ |
**刻意不进白名单(安全/职责分离):**
| 表 | 原因 |
| --- | --- |
| `risk_aml_list` | 反洗钱**名单本体**;命中结果已在 `risk_alert`,不应 NL2SQL 直查名单库 |
| `audit_log` | 审计明细走 HTTP/专用 Tool;问数以 `risk_alert` 聚合即可 |
**可选 P2(非阻塞 merge):** 若后续要问「适当性判定历史通过率/明细」,可把 `risk_suitability_log` 加入白名单并在 `risk` 域只读;当前 Demo 问数(待处理预警数、按类型分布、客户监测分布)**不依赖**该表。
**域规则:** 维持远程 `domain=risk`(台账全量 + 白名单内客户只读),**不新增** compliance/manager 域;与现网风控 Demo(`STAFF-30001` · risk_officer)一致即可。
### 9.3 远程 vs merger 缺口清单
| 缺口 | 远程现状 | merger 现状 | 接缝动作(记入 TODO) |
| --- | --- | --- | --- |
| **G1 customer 不可用** | `assert_analyst_access` 要求 `token_type=staff` | 平台 G-01 支持 customer 本人 | 新增域 `self`;`/api/analyst/*` 入口允许 customer token |
| **G2 sql_guard 无 self 域** | 仅 full/assigned/risk/aggregate | — | `validate()` + `inject_ownership()` 支持 `self` + 强制 `customer_id = auth.customer_id` |
| **G3 双套 AuthContext** | `subject_id` + `utils/auth.py` | `actor_id` + `deps.py` | §3.2 适配器;**废弃** remote auth |
| **G4 入口矩阵分裂** | `/api/analyst/*` 用 `ANALYST_ROLES` 四角色 | `AGENT_ACCESS_MATRIX["analyst"]` 仅 analyst+compliance(**对话线**) | 问数路由 **不走** chat 矩阵;`assert_analyst_query_access()` 覆盖 **customer/advisor/analyst/risk_officer** |
| ~~**G5**~~ | — | — | **已关闭**:无 compliance/risk_manager Demo 角色,不单独开域 |
| **G6 留痕 actor** | `staff_id=subject_id` | customer 无 staff_id | `analytics_query_log` 写 `actor_id` + `actor_role`;customer 写 `customer_id` |
| **G7 前端 PermissionGate** | — | 问数页仍 Placeholder | 403 展示可查范围引导(对齐 `AnalystResponse.suggestions`) |
| **G8 客户解读约束** | 远程无 customer 域 | — | §9.6:趋势总结 · 禁建议 · 尾部「AI 分析有风险」 |
### 9.4 推荐实现落点(merge 时)
```text
app/utils/authz.py # 新增 assert_analyst_query_access(auth) → domain
app/service/sql_guard.py # 扩展 domain=self(§9.6 客户约束)
app/api/analyst.py # Depends(get_auth_context) → assert_analyst_query_access → adapter → agent.run
app/api/analyst_auth_adapter.py # actor_id ↔ 分析线内部视图(可选 NamedTuple)
```
**`/api/chat` analyst stub** 仍走现有 `AGENT_ACCESS_MATRIX`(analyst/compliance 轻聊,Demo 无 compliance 账号);与问数线 **刻意分离**。
---
## 10. 修订记录
| 日期 | 说明 |
| --- | --- |
| 2026-09-09 | 初稿:远程 `b19a241` 审阅 + merger 730 基线对照;**未 merge** |
| 2026-09-09 | Q1=A Q2=A;§9 鉴权缺口与角色矩阵(含 customer self 域待实现) |
| 2026-09-09 | 产品拍板:§9.6 客户趋势总结/禁建议/AI 风险尾注;§9.7 risk 表域够用;关闭 G5 |