# 投顾 Agent 功能架构文档 > 文档版本:v1.0(2026-09-16) > 适用范围:投顾 Agent 的后端服务、前端模块、数据模型、接口与降级策略 > 配套文档:`docs/46-投顾Agent需求文档.md`(需求与验收) > 说明:本文描述的是**仓库当前已落地的实现**,不是前瞻设计 --- ## 1. 架构总览 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 客户端门户 /portal/customer/ │ │ advisor-plans(我的投顾方案 / 主动申报 / 申报进度) │ │ dashboard · holdings · orders · cash-ledger · risk-questionnaire│ └─────────────────────────────────────────────────────────────────┘ │ HTTPS + Bearer JWT ┌─────────────────────────────────────────────────────────────────┐ │ 投顾工作台 /portal/employee-advisor/dashboard/ │ │ dashboard.js(壳/引导/指标) │ │ ├ actions-module 七项操作 + 熔断闸门 + 结果渲染 │ │ ├ assistant-module 自然语言意图路由 │ │ ├ history-module 历史方案记录(只读 + 详情弹窗) │ │ ├ service-request-module 客户申报队列(受理/驳回) │ │ ├ published-module 已发布交付物 │ │ ├ customer-module 客户选择 │ │ └ advisor-engine / advisor-config 本地演示引擎与常量 │ └─────────────────────────────────────────────────────────────────┘ │ ┌─────────────────────────────────────────────────────────────────┐ │ FastAPI 入口层 app/api/controllers/ │ │ recommendations.py 推荐(生成/历史/已发布/审核/发布/删除)│ │ advisor_service_requests.py 客户申报 + 投顾受理 │ │ public_platform.py 公开产品与净值(P001/P002) │ │ onboarding.py / trading.py 风险测评 / 场内模拟交易 │ └─────────────────────────────────────────────────────────────────┘ │ ┌─────────────────────────────────────────────────────────────────┐ │ 服务层 app/service/ │ │ ProductRecommendationService 选品 + 排序 + 审核发布(核心) │ │ AdvisorServiceRequestService 客户申报与受理 │ │ AdvisorReasonService 推荐依据的大模型增强(可选) │ │ SuitabilityService 服务端权威风险画像 │ │ InvestmentGoalService 投资目标 │ │ MarketPriceSyncService 行情同步 │ │ AuthorizationService / IdentityService 权限与身份 │ └─────────────────────────────────────────────────────────────────┘ │ ┌─────────────────────────────────────────────────────────────────┐ │ 存储与依赖 │ │ MySQL(业务主库) · Redis(限流/缓存) · Milvus(知识向量) │ │ Neo4j(图谱,可降级) · 外部行情/净值源 · DeepSeek(可选) │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 2. 后端模块划分 | 层 | 文件 | 职责 | |---|---|---| | 控制器 | `app/api/controllers/recommendations.py` | 三个 router:`advisor`(投顾)、`admin`(平台管理员)、`client`(客户自助) | | 控制器 | `app/api/controllers/advisor_service_requests.py` | 客户申报(`/users/me`)+ 投顾受理(`/advisor`) | | 服务 | `product_recommendation_service.py` | 硬约束过滤、排序、审核、发布、历史;**唯一选品入口** | | 服务 | `advisor_service_request_service.py` | 申报创建/查询、队列、受理(联动生成草稿)、驳回 | | 服务 | `advisor_reason_service.py` | 推荐依据的大模型增强(合规守卫 + 失败回退) | | 仓储 | `advisor_product_repository.py` | 权威候选产品(场内可交易 + 适当性证据 + 合同快照) | | 模型 | `advisor_service_request.py` | 申报单 ORM 模型与状态常量 | | 模型 | `investment_goal.py` | `ClientFacingContent`(方案/方案书) | **设计约束(踩过坑换来的)** - 客户自助路由**不挂投顾灰度闸门**(`enforce_advisor_rollout`)—— 那是投顾业务灰度,客户查看自己的方案不该被它拦。 - 归属范围**只有一处实现**:`ProductRecommendationService._visible_customer_ids`(本人 + `sys_customer_assignment`),历史、已发布、申报队列全部复用它。 - 写操作统一走 `ApiTransactionService.execute`(16-128 位 ASCII 幂等键)。 --- ## 3. 数据模型 | 表 | 说明 | 关键字段 | |---|---|---| | `client_facing_content` | 方案与方案书 | `content_type`(`advisor_recommendation_plan` / `investment_goal_book`)、`customer_id`、`review_status`、`published_at`、`draft_content` | | `advisor_service_request` | 客户申报单 | `request_no`(唯一)、`customer_id`、`amount_wan`、`horizon`、`risk_preference`、`note`、`status`、`handled_by/at`、`advisor_note`、`result_content_id` | | `sys_customer_assignment` | 投顾-客户归属 | 决定可见范围 | | `fin_product` / `fin_market_price` / `fin_nav_history` | 产品 / 行情 / 历史净值 | 图表与指标数据源 | | `api_request_receipt` | 幂等 | `user_id` + `scope_hash` + `idempotency_key` + `request_hash` | | `interaction_audit` | 审计留痕 | 操作人、动作、明细、时间 | **关系要点** ``` advisor_service_request.result_content_id ──▶ client_facing_content.id client_facing_content.customer_id ──▶ sys_user.id(客户) sys_customer_assignment ──▶ 投顾可见客户集合 ``` > 「已发布 / 已发送」**不落库**:由 `published_at` 是否为空推导,避免两处状态不一致。 --- ## 4. 接口清单 ### 4.1 投顾侧(`/api/v1/advisor`,挂限流 + 灰度) | 方法 | 路径 | 权限 | 端点 ID | 幂等 | |---|---|---|---|---| | POST | `/advisor/recommendations` | `product-recommendation:generate:customer` | `ADVISOR_RECOMMEND` | ✅ | | GET | `/advisor/recommendations/history` | `product-recommendation:history` | `ADVISOR_HISTORY` | — | | GET | `/advisor/recommendations/published` | `product-recommendation:read:self` | `ADVISOR_PUBLISHED` | — | | DELETE | `/advisor/recommendations/{contentId}` | `product-recommendation:delete` | `ADVISOR_DELETE_RECOMMENDATION` | ✅ | | POST | `/advisor/recommendations/{contentId}/reviews` | `product-recommendation:review` | — | ✅ | | POST | `/advisor/recommendations/{contentId}/publications` | `product-recommendation:publish` | — | ✅ | | GET | `/advisor/service-requests` | `advisor-request:read` | `ADVISOR_SERVICE_REQUESTS` | — | | POST | `/advisor/service-requests/{requestNo}/reviews` | `advisor-request:review` | `ADVISOR_SERVICE_REQUEST_REVIEW` | ✅ | ### 4.2 客户侧(`/api/v1/users/me`,只挂限流) | 方法 | 路径 | 权限 | 端点 ID | |---|---|---|---| | GET | `/users/me/advisor-contents` | `product-recommendation:read:self` | `MY_ADVISOR_CONTENTS` | | GET | `/users/me/advisor-requests` | `advisor-request:read:self` | `MY_ADVISOR_REQUESTS` | | POST | `/users/me/advisor-requests` | `advisor-request:write:self` | `ADVISOR_REQUEST_CREATE` | ### 4.3 依赖的既有能力 | 端点 | 用途 | |---|---| | `ONB001` / `ONB002` | 风险测评取题与提交 | | `T001`–`T009` | 账户看板、委托、持仓、成交、资金明细 | | `P001` / `P002` | 产品列表(含最新行情)与历史净值序列(图表数据源) | --- ## 5. 关键时序 ### 5.1 生成推荐方案 ```mermaid sequenceDiagram participant A as 投顾前端 participant C as recommendations.py participant S as ProductRecommendationService participant R as AdvisorProductRepository participant G as 图谱 Neo4j participant L as DeepSeek 可选 participant DB as MySQL A->>C: POST /advisor/recommendations (Idempotency-Key) C->>S: generate(payload, context, key) S->>S: 权限 + 熔断 + 目标校验 S->>R: 权威候选产品 R-->>S: 候选(含硬约束证据) S->>G: 组合行业上下文 G-->>S: 正常 / 降级 degraded S->>S: 过滤 + 排序取 TopN S->>L: 真实参数 → 推荐依据 L-->>S: 文案 / 失败(回退规则文案) S->>DB: 落库 pending_review S-->>A: 返回结果(信封,含 content_id) ``` ### 5.2 审核与发布 ```mermaid sequenceDiagram participant A as 投顾/管理员 participant C as recommendations.py participant S as ProductRecommendationService participant DB as MySQL A->>C: POST .../reviews {decision: approved} C->>S: review(content_id, decision, context, key) S->>DB: review_status = approved(幂等) A->>C: POST .../publications C->>S: publish(content_id, context, key) S->>DB: review_status + published_at S-->>A: 发布成功 Note over DB: 此后客户接口才能读到该方案 ``` ### 5.3 客户申报受理 ```mermaid sequenceDiagram participant U as 客户 participant C1 as advisor_service_requests.py participant S as AdvisorServiceRequestService participant P as ProductRecommendationService participant A as 投顾 participant DB as MySQL U->>C1: POST /users/me/advisor-requests S->>S: 校验风评有效性(FM-03) S->>DB: 落库 pending A->>C1: GET /advisor/service-requests A->>C1: POST .../reviews {decision: accepted} S->>P: generate(customer_id) 自动生成草稿 P-->>S: content_id S->>DB: status=accepted + result_content_id S-->>A: 返回草稿编号(待审核) ``` --- ## 6. 外部依赖与降级矩阵 | 依赖 | 用途 | 不可用时 | 表现 | |---|---|---|---| | MySQL | 主存储 | 不可用即故障 | 无降级 | | Redis | 限流 | 自动降级为放行 | 不限流 | | Neo4j | 组合行业图谱 | 降级 | 方案标记 `graph_status=degraded`,业务继续 | | 行情源(腾讯) | 最新价 / 涨跌幅 | 无行情 | 相关指标显示「暂无行情」 | | 净值源(东财) | 历史净值 | 无数据 | 折线图显示「暂无净值数据」 | | DeepSeek | 推荐依据改写 | 回退 | 使用规则文案,`reason_source=rule` | > 行情与净值**没有周期性刷新**:演示前需跑 `tools/sync_market_prices.py` 与 `tools/sync_nav_history.py`(后者全量一次会被超时终止,建议按 `--codes` 分块)。 --- ## 7. 前端模块结构 **投顾工作台**(`employee-advisor/dashboard/`) | 模块 | 职责 | |---|---| | `dashboard.js` | 壳、引导、指标卡、在线探测与占位 | | `actions-module.js` | 七项操作的表单/请求/熔断/结果渲染 | | `assistant-module.js` | 自然语言意图路由(复用 `actions` 的 resolve) | | `history-module.js` | 历史方案记录(只读 + 详情弹窗) | | `service-request-module.js` | 客户申报队列(受理/驳回) | | `published-module.js` | 已发布交付物 | | `customer-module.js` | 客户选择 | | `advisor-engine.js` / `advisor-config.js` | 本地演示引擎与常量 | **客户端**(`customer/advisor-plans/` + `common/`) | 文件 | 职责 | |---|---| | `advisor-plans.js` | 已收到方案列表、申报表单、我的申报 | | `common/advisor-plan-view.js` | **投顾与客户共用**的方案可视化渲染(折线图/环形图/指标) | | `common/advisor-plan.css` | 上述组件的样式(含涨跌色变量) | **两条硬约定** 1. 同一页面内,同一模块的 `?v=` 版本号必须一致 —— 不同版本会被浏览器当成两个模块,导致双实例。 2. 共享模块**不 import `api-client.js`**,apiClient 由调用方传入(依赖注入),否则两端版本不同会多出实例。 --- ## 8. 权限与灰度 | 项 | 说明 | |---|---| | 权限码定义源 | `tools/seed_test_rbac.py`(投顾 9071-9074 为客户申报相关) | | 投顾授权工具 | `tools/grant_advisor_role.py`(权限码必须与种子一致,由 `check_rbac_seed_consistency.py` 校验) | | 客户授权 | 由种子 `CUSTOMER_PERMISSIONS` 决定;既有环境需按同名补建并授权 | | 灰度 | `ADVISOR_ROLLOUT_ENABLED` + `ADVISOR_ROLLOUT_CUSTOMER_IDS`;管理员不受限 | | 幂等 | 所有写接口要求 16-128 位 ASCII `Idempotency-Key` | --- ## 9. 配置清单 | 配置 | 说明 | |---|---| | `ADVISOR_ROLLOUT_ENABLED` / `ADVISOR_ROLLOUT_CUSTOMER_IDS` | 投顾灰度 | | `ADVISOR_REASON_LLM_ENABLED` | 推荐依据是否启用大模型 | | `ADVISOR_REASON_LLM_BASE_URL` / `_MODEL` / `_TIMEOUT_SECONDS` | 模型地址、模型名、超时 | | `DEEPSEEK_API_KEY` | 模型密钥(`.env` 已被 `.gitignore` 忽略,不得入库) | | `NEO4J_URI` / `NEO4J_PASSWORD` | 图谱,留空即降级 | --- ## 10. 已知约束与演进方向 | 约束 | 说明 | 演进建议 | |---|---|---| | 候选池可能为空 | 本环境适当性证据与合同快照暂无数据,fail-closed 导致候选为 0 | 接入治理侧证据导入(`tools/import_product_governance_reference.py`) | | 本地演示引擎 | 画像评分/调仓建议为前端规则模拟 | 后端补接口后切换数据源 | | 行情/净值无自动刷新 | 依赖手工同步脚本 | 增加定时同步或接入实时源 | | 涨跌色口径 | 新图表为**涨红跌绿**;产品列表/收益明细为绿涨红跌 | 统一口径后改 `advisor-plan.css` 两个变量即可 | | alembic 版本指针 | 本地库指针与实际结构对不上,新表依赖手工应用 DDL | 修复版本指针后回归 `alembic upgrade head` |