- 删除 .docs/mvp 目录,内容合并到根目录 mvp/ - 更新 session-storage-design.md 实现变更记录 - 修复引用路径
12 KiB
12 KiB
会话存储方案设计
日期: 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(诊断会话)
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 决策步骤)
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(工具调用明细)
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/ |