- Added a new visitor chat API endpoint (`/api/chat/visitor`) to allow unauthenticated users to engage in conversations without requiring customer data. - Introduced a visitor context dependency to manage visitor interactions seamlessly. - Enhanced the chat API to support explicit session termination and improved response handling for customer service interactions. - Updated the database configuration to include Redis client support for caching visitor data. - Added a new customer note repository to persist user notes independently of the L1 profile slots. This update significantly improves the customer service experience by enabling visitor interactions and ensuring efficient data handling for both registered and unregistered users.
252 lines
17 KiB
Markdown
252 lines
17 KiB
Markdown
# 02-mysql-agent 专用表设计总结
|
||
|
||
> 源文件:`docs/项目框架设计/表设计/02-mysql-agent专用.sql`
|
||
> 数据库:`jinrong_agent`(Agent 专用库,与核心业务库 `jinrong_core` 分离)
|
||
> 配套文件:`01-mysql-共用底座.sql`(会话/消息/RBAC 等共用底座)
|
||
|
||
## 一、文件定位
|
||
|
||
- **用途**:各 Agent 组开发自有功能时建表,也可一次性全建。
|
||
- **引擎**:全部 InnoDB。
|
||
- **表数量**:文件实际包含 **7 张表**。
|
||
> 注意:文件头注释写「单 Agent 专用 MySQL(5 张)」,实际为 7 张——`conversation_archive`、`customer_notes` 为客服 Agent 后期(CS-C-11 / Wave 5)追加,头部计数未同步更新。
|
||
|
||
## 二、表清单(按 Agent 分组)
|
||
|
||
| # | 表名 | 归属 Agent | 作用 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | `customer_threshold_config` | 客户财富 Agent | 客户亏损阈值配置 |
|
||
| 2 | `customer_notify_log` | 客户财富 Agent | 提醒发送留痕 |
|
||
| 3 | `advisor_draft` | 代理人助手 Agent | 话术/跟进草稿(含人工审核) |
|
||
| 4 | `compliance_hit_log` | 代理人助手 Agent | 违规话术命中日志 |
|
||
| 5 | `analytics_query_log` | 数据分析 Agent | NL2SQL 查数留痕 |
|
||
| 6 | `conversation_archive` | 客服 Agent(跨 Agent 通用) | 会话归档摘要 |
|
||
| 7 | `customer_notes` | 客服 Agent | 客户显式备注(独立于 L1 画像) |
|
||
|
||
---
|
||
|
||
## 三、客户财富 Agent(2 张)
|
||
|
||
### 3.1 `customer_threshold_config` — 亏损阈值配置
|
||
|
||
客户自定义亏损提醒线。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | BIGINT UNSIGNED PK | 自增主键 |
|
||
| `customer_id` | VARCHAR(64) NOT NULL | 客户 ID |
|
||
| `scope_type` | ENUM('portfolio','product') | 作用范围:组合 / 单产品,默认 portfolio |
|
||
| `scope_ref` | VARCHAR(64) NULL | 产品引用(scope_type=product 时使用) |
|
||
| `loss_threshold_pct` | DECIMAL(5,2) NOT NULL | 亏损阈值百分比 |
|
||
| `notify_channel` | SET('app','sms','email') | 通知渠道,支持多选,默认 app |
|
||
| `is_enabled` | TINYINT(1) | 启用开关,默认 1 |
|
||
| `created_at` / `updated_at` | DATETIME(3) | 创建/更新时间(毫秒精度,updated_at 自动刷新) |
|
||
|
||
- 索引:`idx_customer(customer_id, is_enabled)`
|
||
|
||
### 3.2 `customer_notify_log` — 提醒留痕
|
||
|
||
每次阈值/波动提醒的发送审计记录。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | BIGINT UNSIGNED PK | 自增主键 |
|
||
| `customer_id` | VARCHAR(64) NOT NULL | 客户 ID |
|
||
| `trace_id` | VARCHAR(64) NOT NULL | 全链路追踪 ID |
|
||
| `notify_type` | ENUM('loss_threshold','market_volatility') | 提醒类型:亏损阈值 / 市场波动 |
|
||
| `threshold_config_id` | BIGINT UNSIGNED NULL | 关联阈值配置(可空) |
|
||
| `payload` | JSON NOT NULL | 提醒详情 |
|
||
| `channel` | VARCHAR(16) NOT NULL | 实际发送渠道 |
|
||
| `send_status` | ENUM('sent','failed') | 发送状态 |
|
||
| `created_at` | DATETIME(3) | 创建时间 |
|
||
|
||
- 索引:`idx_customer_time(customer_id, created_at)`
|
||
|
||
---
|
||
|
||
## 四、代理人助手 Agent(2 张)
|
||
|
||
### 4.1 `advisor_draft` — 话术/跟进草稿
|
||
|
||
AI 为代理人生成的话术或跟进记录,走人工审核闭环。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | BIGINT UNSIGNED PK | 自增主键 |
|
||
| `draft_id` | VARCHAR(64) NOT NULL | 草稿业务 ID(**唯一键** `uk_draft_id`) |
|
||
| `session_id` | VARCHAR(64) NOT NULL | 会话 ID |
|
||
| `trace_id` | VARCHAR(64) NOT NULL | 全链路追踪 ID |
|
||
| `advisor_id` | VARCHAR(64) NOT NULL | 代理人 ID |
|
||
| `customer_id` | VARCHAR(64) NOT NULL | 客户 ID |
|
||
| `draft_type` | ENUM('script','follow_up') | 草稿类型:话术 / 跟进 |
|
||
| `content` | MEDIUMTEXT NOT NULL | 草稿正文 |
|
||
| `review_status` | ENUM('pending','approved','rejected') | 审核状态,默认 pending |
|
||
| `reviewer_id` | VARCHAR(64) NULL | 审核人 ID |
|
||
| `reviewed_at` | DATETIME(3) NULL | 审核时间 |
|
||
| `created_at` | DATETIME(3) | 创建时间 |
|
||
|
||
- 索引:`uk_draft_id(draft_id)`、`idx_advisor_customer(advisor_id, customer_id, created_at)`、`idx_review(review_status, created_at)`
|
||
|
||
### 4.2 `compliance_hit_log` — 违规话术命中日志
|
||
|
||
合规护栏命中留痕(客户/代理人两端通用)。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | BIGINT UNSIGNED PK | 自增主键 |
|
||
| `session_id` | VARCHAR(64) NOT NULL | 会话 ID |
|
||
| `trace_id` | VARCHAR(64) NOT NULL | 全链路追踪 ID |
|
||
| `agent_type` | ENUM('customer','advisor') | 触发端:客户侧 / 代理人侧 |
|
||
| `actor_id` | VARCHAR(64) NOT NULL | 操作者 ID |
|
||
| `hit_category` | ENUM('return_promise','principal_guarantee','buy_sell_guide','product_recommend','other') | 命中类别:收益承诺 / 保本承诺 / 买卖指导 / 产品推荐 / 其他 |
|
||
| `matched_terms` | JSON NOT NULL | 命中词明细 |
|
||
| `severity` | ENUM('low','medium','high') | 严重程度 |
|
||
| `action_taken` | ENUM('flagged','blocked','alerted') | 处置动作:标记 / 拦截 / 告警 |
|
||
| `created_at` | DATETIME(3) | 创建时间 |
|
||
|
||
- 索引:`idx_severity_time(severity, created_at)`、`idx_session(session_id)`
|
||
|
||
---
|
||
|
||
## 五、数据分析 Agent(1 张)
|
||
|
||
### 5.1 `analytics_query_log` — 查数 SQL 留痕
|
||
|
||
自然语言查数(NL2SQL)的完整审计链。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | BIGINT UNSIGNED PK | 自增主键 |
|
||
| `session_id` | VARCHAR(64) NOT NULL | 会话 ID |
|
||
| `trace_id` | VARCHAR(64) NOT NULL | 全链路追踪 ID |
|
||
| `staff_id` | VARCHAR(64) NOT NULL | 员工 ID |
|
||
| `nl_question` | TEXT NOT NULL | 自然语言问题原文 |
|
||
| `generated_sql` | TEXT NOT NULL | 生成的 SQL |
|
||
| `sql_hash` | CHAR(64) NOT NULL | SQL 哈希(去重/统计用) |
|
||
| `row_count` | INT UNSIGNED NULL | 返回行数 |
|
||
| `exec_status` | ENUM('success','error','blocked') | 执行状态:成功 / 报错 / 被拦截 |
|
||
| `exec_latency_ms` | INT UNSIGNED NULL | 执行耗时(毫秒) |
|
||
| `result_summary` | JSON NULL | 结果摘要 |
|
||
| `has_disclaimer` | TINYINT(1) | 是否附带免责声明,默认 0 |
|
||
| `error_message` | VARCHAR(512) NULL | 报错信息 |
|
||
| `created_at` | DATETIME(3) | 创建时间 |
|
||
|
||
- 索引:`idx_staff_time(staff_id, created_at)`、`idx_trace(trace_id)`、`idx_sql_hash(sql_hash)`
|
||
|
||
---
|
||
|
||
## 六、客服 Agent(2 张)
|
||
|
||
### 6.1 `conversation_archive` — 会话归档摘要(CS-C-11,跨 Agent 通用)
|
||
|
||
会话结束时生成摘要写入;设计上 customer/advisor/analyst/risk 四类 Agent 通用。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | BIGINT UNSIGNED PK | 自增主键 |
|
||
| `session_id` | VARCHAR(64) NOT NULL | 关联 `agent_session.session_id`,**一会话一归档**(唯一键 `uk_session_id`) |
|
||
| `trace_id` | VARCHAR(64) NOT NULL | 全链路追踪 ID |
|
||
| `agent_type` | ENUM('customer','advisor','analyst','risk') | Agent 类型 |
|
||
| `actor_id` | VARCHAR(64) NOT NULL | 操作者:客户=customer_id / 游客=VISITOR |
|
||
| `summary` | TEXT NOT NULL | LLM 生成的会话摘要 |
|
||
| `msg_count` | INT UNSIGNED | 消息条数(count agent_message),默认 0 |
|
||
| `archive_reason` | ENUM('timeout','explicit','manual') | 归档原因:超时 / 显式结束 / 手动 |
|
||
| `created_at` | DATETIME(3) | 创建时间 |
|
||
|
||
- 索引:`uk_session_id(session_id)`、`idx_trace(trace_id)`、`idx_actor(agent_type, actor_id, created_at)`
|
||
- **刻意不冗余**:
|
||
- 消息明细不存,见共用底座 `agent_message`;
|
||
- 会话起止时间不存:起 = `agent_session.created_at`,止 = `agent_session.closed_at`(归档动作同时将 `agent_session.status` 置为 closed)。
|
||
|
||
### 6.2 `customer_notes` — 客户显式备注(独立于 L1 画像)
|
||
|
||
用户主动要求记忆的自由文本(如「你要记住我每天看净值」),不进 L1 画像槽位表。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| --- | --- | --- |
|
||
| `id` | BIGINT UNSIGNED PK | 自增主键 |
|
||
| `customer_id` | VARCHAR(64) NOT NULL | 客户 ID(关联 `customer_profile_l1.customer_id`) |
|
||
| `session_id` | VARCHAR(64) NOT NULL | 备注生成的会话 ID |
|
||
| `trace_id` | VARCHAR(64) NOT NULL | 全链路追踪 ID |
|
||
| `content` | VARCHAR(500) NOT NULL | LLM 抽取后的纯净备注内容 |
|
||
| `category` | VARCHAR(32) NULL | 可选分类:habit / preference / reminder / other |
|
||
| `source_text` | VARCHAR(800) NULL | 用户原话片段(审计用) |
|
||
| `is_active` | TINYINT(1) | 1=有效,0=用户已删除(**软删除**,保留审计痕) |
|
||
| `created_at` | DATETIME(3) | 创建时间 |
|
||
|
||
- 索引:`idx_cid_active_time(customer_id, is_active, created_at)`、`idx_trace(trace_id)`
|
||
- **写入与使用链路**:
|
||
1. `keyword_route` 命中「记住 / 记一下 / 帮我记 / 别忘了 / 记着」→ 路由到 `save_note` 意图;
|
||
2. 调 LLM 从用户原话抽取纯净 `content`(≤500 字,超长裁剪)+ 可选 `category`(非枚举值写 NULL);
|
||
3. 写入本表,`source_text` 保留原话片段;
|
||
4. 后续对话每轮读取该客户最近 N 条 active 备注,注入 chitchat/interpret 的 prompt;
|
||
5. 用户要求「忘掉备注」时执行软删除(`is_active=0`);LLM 抽取失败/异常则不写库并回落提示话术。
|
||
|
||
---
|
||
|
||
## 七、共性设计约定
|
||
|
||
1. **主键**:统一 `id BIGINT UNSIGNED AUTO_INCREMENT`。
|
||
2. **时间字段**:`DATETIME(3)` 毫秒精度;`created_at` 默认 `CURRENT_TIMESTAMP(3)`;配置类表(如 `customer_threshold_config`)带 `updated_at ... ON UPDATE CURRENT_TIMESTAMP(3)` 自动刷新。
|
||
3. **全链路追踪**:业务流程表普遍携带 `trace_id` 并建索引。
|
||
4. **取值约束**:状态/分类优先用 `ENUM`;多选用 `SET`(如通知渠道);半结构化内容用 `JSON`(payload、matched_terms、result_summary)。
|
||
5. **索引设计**:围绕最高频查询场景建联合索引,典型模式为「主体 ID + 时间」(如 `customer_id, created_at`)及「状态 + 时间」(如 `review_status, created_at`、`severity, created_at`)。
|
||
6. **审计思路**:所有 AI 动作均有留痕——提醒发送、草稿审核、违规命中、SQL 查数、会话归档、客户备注,支持事后核查。
|
||
7. **软删除**:`customer_notes` 通过 `is_active` 软删除保留审计痕迹;归档表通过唯一键保证一会话一归档。
|
||
|
||
---
|
||
|
||
## 八、各表与代码层对应关系
|
||
|
||
> 结论基于对 `app/`、`tests/` 全量检索表名的实际结果(截至 2026-09-09)。
|
||
> 7 张表中 **2 张已有完整代码落地(客服 Agent)**,1 张仅在注释中被引用,4 张尚无代码(属未来波次 Agent)。
|
||
|
||
### 8.1 落地状态总览
|
||
|
||
| 表名 | Repository | Service | 编排/入口 | 状态 |
|
||
| --- | --- | --- | --- | --- |
|
||
| `customer_notes` | `app/repository/note_repository.py` → `CustomerNoteRepository` | `app/service/note_service.py` | `customer_service.py` LangGraph `save_note` 节点 | ✅ 已落地(Wave 5) |
|
||
| `conversation_archive` | `app/repository/profile_repository.py` → `ProfileRepository`(归档段) | `app/service/profile_service.py`(`archive_session` 等) | `customer_service.py` archive_check / end_session | ✅ 已落地(Wave 3) |
|
||
| `customer_threshold_config` | — | — | 仅 `app/config/profile_slots.py` 槽位注释提及 | ⏳ 未实现(客户财富 Agent) |
|
||
| `customer_notify_log` | — | — | — | ⏳ 未实现(客户财富 Agent) |
|
||
| `advisor_draft` | — | — | — | ⏳ 未实现(代理人助手 Agent) |
|
||
| `compliance_hit_log` | — | — | 现有合规护栏 `app/utils/compliance_guard.py` 仅内存正则,未落库 | ⏳ 未实现(代理人助手 Agent) |
|
||
| `analytics_query_log` | — | — | — | ⏳ 未实现(数据分析 Agent) |
|
||
|
||
### 8.2 `customer_notes` 代码链路
|
||
|
||
| 层 | 文件 | 关键函数/位置 | 对应表操作 |
|
||
| --- | --- | --- | --- |
|
||
| Repository | [note_repository.py](file:///d:/金融系统/app/repository/note_repository.py) | `CustomerNoteRepository.insert_note()`([L26](file:///d:/金融系统/app/repository/note_repository.py#L26)) | `INSERT INTO customer_notes` |
|
||
| Repository | 同上 | `list_active_notes()`([L59](file:///d:/金融系统/app/repository/note_repository.py#L59)) | `SELECT ... WHERE is_active=1 ORDER BY created_at DESC LIMIT N` |
|
||
| Repository | 同上 | `deactivate()` / `deactivate_all()`([L76](file:///d:/金融系统/app/repository/note_repository.py#L76)、[L89](file:///d:/金融系统/app/repository/note_repository.py#L89)) | 软删除 `UPDATE ... SET is_active=0` |
|
||
| Service | [note_service.py](file:///d:/金融系统/app/service/note_service.py) | `save_note_from_message()`([L40](file:///d:/金融系统/app/service/note_service.py#L40)) | 调 DeepSeek 抽取纯净 content+category → `insert_note`;抽取失败不写库 |
|
||
| Service | 同上 | `render_notes_context()`([L90](file:///d:/金融系统/app/service/note_service.py#L90)) | 读最近 `settings.note_max_inject` 条 active 备注,格式化为 prompt 注入文本 |
|
||
| Service | 同上 | `forget_all_notes()`([L114](file:///d:/金融系统/app/service/note_service.py#L114)) | 包装 `deactivate_all`,异常吞掉返回 0 |
|
||
| 编排 | [customer_service.py](file:///d:/金融系统/app/service/customer_service.py) | LangGraph `save_note` 节点([L375](file:///d:/金融系统/app/service/customer_service.py#L375),注册 [L461](file:///d:/金融系统/app/service/customer_service.py#L461),路由 [L481](file:///d:/金融系统/app/service/customer_service.py#L481)) | save_note 意图分支入口 |
|
||
| 编排 | 同上 | 每轮 `render_notes_context(cid)`([L144](file:///d:/金融系统/app/service/customer_service.py#L144)) | 备注注入 chitchat/interpret prompt |
|
||
| 路由/Prompt | [customer_prompts.py](file:///d:/金融系统/app/service/customer_prompts.py) | `keyword_route` 命中「记住/记一下/帮我记/别忘了/记着」→ `save_note`([L153](file:///d:/金融系统/app/service/customer_prompts.py#L153));备注抽取 prompt([L277](file:///d:/金融系统/app/service/customer_prompts.py#L277)) | 关键词前置路由 + LLM 抽取模板 |
|
||
| 测试 | [test_wave5_notes.py](file:///d:/金融系统/tests/test_wave5_notes.py) | Wave 5 共 13 例 | 写入/注入/软删除/抽取失败回落 |
|
||
|
||
### 8.3 `conversation_archive` 代码链路
|
||
|
||
| 层 | 文件 | 关键函数/位置 | 对应表操作 |
|
||
| --- | --- | --- | --- |
|
||
| Repository | [profile_repository.py](file:///d:/金融系统/app/repository/profile_repository.py) | `insert_archive()`([L146](file:///d:/金融系统/app/repository/profile_repository.py#L146)) | `INSERT INTO conversation_archive`(`agent_type` 当前硬编码 `'customer'`) |
|
||
| Repository | 同上 | `close_session()`([L133](file:///d:/金融系统/app/repository/profile_repository.py#L133)) | 先将 `agent_session` 置 `closed`;仅 active 可关,防重复归档、保 `uk_session_id` 不冲突 |
|
||
| Repository | 同上 | `count_session_messages()`([L123](file:///d:/金融系统/app/repository/profile_repository.py#L123)) | `msg_count` 来源(COUNT `agent_message`) |
|
||
| Repository | 同上 | `list_idle_customer_sessions()`([L171](file:///d:/金融系统/app/repository/profile_repository.py#L171)) | 懒扫描空闲超时的 active 客户会话 |
|
||
| Service | [profile_service.py](file:///d:/金融系统/app/service/profile_service.py) | `archive_session()`([L430](file:///d:/金融系统/app/service/profile_service.py#L430)) | 归档主流程:关会话 → 等 1s 避异步落库竞态 → 计数+取消息窗 → LLM 生成摘要(失败回落「会话已结束,共 N 条消息」)→ `insert_archive` → 后台触发画像全量抽槽 `extract_profile` + `refresh_behavior_tags` |
|
||
| Service | 同上 | `archive_idle_sessions()`([L477](file:///d:/金融系统/app/service/profile_service.py#L477)) | 懒扫描批量归档,`archive_reason='timeout'`,每轮上限 `settings.archive_idle_scan_limit` |
|
||
| 编排 | [customer_service.py](file:///d:/金融系统/app/service/customer_service.py) | archive_check 节点(每轮 `_spawn` 后台调 `archive_idle_sessions`) | 超时归档,失败不影响响应 |
|
||
| 编排 | 同上 | 显式结束 `end_session` → `_spawn(archive_session, ..., "explicit")`([L436](file:///d:/金融系统/app/service/customer_service.py#L436)) | 用户主动结束会话归档 |
|
||
|
||
> 归档动作与共用底座表联动:消息明细读 `agent_message`,会话状态写 `agent_session`(均在 `01-mysql-共用底座.sql`),本表只存摘要。
|
||
|
||
### 8.4 未落地表的备注
|
||
|
||
- **`customer_threshold_config`**:目前唯一代码痕迹是 [profile_slots.py L152](file:///d:/金融系统/app/config/profile_slots.py#L152) 的画像槽位说明——`lifecycle.threshold_pref_summary` 槽位注释「客户主动提出的提醒阈值摘要(明细归 customer_threshold_config)」。即画像只存阈值偏好摘要,明细表待客户财富 Agent 波次实现。
|
||
- **`compliance_hit_log`**:现有合规护栏 [compliance_guard.py](file:///d:/金融系统/app/utils/compliance_guard.py) 提供 `scan_reply()` / `sanitize_reply()` / `should_add_disclaimer()`,目前为内存级正则扫描(命中即替换回复),**尚未落库**;表结构预留给代理人助手/双端合规留痕。
|
||
- **`analytics_query_log`**:属数据分析 Agent 的 NL2SQL 留痕。客服侧数据查询(持仓/流水/风评/适当性)按硬约束走 `app/tool/core_ro_tool.py` 预定义函数工具、**禁止 NL2SQL**,因此客服链路不会写此表。
|
||
- **`customer_notify_log` / `advisor_draft`**:分别属客户财富 Agent(提醒发送留痕)与代理人助手 Agent(草稿+审核闭环),当前无代码。
|