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:
@@ -4,7 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ func NewAutomationStore(db *sql.DB) (*AutomationStore, error) {
|
||||
return nil, fmt.Errorf("自动化表迁移失败: %w", err)
|
||||
}
|
||||
|
||||
log.Println("[AutomationStore] 自动化持久化存储已初始化")
|
||||
logger.Println("[AutomationStore] 自动化持久化存储已初始化")
|
||||
return store, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -60,7 +60,7 @@ func NewBriefingStore(db *sql.DB) (*BriefingStore, error) {
|
||||
return nil, fmt.Errorf("简报表迁移失败: %w", err)
|
||||
}
|
||||
|
||||
log.Println("[BriefingStore] 简报持久化存储已初始化")
|
||||
logger.Println("[BriefingStore] 简报持久化存储已初始化")
|
||||
return store, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package store
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -33,7 +33,7 @@ func NewFileStore(db *sql.DB) (*FileStore, error) {
|
||||
return nil, fmt.Errorf("文件表迁移失败: %w", err)
|
||||
}
|
||||
|
||||
log.Println("[FileStore] 文件持久化存储已初始化")
|
||||
logger.Println("[FileStore] 文件持久化存储已初始化")
|
||||
return store, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
@@ -73,7 +73,7 @@ func NewKnowledgeStore(db *sql.DB) (*KnowledgeStore, error) {
|
||||
return nil, fmt.Errorf("知识库表迁移失败: %w", err)
|
||||
}
|
||||
|
||||
log.Println("[KnowledgeStore] 知识库持久化存储已初始化")
|
||||
logger.Println("[KnowledgeStore] 知识库持久化存储已初始化")
|
||||
return store, nil
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func (s *KnowledgeStore) migrate() error {
|
||||
// 尝试创建 GIN 索引(可能因权限或扩展问题失败,但不影响功能)
|
||||
_, err := s.db.Exec(`CREATE INDEX IF NOT EXISTS idx_kc_tsv_gin ON knowledge_chunks USING GIN(tsv)`)
|
||||
if err != nil {
|
||||
log.Printf("[KnowledgeStore] ⚠ GIN索引创建失败(将使用ILIKE降级搜索): %v", err)
|
||||
logger.Printf("[KnowledgeStore] ⚠ GIN索引创建失败(将使用ILIKE降级搜索): %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -260,7 +260,7 @@ func (s *KnowledgeStore) AddDocument(doc *KnowledgeDocument) error {
|
||||
|
||||
// 更新知识库统计
|
||||
if err := s.updateKBStats(doc.KBID); err != nil {
|
||||
log.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
|
||||
logger.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -340,7 +340,7 @@ func (s *KnowledgeStore) DeleteDocument(id string) error {
|
||||
|
||||
// 更新知识库统计
|
||||
if err := s.updateKBStats(kbID); err != nil {
|
||||
log.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
|
||||
logger.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -452,12 +452,12 @@ func (s *KnowledgeStore) ChunkDocument(docID string) (int, error) {
|
||||
|
||||
// 更新文档的分块计数
|
||||
if err := s.UpdateDocumentChunkCount(docID, len(chunks)); err != nil {
|
||||
log.Printf("[KnowledgeStore] 更新文档分块计数失败: %v", err)
|
||||
logger.Printf("[KnowledgeStore] 更新文档分块计数失败: %v", err)
|
||||
}
|
||||
|
||||
// 更新知识库统计
|
||||
if err := s.updateKBStats(doc.KBID); err != nil {
|
||||
log.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
|
||||
logger.Printf("[KnowledgeStore] 更新知识库统计失败: %v", err)
|
||||
}
|
||||
|
||||
return len(chunks), nil
|
||||
@@ -474,7 +474,7 @@ func (s *KnowledgeStore) SearchChunks(kbID, query string, limit int) ([]SearchCh
|
||||
// 尝试使用 PostgreSQL 全文搜索
|
||||
results, err := s.searchWithFullText(kbID, query, limit)
|
||||
if err != nil {
|
||||
log.Printf("[KnowledgeStore] 全文搜索失败,降级为ILIKE: %v", err)
|
||||
logger.Printf("[KnowledgeStore] 全文搜索失败,降级为ILIKE: %v", err)
|
||||
// 降级为 ILIKE
|
||||
results, err = s.searchWithILike(kbID, query, limit)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,7 @@ package store
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewReminderStore(db *sql.DB) (*ReminderStore, error) {
|
||||
return nil, fmt.Errorf("提醒表迁移失败: %w", err)
|
||||
}
|
||||
|
||||
log.Println("[ReminderStore] 提醒持久化存储已初始化")
|
||||
logger.Println("[ReminderStore] 提醒持久化存储已初始化")
|
||||
return store, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package store
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/yourname/cyrene-ai/pkg/logger"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
@@ -24,6 +24,7 @@ type Message struct {
|
||||
ID int `json:"id"`
|
||||
SessionID string `json:"session_id"`
|
||||
Role string `json:"role"`
|
||||
MsgType string `json:"msg_type"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
@@ -60,7 +61,7 @@ func NewSessionStore(databaseURL string) (*SessionStore, error) {
|
||||
return nil, fmt.Errorf("数据库迁移失败: %w", err)
|
||||
}
|
||||
|
||||
log.Println("[SessionStore] PostgreSQL 持久化存储已初始化")
|
||||
logger.Println("[SessionStore] PostgreSQL 持久化存储已初始化")
|
||||
return store, nil
|
||||
}
|
||||
|
||||
@@ -82,11 +83,15 @@ func (s *SessionStore) migrate() error {
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(64) REFERENCES sessions(id) ON DELETE CASCADE,
|
||||
role VARCHAR(16) NOT NULL,
|
||||
msg_type VARCHAR(16) DEFAULT 'chat',
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(session_id, created_at)`,
|
||||
|
||||
// 为已存在的数据库添加 msg_type 列 (Phase 0.1)
|
||||
`ALTER TABLE messages ADD COLUMN IF NOT EXISTS msg_type VARCHAR(16) DEFAULT 'chat'`,
|
||||
}
|
||||
|
||||
for _, q := range queries {
|
||||
@@ -200,10 +205,10 @@ func (s *SessionStore) DeleteAllUserSessions(userID string) error {
|
||||
}
|
||||
|
||||
// AddMessage 添加一条消息到会话
|
||||
func (s *SessionStore) AddMessage(sessionID, role, content string) error {
|
||||
func (s *SessionStore) AddMessage(sessionID, role, msgType, content string) error {
|
||||
_, err := s.db.Exec(
|
||||
`INSERT INTO messages (session_id, role, content) VALUES ($1, $2, $3)`,
|
||||
sessionID, role, content,
|
||||
`INSERT INTO messages (session_id, role, msg_type, content) VALUES ($1, $2, $3, $4)`,
|
||||
sessionID, role, msgType, content,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("添加消息失败: %w", err)
|
||||
@@ -221,7 +226,7 @@ func (s *SessionStore) GetMessages(sessionID string, limit, offset int) ([]Messa
|
||||
}
|
||||
|
||||
rows, err := s.db.Query(
|
||||
`SELECT id, session_id, role, content, created_at
|
||||
`SELECT id, session_id, role, COALESCE(msg_type, 'chat'), content, created_at
|
||||
FROM messages WHERE session_id = $1
|
||||
ORDER BY created_at ASC
|
||||
LIMIT $2 OFFSET $3`,
|
||||
@@ -235,7 +240,7 @@ func (s *SessionStore) GetMessages(sessionID string, limit, offset int) ([]Messa
|
||||
var messages []Message
|
||||
for rows.Next() {
|
||||
var msg Message
|
||||
if err := rows.Scan(&msg.ID, &msg.SessionID, &msg.Role, &msg.Content, &msg.CreatedAt); err != nil {
|
||||
if err := rows.Scan(&msg.ID, &msg.SessionID, &msg.Role, &msg.MsgType, &msg.Content, &msg.CreatedAt); err != nil {
|
||||
return nil, fmt.Errorf("扫描消息行失败: %w", err)
|
||||
}
|
||||
messages = append(messages, msg)
|
||||
@@ -262,6 +267,7 @@ type SearchResult struct {
|
||||
SessionID string `json:"session_id"`
|
||||
SessionTitle string `json:"session_title"`
|
||||
Role string `json:"role"`
|
||||
MsgType string `json:"msg_type"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
@@ -287,7 +293,7 @@ func (s *SessionStore) SearchMessages(userID, query string, limit, offset int) (
|
||||
|
||||
// 分页查询,关联 sessions 获取会话标题
|
||||
rows, err := s.db.Query(
|
||||
`SELECT m.id, m.session_id, COALESCE(s.title, '') AS session_title, m.role, m.content, m.created_at
|
||||
`SELECT m.id, m.session_id, COALESCE(s.title, '') AS session_title, m.role, COALESCE(m.msg_type, 'chat'), m.content, m.created_at
|
||||
FROM messages m
|
||||
JOIN sessions s ON m.session_id = s.id
|
||||
WHERE s.user_id = $1 AND m.content ILIKE '%' || $2 || '%'
|
||||
@@ -303,7 +309,7 @@ func (s *SessionStore) SearchMessages(userID, query string, limit, offset int) (
|
||||
var results []SearchResult
|
||||
for rows.Next() {
|
||||
var r SearchResult
|
||||
if err := rows.Scan(&r.MessageID, &r.SessionID, &r.SessionTitle, &r.Role, &r.Content, &r.CreatedAt); err != nil {
|
||||
if err := rows.Scan(&r.MessageID, &r.SessionID, &r.SessionTitle, &r.Role, &r.MsgType, &r.Content, &r.CreatedAt); err != nil {
|
||||
return nil, 0, fmt.Errorf("扫描搜索结果行失败: %w", err)
|
||||
}
|
||||
results = append(results, r)
|
||||
|
||||
Reference in New Issue
Block a user