Skip to content

4.3 Mood Module

Lumi Bunny edited this page Aug 12, 2025 · 2 revisions

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.

The mood tracking module is split into three main components:

  1. MoodDetector: This component uses regex and sentiment analysis to detect the user's mood from their messages.
  2. DynamicMoodContext: This component manages the mood context for each user, including adding new mood observations, updating decay factors, and generating mood summaries.
  3. IntegratedMoodSystem: This component integrates the mood detection and context management into a single system, and provides a simple interface for other components to use.

Features of the mood tracking module:

  1. 🚀 Performance: Regex handles 80% of cases very quickly, low latency.
  2. 🎯 Accuracy: Transformer catches complex cases, something AI excels at vs regex.
  3. 🧠 Natural Memory: Mood context decays like human emotions.
  4. 💾 No Storage Bloat: No permanent mood files to manage.
  5. 🔄 Dynamic Response: LLM adapts tone based on current emotional state.
  6. 👥 Multi-User: Each user has independent mood tracking.

Overview of MoodDetector

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.

1. Imports and Setup

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

2. MoodResult Class and Data Structure

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 transformer

3. HybridMoodDetector Class

The 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:

  1. Checks if GPU is available: Will not crash if there is no GPU available.
  2. Tries to load transformer model: Graceful fallback if this fails.
  3. Sets up enhanced regex patterns: Always works as backup if GPU and transformers are not available, otherwise acts as good basic support.

Transformer initialization

    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.

Enhanced Regex Setup

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
    }

Main Detection Logic

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)

Overview of DynamicMoodContext

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.

1. MoodObservation Class and Data Structure

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)

2. DynamicMoodContext

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.

3. Adding Observations

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 matters

4. Decay Factor Calculation

Smart 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_factor

5. Context Generation

By 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()
    }

6. Inner Thoughts Generation

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 thoughts

Overview of mood_system.py

The Integration Layer

This class combines both mood detection and context management into a single system that integrates seamlessly within the BunnyBot application.

1. IntegratedMoodSystem Class

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
        }

2. Message Processing

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/debugging

3. LLM Integration

The 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"}
]

4. BunnyChatMoodIntegration Helper

Integration Point

This here is what you'll call in bunny_chat.py to add mood awareness with just one line of code.

How they work together:

The flow:

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

Example Conversation Flow:

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."}

Clone this wiki locally