- Deleted .env.example file as it is no longer needed. - Added .gitignore to manage ignored files and directories. - Introduced CLAUDE.md for AI provider integration documentation. - Created dev.sh for development setup and scripts. - Updated Dockerfile and Dockerfile.production for improved build processes. - Added multiple test files and directories for comprehensive testing. - Introduced new utility and service files for enhanced functionality. - Organized codebase with new directories and files for better maintainability.
460 lines
17 KiB
Python
460 lines
17 KiB
Python
"""
|
|
Personality Engine Plugin - Advanced User Personality Analysis
|
|
Essential personality modeling, learning, and response adaptation
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List
|
|
|
|
from extensions.plugin_manager import (PersonalityEnginePlugin, PluginMetadata,
|
|
PluginType)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AdvancedPersonalityEngine(PersonalityEnginePlugin):
|
|
"""Advanced personality analysis and adaptation engine"""
|
|
|
|
@property
|
|
def metadata(self) -> PluginMetadata:
|
|
return PluginMetadata(
|
|
name="personality_engine",
|
|
version="1.0.0",
|
|
description="Advanced personality analysis with Big Five model and adaptation",
|
|
author="Discord Quote Bot Team",
|
|
plugin_type=PluginType.PERSONALITY_ENGINE,
|
|
dependencies=["memory_system"],
|
|
permissions=["personality.analyze", "data.store"],
|
|
config_schema={
|
|
"min_interactions": {"type": "integer", "default": 10},
|
|
"confidence_threshold": {"type": "number", "default": 0.7},
|
|
},
|
|
)
|
|
|
|
async def on_initialize(self):
|
|
"""Initialize personality engine"""
|
|
logger.info("Initializing Personality Engine...")
|
|
|
|
self.min_interactions = self.config.get("min_interactions", 10)
|
|
self.confidence_threshold = self.config.get("confidence_threshold", 0.7)
|
|
|
|
# Storage
|
|
self.user_personalities: Dict[int, Dict[str, Any]] = {}
|
|
self.interaction_history: Dict[int, List[Dict[str, Any]]] = {}
|
|
|
|
# Big Five dimensions
|
|
self.personality_dimensions = {
|
|
"openness": "Openness to Experience",
|
|
"conscientiousness": "Conscientiousness",
|
|
"extraversion": "Extraversion",
|
|
"agreeableness": "Agreeableness",
|
|
"neuroticism": "Neuroticism",
|
|
}
|
|
|
|
# Communication patterns
|
|
self.communication_styles = {
|
|
"formal": ["please", "thank you", "would you"],
|
|
"casual": ["hey", "cool", "awesome"],
|
|
"technical": ["implementation", "algorithm", "function"],
|
|
"emotional": ["feel", "love", "excited", "worried"],
|
|
}
|
|
|
|
# Register events
|
|
self.register_event_handler("message_analyzed", self.handle_message_analysis)
|
|
|
|
logger.info("Personality Engine initialized")
|
|
|
|
async def analyze_personality(
|
|
self, user_id: int, interactions: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Analyze user personality from interactions"""
|
|
try:
|
|
if len(interactions) < self.min_interactions:
|
|
return {
|
|
"status": "insufficient_data",
|
|
"required": self.min_interactions,
|
|
"current": len(interactions),
|
|
"confidence": 0.0,
|
|
}
|
|
|
|
# Analyze personality dimensions
|
|
big_five = await self._analyze_big_five(interactions)
|
|
communication_style = self._analyze_communication_style(interactions)
|
|
emotional_profile = self._analyze_emotions(interactions)
|
|
|
|
# Calculate confidence
|
|
confidence = self._calculate_confidence(interactions, big_five)
|
|
|
|
# Generate summary
|
|
summary = self._generate_summary(big_five, communication_style)
|
|
|
|
profile = {
|
|
"user_id": user_id,
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"confidence": confidence,
|
|
"interactions_count": len(interactions),
|
|
"big_five": big_five,
|
|
"communication_style": communication_style,
|
|
"emotional_profile": emotional_profile,
|
|
"summary": summary,
|
|
"adaptation_prefs": self._get_adaptation_preferences(
|
|
big_five, communication_style
|
|
),
|
|
}
|
|
|
|
# Store profile
|
|
self.user_personalities[user_id] = profile
|
|
await self._store_profile(user_id, profile)
|
|
|
|
return profile
|
|
|
|
except Exception as e:
|
|
logger.error(f"Personality analysis error: {e}")
|
|
return {"status": "error", "error": str(e), "confidence": 0.0}
|
|
|
|
async def generate_personalized_response(self, user_id: int, context: str) -> str:
|
|
"""Generate response adapted to user personality"""
|
|
try:
|
|
profile = await self._get_profile(user_id)
|
|
|
|
if not profile or profile.get("confidence", 0) < self.confidence_threshold:
|
|
return await self._default_response(context)
|
|
|
|
# Get adaptation preferences
|
|
prefs = profile.get("adaptation_prefs", {})
|
|
big_five = profile.get("big_five", {})
|
|
|
|
# Ensure we have the correct types
|
|
if isinstance(prefs, dict) and isinstance(big_five, dict):
|
|
# Generate adapted response
|
|
return await self._generate_adapted_response(context, prefs, big_five)
|
|
else:
|
|
return await self._default_response(context)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Response generation error: {e}")
|
|
return await self._default_response(context)
|
|
|
|
async def handle_message_analysis(self, **kwargs):
|
|
"""Handle message analysis for learning"""
|
|
try:
|
|
user_id = kwargs.get("user_id")
|
|
message = kwargs.get("message", "")
|
|
sentiment = kwargs.get("sentiment", "neutral")
|
|
|
|
if not user_id or not message:
|
|
return
|
|
|
|
# Store interaction
|
|
interaction = {
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"message": message,
|
|
"sentiment": sentiment,
|
|
"length": len(message),
|
|
"type": self._classify_message_type(message),
|
|
}
|
|
|
|
if user_id not in self.interaction_history:
|
|
self.interaction_history[user_id] = []
|
|
|
|
self.interaction_history[user_id].append(interaction)
|
|
self.interaction_history[user_id] = self.interaction_history[user_id][
|
|
-500:
|
|
] # Keep recent
|
|
|
|
except Exception as e:
|
|
logger.error(f"Message analysis error: {e}")
|
|
|
|
async def _analyze_big_five(
|
|
self, interactions: List[Dict[str, Any]]
|
|
) -> Dict[str, float]:
|
|
"""Analyze Big Five personality traits"""
|
|
try:
|
|
texts = [str(i.get("message", "")) for i in interactions]
|
|
combined_text = " ".join(texts).lower()
|
|
|
|
# Keyword-based analysis (simplified)
|
|
keywords = {
|
|
"openness": ["creative", "new", "idea", "art", "different"],
|
|
"conscientiousness": [
|
|
"plan",
|
|
"organize",
|
|
"work",
|
|
"important",
|
|
"schedule",
|
|
],
|
|
"extraversion": ["people", "social", "party", "friends", "exciting"],
|
|
"agreeableness": ["help", "kind", "please", "thank", "nice"],
|
|
"neuroticism": ["worry", "stress", "anxious", "problem", "upset"],
|
|
}
|
|
|
|
scores = {}
|
|
for trait, words in keywords.items():
|
|
count = sum(1 for word in words if word in combined_text)
|
|
scores[trait] = min(count / 5.0, 1.0) if count > 0 else 0.5
|
|
|
|
return scores
|
|
|
|
except Exception as e:
|
|
logger.error(f"Big Five analysis error: {e}")
|
|
return {trait: 0.5 for trait in self.personality_dimensions.keys()}
|
|
|
|
def _analyze_communication_style(
|
|
self, interactions: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Analyze communication style"""
|
|
try:
|
|
style_scores = {}
|
|
total_words = 0
|
|
|
|
for style, keywords in self.communication_styles.items():
|
|
score = 0
|
|
for interaction in interactions:
|
|
message = str(interaction.get("message", "")).lower()
|
|
words = len(message.split())
|
|
total_words += words
|
|
|
|
keyword_count = sum(1 for keyword in keywords if keyword in message)
|
|
score += keyword_count
|
|
|
|
style_scores[style] = score
|
|
|
|
# Determine dominant style
|
|
dominant = (
|
|
max(style_scores, key=lambda x: style_scores[x])
|
|
if style_scores
|
|
else "neutral"
|
|
)
|
|
|
|
# Calculate average message length
|
|
avg_length = sum(
|
|
len(str(i.get("message", ""))) for i in interactions
|
|
) / len(interactions)
|
|
|
|
return {
|
|
"dominant_style": dominant,
|
|
"style_scores": style_scores,
|
|
"avg_message_length": avg_length,
|
|
"formality": (
|
|
"formal" if style_scores.get("formal", 0) > 2 else "casual"
|
|
),
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Communication style error: {e}")
|
|
return {"dominant_style": "neutral", "formality": "casual"}
|
|
|
|
def _analyze_emotions(self, interactions: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
"""Analyze emotional patterns"""
|
|
try:
|
|
emotions = [str(i.get("sentiment", "neutral")) for i in interactions]
|
|
|
|
# Count emotions
|
|
emotion_counts = {}
|
|
for emotion in emotions:
|
|
emotion_counts[emotion] = emotion_counts.get(emotion, 0) + 1
|
|
|
|
# Calculate distribution
|
|
total = len(emotions)
|
|
distribution = {e: count / total for e, count in emotion_counts.items()}
|
|
|
|
# Determine stability
|
|
stability = distribution.get("neutral", 0) + distribution.get("positive", 0)
|
|
|
|
return {
|
|
"dominant_emotion": max(
|
|
emotion_counts, key=lambda x: emotion_counts[x]
|
|
),
|
|
"distribution": distribution,
|
|
"stability": stability,
|
|
"variance": self._emotional_variance(emotions),
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Emotion analysis error: {e}")
|
|
return {"dominant_emotion": "neutral", "stability": 0.5}
|
|
|
|
def _classify_message_type(self, message: str) -> str:
|
|
"""Classify message type"""
|
|
message = message.lower().strip()
|
|
|
|
if message.endswith("?"):
|
|
return "question"
|
|
elif message.endswith("!"):
|
|
return "exclamation"
|
|
elif any(word in message for word in ["please", "can you", "could you"]):
|
|
return "request"
|
|
elif any(word in message for word in ["haha", "lol", "funny"]):
|
|
return "humor"
|
|
else:
|
|
return "statement"
|
|
|
|
def _emotional_variance(self, emotions: List[str]) -> float:
|
|
"""Calculate emotional variance"""
|
|
try:
|
|
scores = {"positive": 1.0, "neutral": 0.5, "negative": 0.0}
|
|
values = [scores.get(e, 0.5) for e in emotions]
|
|
|
|
if len(values) < 2:
|
|
return 0.0
|
|
|
|
mean = sum(values) / len(values)
|
|
variance = sum((v - mean) ** 2 for v in values) / len(values)
|
|
return variance
|
|
|
|
except Exception:
|
|
return 0.0
|
|
|
|
def _calculate_confidence(
|
|
self, interactions: List[Dict[str, Any]], big_five: Dict[str, float]
|
|
) -> float:
|
|
"""Calculate analysis confidence"""
|
|
try:
|
|
# More interactions = higher confidence
|
|
interaction_factor = min(len(interactions) / 50.0, 1.0)
|
|
|
|
# Less extreme scores = higher confidence
|
|
variance_factor = 1.0 - (max(big_five.values()) - min(big_five.values()))
|
|
|
|
return (interaction_factor + variance_factor) / 2.0
|
|
|
|
except Exception:
|
|
return 0.5
|
|
|
|
def _generate_summary(
|
|
self, big_five: Dict[str, float], comm_style: Dict[str, Any]
|
|
) -> str:
|
|
"""Generate personality summary"""
|
|
try:
|
|
dominant_trait = max(big_five, key=lambda x: big_five[x])
|
|
style = comm_style.get("dominant_style", "neutral")
|
|
|
|
trait_descriptions = {
|
|
"openness": "creative and open to new experiences",
|
|
"conscientiousness": "organized and reliable",
|
|
"extraversion": "social and energetic",
|
|
"agreeableness": "cooperative and trusting",
|
|
"neuroticism": "emotionally sensitive",
|
|
}
|
|
|
|
description = trait_descriptions.get(dominant_trait, "balanced")
|
|
return f"User appears {description} with a {style} communication style."
|
|
|
|
except Exception:
|
|
return "Personality analysis in progress."
|
|
|
|
def _get_adaptation_preferences(
|
|
self, big_five: Dict[str, float], comm_style: Dict[str, Any]
|
|
) -> Dict[str, str]:
|
|
"""Determine adaptation preferences"""
|
|
try:
|
|
prefs = {}
|
|
|
|
# Response length
|
|
avg_length = comm_style.get("avg_message_length", 100)
|
|
if isinstance(avg_length, (int, float)) and avg_length > 150:
|
|
prefs["length"] = "detailed"
|
|
elif isinstance(avg_length, (int, float)) and avg_length < 50:
|
|
prefs["length"] = "brief"
|
|
else:
|
|
prefs["length"] = "moderate"
|
|
|
|
# Formality
|
|
prefs["formality"] = comm_style.get("formality", "casual")
|
|
|
|
# Detail level
|
|
if big_five.get("openness", 0.5) > 0.7:
|
|
prefs["detail"] = "high"
|
|
elif big_five.get("conscientiousness", 0.5) > 0.7:
|
|
prefs["detail"] = "structured"
|
|
else:
|
|
prefs["detail"] = "moderate"
|
|
|
|
return prefs
|
|
|
|
except Exception:
|
|
return {"length": "moderate", "formality": "casual", "detail": "moderate"}
|
|
|
|
async def _generate_adapted_response(
|
|
self, context: str, prefs: Dict[str, str], big_five: Dict[str, float]
|
|
) -> str:
|
|
"""Generate personality-adapted response"""
|
|
try:
|
|
# Build adaptation instructions
|
|
instructions = []
|
|
|
|
if prefs.get("length") == "brief":
|
|
instructions.append("Keep response concise")
|
|
elif prefs.get("length") == "detailed":
|
|
instructions.append("Provide detailed explanation")
|
|
|
|
if prefs.get("formality") == "formal":
|
|
instructions.append("Use formal language")
|
|
else:
|
|
instructions.append("Use casual, friendly language")
|
|
|
|
# Create prompt
|
|
adaptation_prompt = f"""
|
|
Respond to: "{context}"
|
|
|
|
Guidelines: {', '.join(instructions)}
|
|
User traits: Openness={big_five.get('openness', 0.5):.1f},
|
|
Conscientiousness={big_five.get('conscientiousness', 0.5):.1f}
|
|
"""
|
|
|
|
result = await self.ai_manager.generate_text(
|
|
adaptation_prompt, provider="openai", model="gpt-4", max_tokens=300
|
|
)
|
|
|
|
return result.get("content", "I understand what you mean.")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Adapted response error: {e}")
|
|
return await self._default_response(context)
|
|
|
|
async def _default_response(self, context: str) -> str:
|
|
"""Generate default response"""
|
|
try:
|
|
prompt = f"Generate a helpful response to: {context}"
|
|
|
|
result = await self.ai_manager.generate_text(
|
|
prompt, provider="openai", model="gpt-3.5-turbo", max_tokens=150
|
|
)
|
|
|
|
return result.get("content", "That's interesting! Tell me more.")
|
|
|
|
except Exception:
|
|
return "I understand. How can I help you further?"
|
|
|
|
async def _get_profile(self, user_id: int) -> Dict[str, Any] | None:
|
|
"""Get personality profile"""
|
|
try:
|
|
# Check cache
|
|
if user_id in self.user_personalities:
|
|
return self.user_personalities[user_id]
|
|
|
|
# Load from memory
|
|
if self.memory_manager:
|
|
profile = await self.memory_manager.get_personality_profile(user_id)
|
|
if profile:
|
|
self.user_personalities[user_id] = profile
|
|
return profile
|
|
|
|
return None
|
|
|
|
except Exception:
|
|
return None
|
|
|
|
async def _store_profile(self, user_id: int, profile: Dict[str, Any]):
|
|
"""Store personality profile"""
|
|
try:
|
|
if self.memory_manager:
|
|
await self.memory_manager.store_personality_profile(user_id, profile)
|
|
except Exception as e:
|
|
logger.error(f"Profile storage error: {e}")
|
|
|
|
|
|
# Plugin entry point
|
|
main = AdvancedPersonalityEngine
|