-
Notifications
You must be signed in to change notification settings - Fork 1
4.3 Mood Module
The mood tracking module is designed to track the user's mood over time and use it to generate responses from the LLM that are more personalized and appropriate to the user's mood. It does this by using a hybrid of regex and sentiment analysis from an LLM.
- MoodDetector: This component uses regex and sentiment analysis to detect the user's mood from their messages.
- DynamicMoodContext: This component manages the mood context for each user, including adding new mood observations, updating decay factors, and generating mood summaries.
- IntegratedMoodSystem: This component integrates the mood detection and context management into a single system, and provides a simple interface for other components to use.
- 🚀 Performance: Regex handles 80% of cases very quickly, low latency.
- 🎯 Accuracy: Transformer catches complex cases, something AI excels at vs regex.
- 🧠 Natural Memory: Mood context decays like human emotions.
- 💾 No Storage Bloat: No permanent mood files to manage.
- 🔄 Dynamic Response: LLM adapts tone based on current emotional state.
- 👥 Multi-User: Each user has independent mood tracking.
The MoodDetector class is a hybrid mood detector that uses both regex and sentiment analysis to detect the user's mood from their messages. It uses the transformers library to perform sentiment analysis, and a set of regex patterns to detect the user's mood from their messages.
The file detects emotions from text using two methods: enhanced regex (fast) and a transformer model (accurate).
The file suses a few key imports:
-
re: for regex matching -
logging: for logging -
typing: for type hints -
dataclasses: for data classes -
transformers: for sentiment analysis -
torch: for GPU support
The MoodResult data structure provides all the information needed to understand and validate mood detection results.
@dataclass
class MoodResult:
mood: str # "joy", "sadness", etc.
intensity: float # 0.0 to 1.0 (how strong)
confidence: float # 0.0 to 1.0 (how sure we are)
method: str # "regex", "transformer", or "hybrid"
raw_scores: Optional[Dict] = None # Detailed scores from transformerThe HybridMoodDetector class is a hybrid mood detector that uses both regex and sentiment analysis to detect the user's mood from their messages.
class HybridMoodDetector:
# Hybrid mood detection using enhanced regex + lightweight transformer
def __init__(self, use_gpu: bool = True, model_name: str = "j-hartmann/emotion-english-distilroberta-base"):
self.use_gpu = use_gpu and torch.cuda.is_available()
self.model_name = model_name
self.transformer_pipeline = None
# Initialize transformer if available
if TRANSFORMERS_AVAILABLE:
self._initialize_transformer()
# Enhanced regex patterns with negation handling
self._setup_enhanced_regex()What it does:
- Checks if GPU is available: Will not crash if there is no GPU available.
- Tries to load transformer model: Graceful fallback if this fails.
- Sets up enhanced regex patterns: Always works as backup if GPU and transformers are not available, otherwise acts as good basic support.
def _initialize_transformer(self):
# Initialize the transformer model for sentiment analysis
try:
device = 0 if self.use_gpu else -1 # 0 for GPU, -1 for CPU
self.transformer_pipeline = pipeline(
"text-classification",
model=self.model_name,
device=device,
return_all_scores=True
)What this does: Loads a pre-trained emotion detection AI model that can understand complex text like sarcasm and nuance instead of just POSITIVE and NEGATIVE sentiments.
Regex patterns can be used to detect a user's mood from their messages. Enhancing this with negation handling and intensity modifiers allows for more accurate mood detection that simple keyword matching would miss.
def _setup_enhanced_regex(self):
# Detect negation: "not happy", "don't like"
self.negation_patterns = [
r'\b(?:not|n\'t|never|no|none)\b',
r'\b(?:don\'t|doesn\'t|didn\'t|won\'t)\b'
]
# Detect intensity: "very happy" vs "slightly happy"
self.intensity_modifiers = {
'high': ['extremely', 'incredibly', 'very', 'really'],
'medium': ['pretty', 'quite', 'fairly'],
'low': ['a bit', 'slightly', 'kind of']
}
# Emotion patterns with context
self.emotion_patterns = {
'joy': {
'patterns': [
r'\b(?:happy|joy|excited|thrilled)\b', # Direct emotions
r'\b(?:love|enjoy)\s+(?:this|it|that)', # Contextual emotions
r'\b(?:amazing|awesome|fantastic)\b' # Positive descriptors
],
'base_intensity': 0.7 # Default strength for this emotion
}
# ... more emotions
}A practical hybrid approach:
- Fast path: Uses regex for easy and obvious cases.
- Accurate path: Uses AI for complex cases (sarcasm, mixed emotions).
- Best of both: Combine results when both methods agree.
def detect_mood(self, text: str) -> Optional[MoodResult]:
# Step 1: Try regex first (fast - ~1ms)
regex_result = self._detect_with_enhanced_regex(text)
# Step 2: Use transformer for complex cases (~50ms)
if (regex_result is None or
regex_result.confidence < 0.7 or
self._is_complex_text(text)):
transformer_result = self._detect_with_transformer(text)
# Step 3: Combine results intelligently
return self._combine_results(regex_result, transformer_result, text)The DynamicMoodContext class is a dynamic mood context manager that manages temporary mood observations from users that naturally decays over time, providing additional context to the LLM without permanent storage. This fascilitates more personalized responses from the LLM.
Each mood observation is like a "memory" that gets weaker over time, just like human emotions. The MoodObservation class is a data structure that represents a mood observation from a user. It contains the following fields:
@dataclass
class MoodObservation:
mood: str # The detected emotion
intensity: float # How strong (0.0 to 1.0)
confidence: float # How sure we are (0.0 to 1.0)
context: str # What triggered this observation
timestamp: datetime # When it was detected
decay_factor: float = 1.0 # How much influence it has (decreases over time)def __init__(self, max_observations: int = 5, decay_hours: float = 2.0):
self.max_observations = max_observations # Keep only 5 recent moods
self.decay_hours = decay_hours # Moods fade after 2 hours
self.observations = deque(maxlen=max_observations) # Auto-removes old ones
# Define how each mood should influence responses
self.mood_influences = {
'joy': {
'response_tone': 'enthusiastic and positive',
'empathy_level': 'celebratory'
},
'sadness': {
'response_tone': 'gentle and supportive',
'empathy_level': 'compassionate'
}
# ... more moods
}The trick: Uses a deque (double-ended queue) that automatically removes the oldest observation when you add a 6th one.
New mood observations are added, and all existing observations get their "influence" recalculated based on "age".
def add_observation(self, mood: str, intensity: float, confidence: float, context: str):
observation = MoodObservation(
mood=mood,
intensity=intensity,
confidence=confidence,
context=context,
timestamp=datetime.now()
)
self.observations.append(observation) # Add new observation
self._update_decay_factors() # Update how much each observation mattersSmart decay logic:
- Recent moods have more influence than older moods.
- Strong emotions (higher intensity) fade slower.
- Confident detections last longer than uncertain ones.
def _update_decay_factors(self):
for observation in self.observations:
age_hours = (now - observation.timestamp).total_seconds() / 3600
# Time decay: newer = more influential
time_decay = max(0.1, 1.0 - (age_hours / self.decay_hours))
# Intensity factor: strong emotions last longer
intensity_factor = 0.5 + (observation.intensity * 0.5)
# Confidence factor: confident detections last longer
confidence_factor = 0.5 + (observation.confidence * 0.5)
# Combine all factors
observation.decay_factor = time_decay * intensity_factor * confidence_factorBy combining all recent mood observations into a single "current mood state" that considers recency, intensity and condifence, we can supply the AI with a bit more context on the tone of the user's messages. This is useful because AI and computers do not understand emotions the same way humans do, and tone does not always convey well in text format.
def get_current_mood_context(self):
# Calculate weighted mood scores
mood_scores = {}
for obs in self.observations:
weight = obs.decay_factor * obs.confidence
mood_scores[obs.mood] = mood_scores.get(obs.mood, 0) + weight
# Find dominant mood
dominant_mood = max(mood_scores.keys(), key=lambda m: mood_scores[m])
return {
'dominant_mood': dominant_mood,
'mood_summary': self._generate_mood_summary(mood_scores, total_weight),
'response_guidance': self.mood_influences[dominant_mood],
'mood_trend': self._analyze_mood_trend()
}The magic: This function creates "inner thoughts", sort of like an inner dialogue, by supplying mood context to help guide the LLM's reasoning and responses, potentially influencing its response style, without the need to store mood data permanently.
def generate_inner_thoughts(self) -> Optional[str]:
context = self.get_current_mood_context()
mood_summary = context['mood_summary'] # "User seems frustrated"
trend = context['mood_trend'] # "improving"
guidance = context['response_guidance'] # How to respond
# Create inner thoughts for the LLM
thoughts = f"[Internal Observation] {mood_summary}. "
thoughts += f"Response approach: {guidance['response_tone']}."
return thoughtsThis class combines both mood detection and context management into a single system that integrates seamlessly within the BunnyBot application.
Each user gets their own mood context and data, allowing for isolated data and ensuring that the mood system does not affect other users. So, User A's mood does not affect User B's context.
class IntegratedMoodSystem:
def __init__(self, use_gpu: bool = True, transformer_model: str = "..."):
# Initialize the hybrid detector
self.detector = HybridMoodDetector(use_gpu=use_gpu, model_name=transformer_model)
# Create per-user mood contexts (each user gets their own mood tracking)
self.user_contexts: Dict[str, DynamicMoodContext] = {}
# Configuration settings
self.config = {
'min_confidence_threshold': 0.3, # Only use confident mood detections
'context_decay_hours': 2.0, # Moods fade after 2 hours
'max_observations_per_user': 5, # Keep 5 recent moods per user
}This function takes a user message, detects mood, and updates that specific user's mood context.
def process_user_message(self, user_id: str, message: str) -> Optional[Dict]:
# Step 1: Detect mood using hybrid system
mood_result = self.detector.detect_mood(message)
# Step 2: Only proceed if confidence is high enough
if not mood_result or mood_result.confidence < self.config['min_confidence_threshold']:
return None
# Step 3: Get user's personal mood context
context = self._get_user_context(user_id)
# Step 4: Add this mood observation to their context
context.add_observation(
mood=mood_result.mood,
intensity=mood_result.intensity,
confidence=mood_result.confidence,
context=f"Detected from: '{message[:50]}...'"
)
return mood_info # Return for logging/debuggingThe magic happens here: This part takes your regular chat messages and inserts mood context as an "observation" role, taking on the "inner thoughts" of the AI.
**1. Get context: **
def get_mood_aware_messages(self, user_id: str, base_messages: List[Dict]) -> List[Dict]:
context = self._get_user_context(user_id)
return context.create_mood_aware_messages(base_messages)2. Create mood-aware context:
# Before:
[
{"role": "system", "content": "You are helpful"},
{"role": "user", "content": "This bug is frustrating"}
]
# After:
[
{"role": "system", "content": "You are helpful"},
{"role": "observation", "content": "[Internal Observation] User seems frustrated. Response approach: calm and validating."},
{"role": "user", "content": "This bug is frustrating"}
]This here is what you'll call in bunny_chat.py to add mood awareness with just one line of code.
User Message → mood_detector.py → dynamic_mood_context.py → mood_system.py → BunnyChat
↓ ↓ ↓ ↓ ↓
"I'm sad" Detects sadness Adds to context Creates inner Enhanced
(confidence: 0.8) (decays over time) thoughts response
Message 1: "I'm really excited about this project!"
-
Detector: Finds
"excited"→ joy, intensity: 0.8, confidence: 0.9 -
Context: Stores observation, generates:
"User is clearly feeling joy" -
LLM gets:
{"role": "observation", "content": "[Internal Observation] User is clearly feeling joy. Response approach: enthusiastic and positive."}Message 2: (30 minutes later) "This bug is frustrating!" -
Detector: Finds
"frustrating"→ anger, intensity: 0.7, confidence: 0.8 - Context: Previous joy observation decayed to 0.6, new anger is stronger
-
LLM gets:
{"role": "observation", "content": "[Internal Observation] User shows mixed emotions: anger and joy. Emotional trend appears to be declining. Response approach: calm and validating."}Message 3: (2 hours later) "Actually, I figured it out!" -
Detector: Finds
"figured it out"→ joy, intensity: 0.6, confidence: 0.7 - Context: Old observations mostly decayed, fresh joy dominates
-
LLM gets:
{"role": "observation", "content": "[Internal Observation] User seems to be feeling joy. Emotional trend appears to be improving. Response approach: enthusiastic and positive."}