feat: Phase 1+2 架构进化 — 连续思考链/主动消息决策/情感状态机/离线自主思考 (86文件)
Phase 1 (基础设施): - ThinkChain 思考链连续性 + 差异化思考提示词 (persistent) - AutonomousToolPolicy 工具安全策略 (safe/unsafe/conditional) - MessageScheduler 自适应消息节奏 (Idle/Available/Busy) - SessionEnrichmentStore 渐进式上下文丰富 (5层) - ConversationBus 事件总线 + ResponseCache (dedup) - pkg/logger 统一日志 + 所有 handler 替换 fmt.Printf - NPE 守卫/链路优化/数据库表修复/Go workspace Phase 2 (人格交互): - EmotionState/EmotionTracker 情感状态机 (5种心情, 情绪衰减) - ProactiveGuard 主动消息多维决策 (静默时段/紧急度/频率/校验) - Gateway↔ai-core 在线状态感知链路 (presence notification) - 离线思考频率控制 + 重连问候 + 离线消息排队 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -88,7 +88,7 @@ func (h *ChatHandler) HandleWebSocket(c *gin.Context) {
|
||||
// 升级WebSocket连接
|
||||
conn, err := h.upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Printf("[WS] 升级连接失败: %v", err)
|
||||
logger.Printf("[WS] 升级连接失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ func (h *ChatHandler) handleMessage(client *ws.Client, msg ws.ClientMessage) {
|
||||
case "history":
|
||||
h.handleHistoryRequest(client, msg)
|
||||
default:
|
||||
log.Printf("[WS] 未知消息类型: %s from user=%s", msg.Type, client.UserID)
|
||||
logger.Printf("[WS] 未知消息类型: %s from user=%s", msg.Type, client.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,8 +128,8 @@ func (h *ChatHandler) handleChatMessage(client *ws.Client, msg ws.ClientMessage)
|
||||
|
||||
// 持久化用户消息到数据库(在 WebSocket 发送之前)
|
||||
if h.sessionStore != nil && h.sessionStore.IsAvailable() {
|
||||
if err := h.sessionStore.AddMessage(client.SessionID, "user", msg.Content); err != nil {
|
||||
log.Printf("[chat] 持久化用户消息失败: %v", err)
|
||||
if err := h.sessionStore.AddMessage(client.SessionID, "user", "chat", msg.Content); err != nil {
|
||||
logger.Printf("[chat] 持久化用户消息失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func (h *ChatHandler) handleChatMessage(client *ws.Client, msg ws.ClientMessage)
|
||||
}
|
||||
reqBody, err := json.Marshal(aiReq)
|
||||
if err != nil {
|
||||
log.Printf("[chat] 序列化请求失败: %v", err)
|
||||
logger.Printf("[chat] 序列化请求失败: %v", err)
|
||||
h.hub.UpdateSessionState(client.SessionID, "error")
|
||||
client.SendMessage(ws.ServerMessage{
|
||||
Type: "error",
|
||||
@@ -183,7 +183,7 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
aiCoreURL := h.cfg.AICoreURL + "/api/v1/chat"
|
||||
httpReq, err := http.NewRequest("POST", aiCoreURL, bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
log.Printf("[chat] 创建 AI-Core 请求失败: %v", err)
|
||||
logger.Printf("[chat] 创建 AI-Core 请求失败: %v", err)
|
||||
h.hub.UpdateSessionState(client.SessionID, "error")
|
||||
client.SendMessage(ws.ServerMessage{
|
||||
Type: "error",
|
||||
@@ -199,7 +199,7 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
httpClient := &http.Client{Timeout: 120 * time.Second}
|
||||
resp, err := httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
log.Printf("[chat] AI-Core 调用失败: %v", err)
|
||||
logger.Printf("[chat] AI-Core 调用失败: %v", err)
|
||||
h.hub.UpdateSessionState(client.SessionID, "error")
|
||||
client.SendMessage(ws.ServerMessage{
|
||||
Type: "error",
|
||||
@@ -213,7 +213,7 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
log.Printf("[chat] AI-Core 返回错误 [%d]: %s", resp.StatusCode, string(body))
|
||||
logger.Printf("[chat] AI-Core 返回错误 [%d]: %s", resp.StatusCode, string(body))
|
||||
h.hub.UpdateSessionState(client.SessionID, "error")
|
||||
client.SendMessage(ws.ServerMessage{
|
||||
Type: "error",
|
||||
@@ -273,13 +273,13 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
ReviewMessages []ws.ReviewMessage `json:"review_messages,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(data), &chunk); err != nil {
|
||||
log.Printf("[chat] 解析 SSE delta 失败: %v, raw=%s", err, data)
|
||||
logger.Printf("[chat] 解析 SSE delta 失败: %v, raw=%s", err, data)
|
||||
continue
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
if chunk.Error != "" {
|
||||
log.Printf("[chat] AI-Core 流式错误: %s", chunk.Error)
|
||||
logger.Printf("[chat] AI-Core 流式错误: %s", chunk.Error)
|
||||
h.hub.UpdateSessionState(client.SessionID, "error")
|
||||
client.SendMessage(ws.ServerMessage{
|
||||
Type: "error",
|
||||
@@ -312,8 +312,8 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
reviewMsgID := fmt.Sprintf("%s_r%d", msgID, i)
|
||||
// 持久化每条审查消息
|
||||
if h.sessionStore != nil && h.sessionStore.IsAvailable() {
|
||||
if err := h.sessionStore.AddMessage(client.SessionID, role, rm.Content); err != nil {
|
||||
log.Printf("[chat] 持久化审查消息失败: %v", err)
|
||||
if err := h.sessionStore.AddMessage(client.SessionID, role, msgType, rm.Content); err != nil {
|
||||
logger.Printf("[chat] 持久化审查消息失败: %v", err)
|
||||
}
|
||||
}
|
||||
h.hub.CacheMessage(client.UserID, client.SessionID, ws.Message{
|
||||
@@ -331,9 +331,9 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
SessionID: client.SessionID,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
})
|
||||
// 小延迟让消息逐条到达,更像真人
|
||||
if i < len(chunk.ReviewMessages)-1 {
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
// 使用 MessageScheduler 计算的 per-message 延迟
|
||||
if rm.DelayMs > 0 {
|
||||
time.Sleep(time.Duration(rm.DelayMs) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
hasReview = true
|
||||
@@ -366,7 +366,7 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Printf("[chat] SSE 读取错误: %v", err)
|
||||
logger.Printf("[chat] SSE 读取错误: %v", err)
|
||||
h.hub.UpdateSessionState(client.SessionID, "error")
|
||||
client.SendMessage(ws.ServerMessage{
|
||||
Type: "error",
|
||||
@@ -416,8 +416,8 @@ func (h *ChatHandler) streamResponse(client *ws.Client, mode string, reqBody []b
|
||||
// 如果有审查消息,每条已单独持久化,跳过 fullText 以避免重复
|
||||
if !hasReview && fullText != "" {
|
||||
if h.sessionStore != nil && h.sessionStore.IsAvailable() {
|
||||
if err := h.sessionStore.AddMessage(client.SessionID, "assistant", fullText); err != nil {
|
||||
log.Printf("[chat] 持久化 AI 回复失败: %v", err)
|
||||
if err := h.sessionStore.AddMessage(client.SessionID, "assistant", "chat", fullText); err != nil {
|
||||
logger.Printf("[chat] 持久化 AI 回复失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,18 +466,20 @@ func (h *ChatHandler) handleHistoryRequest(client *ws.Client, msg ws.ClientMessa
|
||||
if len(messages) == 0 && h.sessionStore != nil && h.sessionStore.IsAvailable() {
|
||||
dbMessages, err := h.sessionStore.GetMessages(sessionID, 50, 0)
|
||||
if err == nil && len(dbMessages) > 0 {
|
||||
log.Printf("[history] 从数据库恢复会话历史: session=%s, %d 条消息", sessionID, len(dbMessages))
|
||||
logger.Printf("[history] 从数据库恢复会话历史: session=%s, %d 条消息", sessionID, len(dbMessages))
|
||||
// 恢复到内存缓存
|
||||
for _, dbMsg := range dbMessages {
|
||||
messages = append(messages, ws.Message{
|
||||
ID: fmt.Sprintf("db_%d", dbMsg.ID),
|
||||
Role: dbMsg.Role,
|
||||
MsgType: dbMsg.MsgType,
|
||||
Content: dbMsg.Content,
|
||||
Timestamp: dbMsg.CreatedAt.UnixMilli(),
|
||||
})
|
||||
h.hub.CacheMessage(client.UserID, sessionID, ws.Message{
|
||||
ID: fmt.Sprintf("db_%d", dbMsg.ID),
|
||||
Role: dbMsg.Role,
|
||||
MsgType: dbMsg.MsgType,
|
||||
Content: dbMsg.Content,
|
||||
Timestamp: dbMsg.CreatedAt.UnixMilli(),
|
||||
})
|
||||
@@ -497,7 +499,7 @@ func (h *ChatHandler) handleHistoryRequest(client *ws.Client, msg ws.ClientMessa
|
||||
}
|
||||
|
||||
if err := client.SendMessage(response); err != nil {
|
||||
log.Printf("[WS] 发送历史消息失败: %v", err)
|
||||
logger.Printf("[WS] 发送历史消息失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,10 +537,22 @@ func (h *ChatHandler) HandleProactiveMessage(c *gin.Context) {
|
||||
// 检查用户是否在线
|
||||
onlineCount := h.hub.UserClientCount(req.UserID)
|
||||
if onlineCount == 0 {
|
||||
// Phase 2: 离线时排队,等待用户重连后推送
|
||||
data, _ := json.Marshal(ws.ServerMessage{
|
||||
Type: "response",
|
||||
MessageID: "proactive_" + generateID(),
|
||||
Content: req.Content,
|
||||
Role: "assistant",
|
||||
MsgType: "proactive",
|
||||
SessionID: req.SessionID,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
})
|
||||
h.hub.QueueProactiveMessage(req.UserID, data)
|
||||
logger.Printf("[proactive] 用户离线,消息已排队: user=%s", req.UserID)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"reason": "user_offline",
|
||||
"message": "用户不在线,消息未发送",
|
||||
"success": true,
|
||||
"reason": "queued",
|
||||
"message": "用户离线,消息已排队等待重连后推送",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -557,7 +571,7 @@ func (h *ChatHandler) HandleProactiveMessage(c *gin.Context) {
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
log.Printf("[proactive] 序列化消息失败: %v", err)
|
||||
logger.Printf("[proactive] 序列化消息失败: %v", err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "内部错误"})
|
||||
return
|
||||
}
|
||||
@@ -577,7 +591,7 @@ func (h *ChatHandler) HandleProactiveMessage(c *gin.Context) {
|
||||
})
|
||||
h.hub.RecordMessage(sessionID, "assistant", req.Content)
|
||||
|
||||
log.Printf("[proactive] 主动消息已推送: user=%s, online=%d, content_len=%d", req.UserID, onlineCount, len(req.Content))
|
||||
logger.Printf("[proactive] 主动消息已推送: user=%s, online=%d, content_len=%d", req.UserID, onlineCount, len(req.Content))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
|
||||
Reference in New Issue
Block a user