Files
Cyrene/backend/ai-core/internal/persona/emotion_state.go
T
AskaEth 87214b9441 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>
2026-05-23 15:25:12 +08:00

182 lines
5.4 KiB
Go

package persona
import (
"log"
"sync"
"time"
)
// MoodTransition records a change from one mood to another.
type MoodTransition struct {
From string `json:"from"`
To string `json:"to"`
Reason string `json:"reason"`
Timestamp time.Time `json:"timestamp"`
}
// EmotionState is the current emotional state of the persona.
type EmotionState struct {
CurrentMood string `json:"current_mood"`
Intensity float64 `json:"intensity"` // 0.0 - 1.0
DominantSentiment string `json:"dominant_sentiment"`
SentimentCounts map[string]int `json:"sentiment_counts"`
MoodHistory []MoodTransition `json:"mood_history"`
LastUpdated time.Time `json:"last_updated"`
}
// EmotionTracker manages emotional state for a single user.
// Tracks mood, intensity, sentiment accumulation, and triggers transitions.
type EmotionTracker struct {
mu sync.Mutex
state EmotionState
moodConfig []MoodConfig // from YAML mood_system
positiveThreshold int // sentiment count to trigger positive transition
negativeThreshold int // sentiment count to trigger negative transition
maxHistory int // max mood history entries
}
// NewEmotionTracker creates a new tracker from YAML mood config.
func NewEmotionTracker(moodSystem []MoodConfig) *EmotionTracker {
return &EmotionTracker{
state: EmotionState{
CurrentMood: "thoughtful",
Intensity: 0.3,
DominantSentiment: "neutral",
SentimentCounts: map[string]int{"positive": 0, "neutral": 0, "negative": 0},
MoodHistory: make([]MoodTransition, 0, 20),
LastUpdated: time.Now(),
},
moodConfig: moodSystem,
positiveThreshold: 3,
negativeThreshold: 3,
maxHistory: 20,
}
}
// RecordSentiment records a user sentiment and potentially triggers mood transitions.
func (t *EmotionTracker) RecordSentiment(sentiment string) {
t.mu.Lock()
defer t.mu.Unlock()
t.state.SentimentCounts[sentiment]++
t.state.LastUpdated = time.Now()
total := t.state.SentimentCounts["positive"] + t.state.SentimentCounts["neutral"] + t.state.SentimentCounts["negative"]
if total > 0 {
posRatio := float64(t.state.SentimentCounts["positive"]) / float64(total)
negRatio := float64(t.state.SentimentCounts["negative"]) / float64(total)
switch {
case posRatio > 0.5:
t.state.DominantSentiment = "positive"
case negRatio > 0.5:
t.state.DominantSentiment = "negative"
default:
t.state.DominantSentiment = "neutral"
}
}
posCount := t.state.SentimentCounts["positive"]
negCount := t.state.SentimentCounts["negative"]
if posCount >= t.positiveThreshold && t.state.CurrentMood != "happy" && t.state.CurrentMood != "playful" {
if t.state.Intensity > 0.6 {
t.applyMoodTransition("playful", "积极情绪积累")
} else {
t.applyMoodTransition("happy", "积极情绪积累")
}
t.state.SentimentCounts["positive"] = 0
}
if negCount >= t.negativeThreshold && t.state.CurrentMood != "worried" {
t.applyMoodTransition("worried", "消极情绪积累")
t.state.SentimentCounts["negative"] = 0
}
}
// UpdateMood explicitly changes mood for significant events.
func (t *EmotionTracker) UpdateMood(trigger string) {
t.mu.Lock()
defer t.mu.Unlock()
switch trigger {
case "user_returned":
t.applyMoodTransition("happy", "开拓者回来了")
case "long_silence":
if t.state.CurrentMood != "thoughtful" && t.state.CurrentMood != "nostalgic" {
t.applyMoodTransition("thoughtful", "长时间没有交流")
}
case "deep_conversation":
t.applyMoodTransition("thoughtful", "深度对话后")
case "nostalgic_trigger":
t.applyMoodTransition("nostalgic", "触及回忆")
}
}
// GetCurrentMood returns the current mood, its YAML expression, and intensity.
func (t *EmotionTracker) GetCurrentMood() (mood string, expression string, intensity float64) {
t.mu.Lock()
defer t.mu.Unlock()
mood = t.state.CurrentMood
intensity = t.state.Intensity
for _, mc := range t.moodConfig {
if mc.Mood == mood {
expression = mc.Expression
break
}
}
return
}
// Decay reduces intensity over time, drifting toward "thoughtful" baseline.
func (t *EmotionTracker) Decay() {
t.mu.Lock()
defer t.mu.Unlock()
hoursSinceUpdate := time.Since(t.state.LastUpdated).Hours()
decayAmount := hoursSinceUpdate * 0.1
t.state.Intensity -= decayAmount
if t.state.Intensity < 0.1 {
t.state.Intensity = 0.1
}
if t.state.Intensity < 0.2 && t.state.CurrentMood != "thoughtful" {
t.applyMoodTransition("thoughtful", "情绪自然消退")
}
}
// applyMoodTransition internal mood change with hysteresis.
func (t *EmotionTracker) applyMoodTransition(newMood, reason string) {
if t.state.CurrentMood == newMood {
return
}
oldMood := t.state.CurrentMood
t.state.CurrentMood = newMood
t.state.Intensity = 0.5 + t.state.Intensity*0.3
if t.state.Intensity > 1.0 {
t.state.Intensity = 1.0
}
transition := MoodTransition{
From: oldMood,
To: newMood,
Reason: reason,
Timestamp: time.Now(),
}
t.state.MoodHistory = append(t.state.MoodHistory, transition)
if len(t.state.MoodHistory) > t.maxHistory {
t.state.MoodHistory = t.state.MoodHistory[1:]
}
log.Printf("[情感] 心情转变: %s -> %s (原因: %s, 强度: %.2f)", oldMood, newMood, reason, t.state.Intensity)
}
// GetState returns a copy of the current emotion state.
func (t *EmotionTracker) GetState() EmotionState {
t.mu.Lock()
defer t.mu.Unlock()
return t.state
}