# 会话存储方案设计 **日期**: 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/` |