Files

4.4 KiB
Raw Permalink Blame History

会话管理设计

会话存储策略

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 代码

@Service
public class SessionService {
    
    @Autowired
    private RedisTemplate<String, String> 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);
    }
}