Files
group_xinghuo_jinrong/README.md
T

192 lines
13 KiB
Markdown
Raw Normal View History

# JinRong · 金融四 Agent 智能管家
四个 Agent(客户财富 / 代理人助手 / 数据分析 / **风控监测**)共用统一数据层与合规底座;Agent 之间**不互调 LLM**,跨 Agent 协作走 L1/L2/L3 画像与预警表。
> **本分支(`risk-control-agent`)= 风控 Agent 模块的交付分支**。模块功能已全部交付(2026-09-07),合并 main 的事宜见《合并注意事项》一文,后续人员请按下表对号入座。
## 你是哪种后续人员?
| 角色 | 先读 |
| --- | --- |
| 接手模块开发 / 维护 | [交接文档.md](交接文档.md)(本地)→ [docs/memory/MEMORY.md](docs/memory/MEMORY.md) §0 → [docs/memory/TODO.md](docs/memory/TODO.md) |
| 负责把模块并入 main | [docs/项目框架设计/合并注意事项-风控模块并入main.md](docs/项目框架设计/合并注意事项-风控模块并入main.md)(自包含操作手册) |
| 了解需求 / 做方案评审 | [docs/PRD/PRD-风控监测Agent.md](docs/PRD/PRD-风控监测Agent.md)(v1.1)+ [附-风控规则表.md](docs/PRD/附-风控规则表.md) |
| 起环境跑演示 | [docs/项目框架设计/演示SOP-风控模块.md](docs/项目框架设计/演示SOP-风控模块.md) + 本页「快速启动」 |
## 模块交付状态(截至 2026-09-08)
**后端功能全部完成,全量 pytest 510 passed 0 failed(sqlite + 真 MySQL 集成),核心接口实调验收通过。**
| 里程碑 tag | 内容 |
| --- | --- |
| Wave 0 / T-04 / T-03 / T-21 | JWT 鉴权 · 审计中间件 · chat 闭环 · LangGraph+DeepSeek · Core 只读 Tool · 输入防护 · Milvus 知识库 |
| `risk-m1` | 适当性校验(已换核为 C×R 矩阵契约:match_result 五值 + JR-AST/FM 规则编号) |
| `risk-m2` | 事件线全链路(RISK-001~005 规则引擎 / 预警聚合 / L3 / AML / 交易网关 / 4 个 API) |
| `risk-m3` | 对话线闭环(风控四只读 Tool + StateGraph risk 分支;现为五只读 Tool,C5 追加 `query_overdue_alerts`) |
| 阶段一 | 对齐 main 基准 AL-01~08(SUIT-001~008 退役 → core_ro.check_suitability 矩阵判定) |
| `risk-m4` | 追加需求:FR-8 集中度预警 RISK-006 / FR-9 处置时效升级 RISK-007 / FR-10 代理人行为链 RISK-008 |
**前端接入面(2026-09-08 补齐,后端侧已齐)**:方案 B 会话管理三端点(`8328c24`)+ 方案 C SSE 流式对话(`01ec5fc`)。同步端点 `POST /api/chat` 契约不变,前端可任选同步/流式。详见下文「前端接入契约」。
**架构定位**:风控 Agent 为**独立封装模块**——模块内自带鉴权(`app/api/deps.py` + `app/service/auth_service.py`)、防护、trace、引擎工厂;与宿主的耦合只走 4 个接缝(挂载点 / AuthContext / settings / 引擎工厂),由 `tests/test_module_boundary.py` 防呆锁定。详见《[风控Agent模块边界与合并接缝标注.md](docs/项目框架设计/风控Agent模块边界与合并接缝标注.md)》。
## 更新说明(2026-09-09,已推送 origin/risk-control-agent,待合并 main)
> **给接手合并的人**:本分支在 2026-09-07 模块交付后,又做了一轮「架构改进与稳定性加固」(T-101~T-202),已全部推送到远程(HEAD = `fadc5e1`),pytest 实测 **510 passed 0 failed**(较 503 基线 +7)。所有改动均为**文档 / 告警 / 锁原语修正,接口契约与表结构零变更**,合并 main 无新增冲突面。重点如下。
### 最关键的稳定性改动:锁原语升级为 Redis 双层分布式锁(T-201)
- **改前**:`app/service/risk/locks.py` 用进程内 `threading.Lock`,**多实例部署会失效**(各实例各锁各的,无跨进程互斥)。
- **改后**:`redis_gateway.acquire_lock` / `release_lock`(Lua 脚本,仅删自己的锁,防误删)+ `run_locked` 三档降级:
1. 获取锁 → 持锁执行;
2. 获取超时(2s)→ 无锁执行并记日志;
3. Redis 不可用 / 任何异常 → 回退进程内锁兜底,**永不抛异常**。
- 调用点三处(key 统一前缀 `lock:`):`alert_service` 交易事件预警聚合 / 适当性阻断预警(R-02)聚合 / `profile_l3` 写入。
- 配套测试 `tests/test_locks_redis.py`(6 例)覆盖获取 / 超时 / 三档降级 / 仅删己锁。
### 其余改动一览
| 任务 | 内容 | 落点 |
| --- | --- | --- |
| T-101 | 输入防护注入词表补齐至 **45 条** | `app/service/input_guard.py` |
| T-102 | 风控只读 Tool 补 `query_overdue_alerts`,共 **5 个只读 Tool**(RISK_TOOL_REGISTRY) | `app/service/risk/chat_tools.py` |
| T-103 | agent 专用 SQL 注释表数 5→**6 张** | `docs/项目框架设计/表设计/02-mysql-agent专用.sql` |
| T-104 | 架构设计补 `scoring.py` 桩说明(一期 `NotImplementedError`、L3 risk_score 恒 NULL) | `docs/项目框架设计/架构设计-风控模块.md` §2 |
| T-105 | 架构设计补 `locks.py` 进程内锁说明(多实例失效,T-201 已升级双层) | `docs/项目框架设计/架构设计-风控模块.md` §5.2 |
| T-106 | 架构设计新增 §5.8 三项设计遗留(G1 X-Trace-Id 白名单 / G2 uuid4 概率唯一 / G3 admin·knowledge 空壳非 RAG 缺失) | `docs/项目框架设计/架构设计-风控模块.md` §5.8 |
| T-107 | 启动期缺 `DEEPSEEK_API_KEY` 显式告警(对话走降级前缀) | `app/main.py` lifespan |
| T-108 | 鉴权拒绝留痕补 4 字段(code/agent/actor/trace_id) | `app/utils/authz.py` |
| T-109 | 审计中间件降级日志补 status/path/request_id | `app/api/audit_middleware.py` |
| T-202 | 审计中间件运行于 trace 中间件**内层**的顺序守卫测试(交换顺序即红) | `tests/test_audit_middleware.py` |
**接手合并须知**:分支 `risk-control-agent` 已推送远程(HEAD `fadc5e1`),合并 main 时本批改动**无需**处理新冲突——仅文档与告警/锁原语,接口契约与表结构零变更。操作手册见《docs/项目框架设计/合并注意事项-风控模块并入main.md》与本页「模块交付状态」里程碑表。
## 铁律(改码前必记)
- Core 正式 C1~C5 不可被画像覆盖;Core 库只读(仅模拟交易网关可 INSERT core_trade)
- 审计表(audit_log 等)只 INSERT
- 代理人草稿不自动外发客户;风控不自动冻户
- 仅适当性 R-02 可阻断交易
- 四 Agent 不互调 LLM
- 不改表结构(改表需用户确认);新增依赖须用户确认
## 后端结构
```text
app/
├── api/ # 路由:chat、risk(4 API)、simulate、deps(JWT 鉴权工厂)、audit_middleware
├── service/ # agent_service(LangGraph StateGraph)、risk/*(rules/engine/alert/aml/L3/C4~C6)、
│ # suitability、tool_service、auth_service、input_guard、RAG 三层
├── tool/ # core_tools(Core 只读 Tool)、kb_tools(知识库检索 Tool)
├── gateway/ # trade_gateway 模拟交易网关(唯一可写 core_trade)
├── model/ # schemas、suitability(21 列契约)、entities(29 表 ORM 参考)
├── repository/ # core_ro(Core 只读,含 check_suitability 矩阵)、risk_repository、session_repository
├── config/ # settings(双库 + risk_* 阈值 + JWT)
└── utils/ # trace、authz、response(统一错误体)、db、exceptions
scripts/ # core 灌库、demo 演示数据、kb 知识库入库、cron 定时扫描、dev 签发 token
tests/ # 30+ 模块;_ddl.py 为 sqlite DDL 单一事实源;test_module_boundary.py 锁模块边界
data/kb/ # 6 只种子产品手册(知识库语料)
```
## 环境要求
- **Python 3.13**(项目依赖与 pytest 装在系统 Python 3.13.14)
- MySQL 8.0(双库:`jinrong_core` 模拟 Core 只读 / `jinrong_agent` 业务库)、Redis 8
- Neo4j Desktop(全图同步)、Ollama(bge-m3,1024 维,仅做 embedding)
- Windows 原生部署,不使用本地 Docker(见技术选型文档)
## 快速启动
```bash
cp .env.example .env # 填 MYSQL_PASSWORD / NEO4J_PASSWORD / DEEPSEEK_API_KEY
pip install -r requirements.txt
# 首次灌库与同步:见 docs/memory/FLOW.md §0(权威步骤)
uvicorn app.main:app --reload # → GET http://127.0.0.1:8000/health
python -m pytest # 基线 510 用例(系统 Python)
```
**演示库重灌(走查/集成测试前必做,两步缺一不可)**:
`scripts/core/reset.ps1`(交互式输密码)→ `scripts/demo/prepare_risk_demo.sql`。
⚠️ 只跑第一步不跑第二步 → 33 客户中 27 人风评过期、约 82% 适当性检查返回 risk_expired,这是种子设计不是 bug。
**调接口(JWT 通道)**:
```bash
# 签发 token(dev)
python scripts/dev/issue_dev_token.py --sub STAFF-90001 --roles risk_officer
# 调用须同时带两个头
curl -X POST http://127.0.0.1:8000/api/risk/suitability/check \
-H "Authorization: Bearer <token>" -H "X-Agent-Type: risk" \
-H "Content-Type: application/json" \
-d '{"customer_id":"CUST-1001","product_id":"PROD-005827"}'
```
dev 环境无 Bearer 时可用 `X-Debug-Role` / `X-Debug-Actor` 头兜底(仅 development + 未配 RS256 公钥时生效)。
## 核心接口一览
| 接口 | 说明 | 权限 |
| --- | --- | --- |
| `GET /api/risk/alerts` | 预警台账分页(FR-4) | risk_officer / risk_manager(只读)/ compliance(仅 aml) |
| `POST /api/risk/alerts/{id}/handle` | 人工处置(状态机 + 审计) | risk_officer |
| `POST /api/risk/suitability/check` | 适当性校验(C×R 矩阵,G-01 归属) | customer 本人 / advisor 名下 / risk_officer |
| `POST /api/risk/aml/scan` | 手动全量 AML 扫描(FR-5) | risk_officer |
| `POST /api/simulate/trade` | 模拟交易(适当性阻断 + 规则引擎) | risk_demo 或客户本人 |
| `POST /api/chat` | 对话(X-Agent-Type 分流四 Agent;风控线五只读 Tool) | 按准入矩阵 |
| `GET /api/chat/sessions` | 会话列表(本人 + 本 Agent 线,分页) | 按准入矩阵 |
| `GET /api/chat/sessions/{id}/messages` | 会话历史消息(seq_no 升序分页) | 仅本人会话 |
| `POST /api/chat/sessions/{id}/close` | 关闭会话(active→closed) | 仅本人会话 |
| `POST /api/chat/stream` | **SSE 流式对话**(OpenAI 兼容 chunk) | 按准入矩阵 |
> 对话线(含上述全部 `/api/chat*`)**仅 risk_officer**:`risk_manager` 放行 HTTP 台账只读(`GET /api/risk/alerts`),进对话线一律 403(PRD 4A.1 冻结口径)。
## 前端接入契约
**1)会话管理(方案 B)**
```bash
curl "http://127.0.0.1:8000/api/chat/sessions?limit=20&offset=0" \
-H "Authorization: Bearer <token>" -H "X-Agent-Type: risk"
# → {"items":[{session_id,agent_type,customer_id,title,status,created_at,...}],"total":2,"limit":20,"offset":0}
curl "http://127.0.0.1:8000/api/chat/sessions/<sid>/messages?limit=50&offset=0" ...
# → {"session_id":"...","items":[{seq_no,role,content,has_disclaimer,created_at}],"total":4,...}
```
**2)流式对话(方案 C,OpenAI 兼容 chunk)**
```
首帧 data: {"id":"<trace_id>","object":"chat.completion.chunk",
"choices":[{"delta":{"role":"assistant"},"finish_reason":null}],
"meta":{"session_id":"sess-…","trace_id":"…","has_disclaimer":true,
"disclaimer":"以上内容由 AI 生成,仅供业务参考,不构成投资建议。"}}
中间 data: {…,"choices":[{"delta":{"content":"文本块"}}]}
结束 data: {…,"choices":[{"delta":{},"finish_reason":"stop"}],"meta":{"has_disclaimer":true}}
data: [DONE]
异常 data: {"error":{"code":"STREAM_FAILED|PERSIST_FAILED","message":"…"}} → data: [DONE]
```
前端须知:
- **免责声明由首帧 `meta.disclaimer` 下发,前端需常驻渲染**(customer/risk 线合规要求);落库文本仍按原口径拼在尾部
- `401/403/404/409/429/400` 在流开始前返回**普通 JSON**,只有正常流才是 `text/event-stream`
- 断连/生成异常 = 整轮消息不落库(Tool 留痕仍可审计),前端需提示重试
- 心跳帧未做:长生成空隙靠反代 `proxy_read_timeout` 兜(已知遗留)
## 文档地图
| 目录 / 文件 | 内容 |
| --- | --- |
| [docs/memory/](docs/memory/) | 项目记忆六件套(MEMORY/TODO/REQUIREMENTS/FRAMEWORK/FLOW/ENVIRONMENT),入口为 MEMORY.md §0 |
| [docs/PRD/](docs/PRD/) | 风控 PRD v1.1(§4A = 追加需求)+ 风控规则表 |
| [docs/项目框架设计/](docs/项目框架设计/) | 架构设计 · 开发计划 · 实现方案 C4~C6 · 表设计(SQL 单一事实源)· 技术选型与 JWT 手册 · Core 模拟底座 · 演示 SOP · 边界标注 · **合并注意事项** |
| [docs/需求拆解/](docs/需求拆解/) | 业务场景优先级、数据交互矩阵、合规约束、四角色用户故事(docx) |
| [docs/业务记忆管理/](docs/业务记忆管理/) | 业务记忆分层手册 |
| [交接文档.md](交接文档.md) | 模块开发交接(本地保留,不入库) |
## 前端
React 19 + Vite 7 + TypeScript strict + Ant Design 5 + HashRouter——`web/` 目录尚未 init,为下一主线。
后端接入面已就绪,前端只需三件事:① `POST /api/chat/stream` 接 SSE(首帧拿 `session_id`/`disclaimer`,逐帧拼 `delta.content`,收到 `[DONE]` 收尾);② 进页面先 `GET /api/chat/sessions` 拉会话列表、选中后 `GET .../messages` 拉历史;③ 结束会话调 `POST .../close`。所有请求带 `Authorization: Bearer` + `X-Agent-Type`。