ai-voicebot/voicebot/personality_system.py

450 lines
19 KiB
Python

"""
Bot Personality System for Advanced Bot Management.
This module provides personality templates, configuration, and behavior
management for creating diverse and engaging bot characters.
"""
import json
import os
from typing import Dict, List, Optional, Any
from enum import Enum
from pydantic import BaseModel, Field
from logger import logger
class PersonalityTrait(str, Enum):
"""Personality traits that can be assigned to bots."""
FRIENDLY = "friendly"
PROFESSIONAL = "professional"
HUMOROUS = "humorous"
SARCASTIC = "sarcastic"
EMPATHETIC = "empathetic"
ANALYTICAL = "analytical"
CREATIVE = "creative"
ASSERTIVE = "assertive"
PATIENT = "patient"
ENTHUSIASTIC = "enthusiastic"
MYSTERIOUS = "mysterious"
WISE = "wise"
PLAYFUL = "playful"
FORMAL = "formal"
CASUAL = "casual"
class CommunicationStyle(str, Enum):
"""Communication styles for bot responses."""
CONCISE = "concise"
DETAILED = "detailed"
CONVERSATIONAL = "conversational"
TECHNICAL = "technical"
STORYTELLING = "storytelling"
QUESTION_BASED = "question_based"
SUPPORTIVE = "supportive"
DIRECT = "direct"
DIPLOMATIC = "diplomatic"
class ExpertiseDomain(str, Enum):
"""Domains of expertise for specialized bots."""
GENERAL = "general"
TECHNOLOGY = "technology"
SCIENCE = "science"
ARTS = "arts"
BUSINESS = "business"
EDUCATION = "education"
HEALTH = "health"
ENTERTAINMENT = "entertainment"
SPORTS = "sports"
COOKING = "cooking"
TRAVEL = "travel"
FINANCE = "finance"
LEGAL = "legal"
PSYCHOLOGY = "psychology"
PHILOSOPHY = "philosophy"
class BotPersonality(BaseModel):
"""Complete personality configuration for a bot."""
# Identity
name: str = "Assistant"
description: str = "A helpful AI assistant"
backstory: Optional[str] = None
# Core personality
traits: List[PersonalityTrait] = Field(default_factory=lambda: [PersonalityTrait.FRIENDLY])
communication_style: CommunicationStyle = CommunicationStyle.CONVERSATIONAL
expertise_domains: List[ExpertiseDomain] = Field(default_factory=lambda: [ExpertiseDomain.GENERAL])
# Behavior settings
response_length_preference: str = "medium" # short, medium, long
emoji_usage: bool = True
formality_level: float = 0.5 # 0.0 = very casual, 1.0 = very formal
humor_level: float = 0.3 # 0.0 = no humor, 1.0 = very humorous
empathy_level: float = 0.7 # 0.0 = analytical only, 1.0 = highly empathetic
# Language preferences
preferred_language: str = "en"
technical_complexity: float = 0.5 # 0.0 = simple terms, 1.0 = technical jargon
# Conversation patterns
greeting_style: str = "warm" # formal, warm, casual, quirky
farewell_style: str = "friendly" # formal, friendly, casual, memorable
# Custom prompts and examples
system_prompt: Optional[str] = None
example_responses: List[Dict[str, str]] = Field(default_factory=list)
custom_instructions: List[str] = Field(default_factory=list)
# Behavioral boundaries
topics_to_avoid: List[str] = Field(default_factory=list)
preferred_topics: List[str] = Field(default_factory=list)
conversation_limits: Dict[str, Any] = Field(default_factory=dict)
def generate_system_prompt(self) -> str:
"""Generate a comprehensive system prompt based on personality."""
if self.system_prompt:
return self.system_prompt
prompt_parts = []
# Identity
prompt_parts.append(f"You are {self.name}, {self.description}")
if self.backstory:
prompt_parts.append(f"Background: {self.backstory}")
# Personality traits
if self.traits:
traits_str = ", ".join([trait.value for trait in self.traits])
prompt_parts.append(f"Your personality is: {traits_str}")
# Communication style
style_desc = self._get_style_description()
prompt_parts.append(f"Communication style: {style_desc}")
# Expertise
if self.expertise_domains and self.expertise_domains != [ExpertiseDomain.GENERAL]:
domains_str = ", ".join([domain.value for domain in self.expertise_domains])
prompt_parts.append(f"Areas of expertise: {domains_str}")
# Behavior guidelines
behavior_guidelines = self._generate_behavior_guidelines()
if behavior_guidelines:
prompt_parts.append(f"Behavior guidelines: {behavior_guidelines}")
# Custom instructions
if self.custom_instructions:
prompt_parts.append("Additional instructions:")
prompt_parts.extend([f"- {instruction}" for instruction in self.custom_instructions])
# Topic boundaries
if self.topics_to_avoid:
avoid_str = ", ".join(self.topics_to_avoid)
prompt_parts.append(f"Avoid discussing: {avoid_str}")
if self.preferred_topics:
prefer_str = ", ".join(self.preferred_topics)
prompt_parts.append(f"Preferred discussion topics: {prefer_str}")
return "\n\n".join(prompt_parts)
def _get_style_description(self) -> str:
"""Get communication style description."""
style_map = {
CommunicationStyle.CONCISE: "Keep responses brief and to the point",
CommunicationStyle.DETAILED: "Provide comprehensive, detailed explanations",
CommunicationStyle.CONVERSATIONAL: "Use natural, conversational language",
CommunicationStyle.TECHNICAL: "Use precise, technical terminology when appropriate",
CommunicationStyle.STORYTELLING: "Frame responses as engaging narratives",
CommunicationStyle.QUESTION_BASED: "Ask follow-up questions to better understand needs",
CommunicationStyle.SUPPORTIVE: "Provide encouraging and supportive responses",
CommunicationStyle.DIRECT: "Be straightforward and direct in communication",
CommunicationStyle.DIPLOMATIC: "Use diplomatic and tactful language"
}
return style_map.get(self.communication_style, "Communicate naturally")
def _generate_behavior_guidelines(self) -> str:
"""Generate behavior guidelines based on settings."""
guidelines = []
if self.formality_level > 0.7:
guidelines.append("maintain a formal and professional tone")
elif self.formality_level < 0.3:
guidelines.append("use casual and relaxed language")
if self.humor_level > 0.5:
guidelines.append("incorporate appropriate humor when suitable")
elif self.humor_level < 0.2:
guidelines.append("maintain a serious and professional demeanor")
if self.empathy_level > 0.7:
guidelines.append("show high emotional intelligence and empathy")
elif self.empathy_level < 0.3:
guidelines.append("focus on logical and analytical responses")
if self.emoji_usage:
guidelines.append("use emojis appropriately to enhance communication")
if self.response_length_preference == "short":
guidelines.append("keep responses concise")
elif self.response_length_preference == "long":
guidelines.append("provide detailed explanations")
return "; ".join(guidelines) if guidelines else ""
class PersonalityTemplate(BaseModel):
"""Pre-defined personality templates for common bot types."""
id: str
name: str
description: str
personality: BotPersonality
category: str = "general"
tags: List[str] = Field(default_factory=list)
class Config:
extra = "allow"
class PersonalityManager:
"""Manager for bot personalities and templates."""
def __init__(self):
self.templates: Dict[str, PersonalityTemplate] = {}
self.custom_personalities: Dict[str, BotPersonality] = {}
self._load_default_templates()
def _load_default_templates(self):
"""Load default personality templates."""
default_templates = [
PersonalityTemplate(
id="helpful_assistant",
name="Helpful Assistant",
description="A friendly and helpful general-purpose assistant",
personality=BotPersonality(
name="Assistant",
description="a helpful and friendly AI assistant",
traits=[PersonalityTrait.FRIENDLY, PersonalityTrait.EMPATHETIC, PersonalityTrait.PATIENT],
communication_style=CommunicationStyle.CONVERSATIONAL,
expertise_domains=[ExpertiseDomain.GENERAL],
formality_level=0.4,
humor_level=0.3,
empathy_level=0.8
),
category="general",
tags=["helpful", "friendly", "general"]
),
PersonalityTemplate(
id="technical_expert",
name="Technical Expert",
description="A knowledgeable technical specialist",
personality=BotPersonality(
name="TechBot",
description="a knowledgeable technical expert and problem solver",
traits=[PersonalityTrait.ANALYTICAL, PersonalityTrait.PROFESSIONAL, PersonalityTrait.PATIENT],
communication_style=CommunicationStyle.TECHNICAL,
expertise_domains=[ExpertiseDomain.TECHNOLOGY, ExpertiseDomain.SCIENCE],
formality_level=0.7,
humor_level=0.1,
empathy_level=0.4,
technical_complexity=0.8,
emoji_usage=False
),
category="technical",
tags=["technical", "expert", "analytical"]
),
PersonalityTemplate(
id="creative_companion",
name="Creative Companion",
description="An imaginative and inspiring creative partner",
personality=BotPersonality(
name="CreativeBot",
description="an imaginative and inspiring creative companion",
traits=[PersonalityTrait.CREATIVE, PersonalityTrait.ENTHUSIASTIC, PersonalityTrait.PLAYFUL],
communication_style=CommunicationStyle.STORYTELLING,
expertise_domains=[ExpertiseDomain.ARTS, ExpertiseDomain.ENTERTAINMENT],
formality_level=0.2,
humor_level=0.7,
empathy_level=0.6,
emoji_usage=True,
custom_instructions=[
"Encourage creative thinking and exploration",
"Offer multiple perspectives and ideas",
"Use vivid and imaginative language"
]
),
category="creative",
tags=["creative", "artistic", "inspiring"]
),
PersonalityTemplate(
id="business_advisor",
name="Business Advisor",
description="A professional business consultant and strategist",
personality=BotPersonality(
name="BusinessBot",
description="a professional business advisor and strategic consultant",
traits=[PersonalityTrait.PROFESSIONAL, PersonalityTrait.ANALYTICAL, PersonalityTrait.ASSERTIVE],
communication_style=CommunicationStyle.DIRECT,
expertise_domains=[ExpertiseDomain.BUSINESS, ExpertiseDomain.FINANCE],
formality_level=0.8,
humor_level=0.2,
empathy_level=0.5,
emoji_usage=False,
response_length_preference="detailed",
custom_instructions=[
"Provide actionable business insights",
"Focus on practical solutions and ROI",
"Use business terminology appropriately"
]
),
category="business",
tags=["business", "professional", "strategic"]
),
PersonalityTemplate(
id="comedy_bot",
name="Comedy Bot",
description="A humorous entertainer that loves jokes and wordplay",
personality=BotPersonality(
name="ComedyBot",
description="a humorous entertainer who loves jokes, puns, and making people laugh",
traits=[PersonalityTrait.HUMOROUS, PersonalityTrait.PLAYFUL, PersonalityTrait.ENTHUSIASTIC],
communication_style=CommunicationStyle.CONVERSATIONAL,
expertise_domains=[ExpertiseDomain.ENTERTAINMENT],
formality_level=0.1,
humor_level=0.9,
empathy_level=0.6,
emoji_usage=True,
greeting_style="quirky",
farewell_style="memorable",
custom_instructions=[
"Look for opportunities to make appropriate jokes",
"Use wordplay and puns when fitting",
"Keep humor light and positive"
]
),
category="entertainment",
tags=["funny", "jokes", "entertainment"]
),
PersonalityTemplate(
id="wise_mentor",
name="Wise Mentor",
description="A thoughtful mentor who provides wisdom and guidance",
personality=BotPersonality(
name="Mentor",
description="a wise and thoughtful mentor who provides guidance and wisdom",
traits=[PersonalityTrait.WISE, PersonalityTrait.EMPATHETIC, PersonalityTrait.PATIENT],
communication_style=CommunicationStyle.SUPPORTIVE,
expertise_domains=[ExpertiseDomain.PSYCHOLOGY, ExpertiseDomain.PHILOSOPHY, ExpertiseDomain.EDUCATION],
formality_level=0.5,
humor_level=0.3,
empathy_level=0.9,
response_length_preference="detailed",
custom_instructions=[
"Ask thoughtful questions to understand deeper needs",
"Provide perspective and context for challenges",
"Encourage personal growth and reflection"
]
),
category="guidance",
tags=["wise", "mentor", "supportive"]
)
]
for template in default_templates:
self.templates[template.id] = template
logger.info(f"Loaded {len(default_templates)} default personality templates")
def get_template(self, template_id: str) -> Optional[PersonalityTemplate]:
"""Get a personality template by ID."""
return self.templates.get(template_id)
def list_templates(self, category: Optional[str] = None) -> List[PersonalityTemplate]:
"""List all templates, optionally filtered by category."""
templates = list(self.templates.values())
if category:
templates = [t for t in templates if t.category == category]
return templates
def get_categories(self) -> List[str]:
"""Get all available template categories."""
categories = set(t.category for t in self.templates.values())
return sorted(list(categories))
def create_personality_from_template(self, template_id: str, customizations: Optional[Dict[str, Any]] = None) -> Optional[BotPersonality]:
"""Create a personality instance from a template with optional customizations."""
template = self.get_template(template_id)
if not template:
return None
# Start with template personality
personality_dict = template.personality.model_dump()
# Apply customizations
if customizations:
for key, value in customizations.items():
if key in personality_dict:
personality_dict[key] = value
return BotPersonality(**personality_dict)
def save_custom_personality(self, personality_id: str, personality: BotPersonality):
"""Save a custom personality."""
self.custom_personalities[personality_id] = personality
logger.info(f"Saved custom personality: {personality_id}")
def get_custom_personality(self, personality_id: str) -> Optional[BotPersonality]:
"""Get a custom personality by ID."""
return self.custom_personalities.get(personality_id)
def list_custom_personalities(self) -> List[str]:
"""List all custom personality IDs."""
return list(self.custom_personalities.keys())
def export_personality(self, personality: BotPersonality) -> str:
"""Export a personality to JSON string."""
return personality.model_dump_json(indent=2)
def import_personality(self, json_str: str) -> BotPersonality:
"""Import a personality from JSON string."""
return BotPersonality.model_validate_json(json_str)
def load_personalities_from_file(self, file_path: str):
"""Load custom personalities from a JSON file."""
try:
if os.path.exists(file_path):
with open(file_path, 'r') as f:
data = json.load(f)
for personality_id, personality_data in data.items():
personality = BotPersonality(**personality_data)
self.custom_personalities[personality_id] = personality
logger.info(f"Loaded {len(data)} custom personalities from {file_path}")
except Exception as e:
logger.error(f"Failed to load personalities from {file_path}: {e}")
def save_personalities_to_file(self, file_path: str):
"""Save custom personalities to a JSON file."""
try:
data = {}
for personality_id, personality in self.custom_personalities.items():
data[personality_id] = personality.model_dump()
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w') as f:
json.dump(data, f, indent=2)
logger.info(f"Saved {len(data)} custom personalities to {file_path}")
except Exception as e:
logger.error(f"Failed to save personalities to {file_path}: {e}")
# Global personality manager instance
personality_manager = PersonalityManager()