# 会话管理设计 ## 会话存储策略 ### Redis(主) **数据结构**: ``` key: session:{session_id} value: { "sessionId": "sess-abc", "userId": "user-123", "currentDiagnosisId": "diag-001", "messages": [ {"role": "user", "content": "诊断订单 A"}, {"role": "assistant", "content": "完整报告..."} ], "context": { "province": "广东", "apiName": "社保查询", "errorCode": "40003" }, "createdAt": "2024-06-15T14:30:00Z", "lastActiveAt": "2024-06-15T14:35:00Z" } ttl: 1800秒(30分钟) ``` **优势**: - ✅ 快速读写 - ✅ 自动过期 - ✅ 支持追问(保存上下文) --- ### MySQL(辅助,可选) **同步策略**: 1. 重要会话同步 - 有用户反馈的会话 - 诊断失败的会话(BadCase) - 多轮对话 > 3 轮的会话 2. 同步时机 - 会话结束时(30分钟过期) - 用户反馈时(实时) - 定时任务(每小时,可选) 3. 同步目标 - conversation_history 表 - 用于长期分析和审计 --- ## 数据流设计 ### 场景1:单次诊断(主流 80%) ``` 1. 用户发起诊断 POST /api/diagnosis/start { "orderId": "202406150001" } 2. 创建会话(Redis) key: session:sess-abc ttl: 1800秒 3. 创建诊断记录(MySQL) INSERT INTO diagnosis_record - diagnosis_id: diag-001 - session_id: sess-abc - status: RUNNING 4. Agent 执行诊断 - 调用工具(queryOrder, queryLogs, searchDoc...) - 生成报告 5. 更新诊断记录(MySQL) UPDATE diagnosis_record - status: SUCCESS - root_cause: "idCard字段缺失" - report_markdown: "完整报告..." 6. 返回报告 → 大部分用户到此结束 ``` --- ### 场景2:追问(少数 20%) ``` 1. 用户追问 POST /api/chat { "sessionId": "sess-abc", "message": "为什么会缺失字段?" } 2. 从 Redis 获取上下文 GET session:sess-abc - 有之前的诊断结果 - 有对话历史 3. Agent 基于上下文回答 - 不创建新的 diagnosis_record - 只是普通对话 4. 更新 Redis 会话 - 追加对话历史 - 刷新 TTL(重新计时30分钟) 5. 可选:保存到 conversation_history(MySQL) - 如果需要长期分析 - 异步存储 ``` --- ### 场景3:同一会话多次诊断 ``` 1. 用户第一次诊断 "诊断订单 A" → diagnosis_record(diag-001, session_id=sess-abc) 2. 用户第二次诊断 "再诊断订单 B" → diagnosis_record(diag-002, session_id=sess-abc) 3. 会话关联 - 同一个 session_id - 两条 diagnosis_record - Redis 中保存完整对话历史 ``` --- ## 会话生命周期 ``` 创建 ↓ 活跃(每次交互刷新TTL) ↓ 30分钟无活动 ↓ 自动过期 ↓ 可选:同步到 MySQL(重要会话) ``` --- ## 实现示例 ### Java 代码 ```java @Service public class SessionService { @Autowired private RedisTemplate redisTemplate; private static final String SESSION_PREFIX = "session:"; private static final Duration SESSION_TTL = Duration.ofMinutes(30); // 创建会话 public String createSession(String userId) { String sessionId = UUID.randomUUID().toString(); SessionData session = SessionData.builder() .sessionId(sessionId) .userId(userId) .messages(new ArrayList<>()) .context(new HashMap<>()) .createdAt(LocalDateTime.now()) .lastActiveAt(LocalDateTime.now()) .build(); String key = SESSION_PREFIX + sessionId; redisTemplate.opsForValue().set(key, toJson(session), SESSION_TTL); return sessionId; } // 获取会话 public SessionData getSession(String sessionId) { String key = SESSION_PREFIX + sessionId; String json = redisTemplate.opsForValue().get(key); return json != null ? fromJson(json) : null; } // 更新会话(刷新TTL) public void updateSession(SessionData session) { session.setLastActiveAt(LocalDateTime.now()); String key = SESSION_PREFIX + session.getSessionId(); redisTemplate.opsForValue().set(key, toJson(session), SESSION_TTL); } // 删除会话 public void deleteSession(String sessionId) { String key = SESSION_PREFIX + sessionId; redisTemplate.delete(key); } } ```