Files
SuperBizAgent-java/mvp/plan/session-storage-design.md
T
zhuyongxin 9b52afce07 docs: 合并 .docs/mvp 到根 mvp 目录并更新文档
- 删除 .docs/mvp 目录,内容合并到根目录 mvp/
- 更新 session-storage-design.md 实现变更记录
- 修复引用路径
2026-06-26 17:29:27 +08:00

297 lines
12 KiB
Markdown
Raw 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.
# 会话存储方案设计
**日期**: 2026-06-26
**类型**: 架构设计
**状态**: 已实现 (2026-06-26)
---
## 一、背景与目标
### 1.1 现状问题
当前仅有一张 `diagnosis_record` 表,存在以下问题:
| 问题 | 说明 |
|------|------|
| **语义耦合** | `fault_category`、`error_code`、`root_cause`、`solution` 等字段耦合在"告警分析"领域语义,ChatService 通用问答场景用不上 |
| **Agent 维度缺失** | 只有一个 `tool_calls` JSON 字段,存不下两个 Agent 的多轮决策链 |
| **检索质量不可追溯** | 没有记录 L0/L1 命中层、截断信息、召回内容长度 |
| **指标不完整** | 有 `duration` 和 `confidence`,但缺 token 用量、自评信号、采纳率 |
### 1.2 存储范围
需要覆盖四个层面的数据:
```
诊断级元数据
├── 单次诊断的唯一 ID、查询问题、状态
├── 会话级决策链
│ ├── agent_step:每个 Agent 的每一步(输入、输出、延迟、Token)
│ └── tool_invocation:每次工具调用(参数、结果、耗时)
├── 检索质量明细
│ └── 每次 lookup_knowledge 的命中层(L0/L1)、内容长度、是否截断
└── 自评估信号
└── LLM 对结论的置信度自评
```
### 1.3 设计目标
- **可观测**:Debug 时能回溯完整决策链
- **可评估**:能统计 L0/L1 命中率、平均 Token 消耗、工具采纳率等指标
- **可演进**:覆盖当前两个 Agent(ChatService / AiOpsService),未来新增 Agent 也能接入
---
## 二、存储选型分析
### 2.1 方案对比
| 维度 | SQL + JSON 列 | NoSQL 文档库 |
|------|:------------:|:-----------:|
| 基础设施 | 已有的 MySQL,零新增 | 需新部署 MongoDB 等 |
| 层级查询 | `WHERE session_id=? AND agent_name=?` 高效 | 需二级索引 |
| 指标聚合 | `AVG(token_count) GROUP BY agent_name` 原生支持 | 聚合管道,学习成本 |
| 非结构化内容 | JSON 列(MySQL 8+ 支持良好) | 天然支持 |
| MVP 迭代速度 | JPA Entity + Flyway 快速迭代 | 新 ORM 学习成本 |
### 2.2 结论
**采用 MySQL + JSON 列**。结构化字段做查询和聚合,JSON 列存非结构化载荷。MVP 阶段数据量可控,等后续 > 百万级或需要更灵活 schema 时再评估 NoSQL。
---
## 三、存储模型
### 3.1 整体关系
```
diagnosis_session (1)
│
└── agent_step (0:N) —— 单次诊断的每一步 Agent 决策
│
└── tool_invocation (0:N) —— 每步中的工具调用
```
### 3.2 表设计
#### 表 1:diagnosis_session(诊断会话)
```sql
CREATE TABLE diagnosis_session (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) UNIQUE NOT NULL COMMENT '会话唯一 ID',
-- 请求
query TEXT NOT NULL COMMENT '用户原始问题',
status VARCHAR(16) DEFAULT 'PENDING' COMMENT 'PENDING / RUNNING / SUCCESS / FAILED',
agent_flow VARCHAR(32) COMMENT 'CHAT / AI_OPS',
-- 汇总指标
total_duration_ms INT COMMENT '总耗时(毫秒)',
total_token_count INT COMMENT '总 Token 消耗',
step_count INT COMMENT 'Agent 步数',
tool_call_count INT COMMENT '工具调用次数',
-- 自评估信号(模型对结论的置信度自评)
self_evaluation JSON COMMENT '{"confidence": 0-100, "reasoning": "...", "evidence_count": 3}',
-- 用户反馈
feedback VARCHAR(16) COMMENT 'useful / not_useful / null',
-- 元数据
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_created_at (created_at),
INDEX idx_status (status),
INDEX idx_agent_flow (agent_flow)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='诊断会话表';
```
#### 表 2:agent_step(Agent 决策步骤)
```sql
CREATE TABLE agent_step (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) NOT NULL COMMENT '关联 diagnosis_session',
step_index INT NOT NULL COMMENT '当前 Agent 的第几步(从0开始)',
agent_name VARCHAR(32) NOT NULL COMMENT 'intelligent_assistant / planner / executor / supervisor',
-- 模型调用(输入输出摘要,非完整消息体)
model_input JSON COMMENT '模型输入摘要 [{role, content_truncated}, ...]',
model_output JSON COMMENT '模型输出摘要 {text, tool_calls, ...}',
thought TEXT COMMENT 'Agent 思考过程文本',
has_tool_call BOOLEAN DEFAULT FALSE COMMENT '本轮是否调用了工具',
-- 性能指标
duration_ms INT COMMENT '本轮耗时',
token_count INT COMMENT '本轮 Token 消耗',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_session_step (session_id, step_index),
INDEX idx_agent_name (agent_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Agent 决策步骤表';
```
#### 表 3:tool_invocation(工具调用明细)
```sql
CREATE TABLE tool_invocation (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id VARCHAR(64) NOT NULL COMMENT '关联 diagnosis_session',
step_id BIGINT COMMENT '关联 agent_step.id(可为空,不强制外键)',
tool_name VARCHAR(64) NOT NULL COMMENT 'lookup_knowledge / queryPrometheusAlerts / 等',
-- 调用信息
input_params JSON NOT NULL COMMENT '工具入参',
output_preview TEXT COMMENT '输出前500字符(可观测用,不存完整输出)',
output_length INT COMMENT '输出总字符数',
-- 检索质量(仅 lookup_knowledge 时有意义)
retrieval_layer VARCHAR(8) COMMENT 'L0 / L1 / L0+L1',
l0_match_count INT COMMENT 'L0 匹配数',
l1_match_count INT COMMENT 'L1 匹配数',
is_truncated BOOLEAN DEFAULT FALSE COMMENT '返回内容是否被截断',
retrieval_details JSON COMMENT '{"l0_titles":[], "l1_scores":[], "has_supplement": true}',
-- 性能 & 状态
duration_ms INT COMMENT '工具执行耗时',
success BOOLEAN DEFAULT TRUE COMMENT '是否成功',
error_message TEXT COMMENT '失败原因',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_session_id (session_id),
INDEX idx_tool_name (tool_name),
INDEX idx_retrieval_layer (retrieval_layer)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工具调用明细表';
```
---
## 四、数据流设计
### 4.1 完整链路
```
用户请求
│
▼
1. 创建 diagnosis_session(status=RUNNING)
│
▼
2. Agent Loop(可能多轮)
│
├── beforeModel()
│ └── AgentLoggingHook 记录 model_input + 开始时间 → 写入 agent_step(先创建,duration 待填)
│
├── afterModel()
│ └── AgentLoggingHook 记录 model_output + token_count + 工具调用决策 → 更新 agent_step
│
├── 工具执行(如 lookup_knowledge)
│ └── LookupKnowledgeTool 记录 tool_invocation(L0/L1 明细、耗时、是否截断)
│
└── 循环直到模型不再调用工具
│
▼
3. 诊断完成 → 更新 diagnosis_session
├── status = SUCCESS / FAILED
├── 汇总指标:total_duration_ms / total_token_count / step_count / tool_call_count
└── self_evaluation(可选,由 LLM 自评)
```
### 4.2 变更点
| 模块 | 当前行为 | 改造后 |
|------|---------|--------|
| `AgentLoggingHook` | 只打日志到 stdout | 同时写入 `agent_step` 表 |
| `LookupKnowledgeTool` | 只打日志到 stdout | 同时写入 `tool_invocation` 表 |
| `ChatService` / `AiOpsService` | 执行前后无持久化 | 创建 + 更新 `diagnosis_session` |
---
## 五、可观测能力
### 5.1 查询场景
| 需求 | SQL | 说明 |
|------|-----|------|
| 某次诊断用了哪些工具 | `SELECT * FROM tool_invocation WHERE session_id=?` | 按 session 关联 |
| lookup_knowledge 的 L0/L1 命中率 | `SELECT retrieval_layer, COUNT(*) FROM tool_invocation WHERE tool_name='lookup_knowledge' GROUP BY retrieval_layer` | 聚合检索层分布 |
| 某个 Agent 的平均思考耗时 | `SELECT AVG(duration_ms) FROM agent_step WHERE agent_name=?` | 按 Agent 分组 |
| 某次诊断的完整决策链 | `SELECT * FROM agent_step WHERE session_id=? ORDER BY step_index` | 按步骤号排序 |
| 被截断的检索占比 | `SELECT COUNT(*) FROM tool_invocation WHERE is_truncated=true AND tool_name='lookup_knowledge'` | 条件计数 |
| 高置信度但用户反馈 negative | `SELECT * FROM diagnosis_session WHERE JSON_EXTRACT(self_evaluation, '$.confidence') > 80 AND feedback='not_useful'` | JSON 条件查询 |
### 5.2 评估指标
| 指标 | 计算方式 | 数据来源 |
|------|---------|---------|
| 平均诊断耗时 | `AVG(total_duration_ms)` | diagnosis_session |
| 平均 Token 消耗 | `AVG(total_token_count)` | diagnosis_session |
| 工具采纳率 | `tools_accepted / tools_proposed` | self_evaluation |
| L0 命中率 | `l0_match_count > 0 的比例` | tool_invocation |
| 截断率 | `is_truncated=true 的比例` | tool_invocation |
| 用户满意度 | `feedback='useful' 的比例` | diagnosis_session |
---
## 六、与现有表的关系
### 6.1 diagnosis_session vs 现有 diagnosis_record
- **`diagnosis_record`** 保持不动,继续用于"告警分析"场景的领域字段(root_cause、solution 等)
- **`diagnosis_session`** 是通用会话存储,覆盖 ChatService 和 AiOpsService
- 两者通过 `session_id` 可关联
### 6.2 迁移策略
| 阶段 | 动作 |
|:----:|------|
| MVP | 新建三张表,新代码写入新表 |
| V1.1 | 评估是否将 diagnosis_record 合并回 diagnosis_session(加 fault 相关字段到 JSON) |
| V1.2 | 数据量 > 10 万时评估是否需要归档或迁移 |
---
## 七、未完成事项
- [ ] AI Ops Supervisor 的 Agent 执行步骤如何对应 agent_step 表(Supervisor 内嵌的子 Agent 步骤归到同一个 session 还是独立)
- [ ] self_evaluation 的 confidence 自评通过什么方式获取(单独的 LLM 调用还是在 prompt 中要求输出)
- [ ] feedback 字段和前端的交互方式
- [ ] Tool_invocation 的 output_preview 截断策略(当前建议 500 字符)
---
## 八、实现变更记录
### 8.1 与设计文档的差异
| 设计 | 实现 | 原因 |
|------|------|------|
| AgentLoggingHook 为 @Component | 改为 POJO(构造注入 Repository + agentName) | 需要为 ChatService / AiOpsService 创建多个 Hook 实例(不同 agentName) |
| sessionId 通过 RunnableConfig 的 metadata 携带 | 通过 `RunnableConfig.builder().addMetadata("sessionId", id)` 构建 | 确认框架 API 原生支持,线程安全 |
| Token 从 ChatResponse 获取 | 增加了 `TokenTrackingChatModel` 包装器拦截 ChatModel.call() | 框架的 `_TOKEN_USAGE_` 仅在 stream 路径可用,call 路径需自行拦截 |
| sessionId 汇总后回填 | `backfillSessionMetrics()` 从 agent_step 表统计 | 避免在 Hook 中维护累加状态 |
### 8.2 新增文件(超出原设计)
| 文件 | 用途 |
|------|------|
| `TokenTrackingChatModel.java` | ChatModel 包装器,拦截 call() 获取实际 token 用量 |
| `TokenUsageHolder.java` | ThreadLocal 传递 token 数给 Hook |
| `QuestionComplexity.java` | 问题复杂度判断,路由单 Agent / 多 Agent |
| `SessionContextHolder.java` | ThreadLocal 传递 sessionId(同步路径兜底) |
### 8.3 删除文件
| 文件 | 原因 |
|------|------|
| `DiagnosisRecord.java` / `DiagnosisRecordRepository.java` / `DiagnosisStatus.java` | 被新三表替代,V007 Flyway 迁移删除 |
| `.docs/mvp/` | 内容合并到根目录 `mvp/` |