- 删除 .docs/mvp 目录,内容合并到根目录 mvp/ - 更新 session-storage-design.md 实现变更记录 - 修复引用路径
297 lines
12 KiB
Markdown
297 lines
12 KiB
Markdown
# 会话存储方案设计
|
||
|
||
**日期**: 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/` |
|
||
|