# Opinion System Deep Dive The opinion system allows the bot to develop and express opinions on topics it discusses with users. ## Overview The bot forms opinions through repeated discussions: ``` ┌──────────────────────────────────────────────────────────────────────────────┐ │ Opinion Formation │ └──────────────────────────────────────────────────────────────────────────────┘ Discussion 1: "I love gaming!" → gaming: sentiment +0.8 Discussion 2: "Games are so fun!" → gaming: sentiment +0.7 (weighted avg) Discussion 3: "I beat the boss!" → gaming: sentiment +0.6, interest +0.8 After 3+ discussions → Opinion formed! "You really enjoy discussing gaming" ``` --- ## Opinion Attributes Each opinion tracks: | Attribute | Type | Range | Description | |-----------|------|-------|-------------| | `topic` | string | - | The topic (lowercase) | | `sentiment` | float | -1 to +1 | How positive/negative the bot feels | | `interest_level` | float | 0 to 1 | How engaged/interested | | `discussion_count` | int | 0+ | How often discussed | | `reasoning` | string | - | AI-generated explanation (optional) | | `last_reinforced_at` | datetime | - | When last discussed | ### Sentiment Interpretation | Range | Interpretation | Prompt Modifier | |-------|----------------|-----------------| | > 0.5 | Really enjoys | "You really enjoy discussing {topic}" | | 0.2 to 0.5 | Finds interesting | "You find {topic} interesting" | | -0.3 to 0.2 | Neutral | (no modifier) | | < -0.3 | Not enthusiastic | "You're not particularly enthusiastic about {topic}" | ### Interest Level Interpretation | Range | Interpretation | |-------|----------------| | 0.8 - 1.0 | Very engaged when discussing | | 0.5 - 0.8 | Moderately interested | | 0.2 - 0.5 | Somewhat interested | | 0.0 - 0.2 | Not very interested | --- ## Opinion Updates When a topic is discussed, the opinion is updated using weighted averaging: ```python weight = 0.2 # 20% weight to new data new_sentiment = (old_sentiment * 0.8) + (discussion_sentiment * 0.2) new_interest = (old_interest * 0.8) + (engagement_level * 0.2) ``` ### Why 20% Weight? - Prevents single interactions from dominating - Opinions evolve gradually over time - Reflects how real opinions form - Protects against manipulation ### Example Evolution ``` Initial: sentiment = 0.0, interest = 0.5 Discussion 1 (sentiment=0.8, engagement=0.7): sentiment = 0.0 * 0.8 + 0.8 * 0.2 = 0.16 interest = 0.5 * 0.8 + 0.7 * 0.2 = 0.54 Discussion 2 (sentiment=0.6, engagement=0.9): sentiment = 0.16 * 0.8 + 0.6 * 0.2 = 0.248 interest = 0.54 * 0.8 + 0.9 * 0.2 = 0.612 Discussion 3 (sentiment=0.7, engagement=0.8): sentiment = 0.248 * 0.8 + 0.7 * 0.2 = 0.338 interest = 0.612 * 0.8 + 0.8 * 0.2 = 0.65 After 3 discussions: sentiment=0.34, interest=0.65 → "You find programming interesting" ``` --- ## Topic Detection Topics are extracted from messages using keyword matching: ### Topic Categories ```python topic_keywords = { # Hobbies "gaming": ["game", "gaming", "video game", "play", "xbox", "playstation", ...], "music": ["music", "song", "band", "album", "concert", "spotify", ...], "movies": ["movie", "film", "cinema", "netflix", "show", "series", ...], "reading": ["book", "read", "novel", "author", "library", "kindle"], "sports": ["sports", "football", "soccer", "basketball", "gym", ...], "cooking": ["cook", "recipe", "food", "restaurant", "meal", ...], "travel": ["travel", "trip", "vacation", "flight", "hotel", ...], "art": ["art", "painting", "drawing", "museum", "gallery", ...], # Tech "programming": ["code", "programming", "developer", "software", ...], "technology": ["tech", "computer", "phone", "app", "website", ...], "ai": ["ai", "artificial intelligence", "machine learning", ...], # Life "work": ["work", "job", "office", "career", "boss", "meeting"], "family": ["family", "parents", "mom", "dad", "brother", "sister", ...], "pets": ["pet", "dog", "cat", "puppy", "kitten", "animal"], "health": ["health", "doctor", "exercise", "diet", "sleep", ...], # Interests "philosophy": ["philosophy", "meaning", "life", "existence", ...], "science": ["science", "research", "study", "experiment", ...], "nature": ["nature", "outdoor", "hiking", "camping", "mountain", ...], } ``` ### Detection Function ```python def extract_topics_from_message(message: str) -> list[str]: message_lower = message.lower() found_topics = [] for topic, keywords in topic_keywords.items(): for keyword in keywords: if keyword in message_lower: if topic not in found_topics: found_topics.append(topic) break return found_topics ``` ### Example **Message:** "I've been playing this new video game all weekend!" **Detected Topics:** `["gaming"]` --- ## Opinion Requirements Opinions are only considered "formed" after 3+ discussions: ```python async def get_top_interests(guild_id, limit=5): return select(BotOpinion).where( BotOpinion.guild_id == guild_id, BotOpinion.discussion_count >= 3, # Minimum threshold ).order_by(...) ``` **Why 3 discussions?** - Single mentions don't indicate sustained interest - Prevents volatile opinion formation - Ensures meaningful opinions - Reflects genuine engagement with topic --- ## Prompt Modifiers Relevant opinions are included in the AI's system prompt: ```python def get_opinion_prompt_modifier(opinions: list[BotOpinion]) -> str: parts = [] for op in opinions[:3]: # Max 3 opinions if op.sentiment > 0.5: parts.append(f"You really enjoy discussing {op.topic}") elif op.sentiment > 0.2: parts.append(f"You find {op.topic} interesting") elif op.sentiment < -0.3: parts.append(f"You're not particularly enthusiastic about {op.topic}") if op.reasoning: parts.append(f"({op.reasoning})") return "; ".join(parts) ``` ### Example Output ``` You really enjoy discussing programming; You find gaming interesting; You're not particularly enthusiastic about politics (You prefer to focus on fun and creative topics). ``` --- ## Opinion Reasoning Optionally, AI can generate reasoning for opinions: ```python await opinion_service.set_opinion_reasoning( topic="programming", guild_id=123, reasoning="It's fascinating to help people create things" ) ``` This adds context when the opinion is mentioned in prompts. --- ## Database Schema ### BotOpinion Table | Column | Type | Description | |--------|------|-------------| | `id` | Integer | Primary key | | `guild_id` | BigInteger | Guild ID (nullable for global) | | `topic` | String | Topic name (lowercase) | | `sentiment` | Float | -1 to +1 sentiment | | `interest_level` | Float | 0 to 1 interest | | `discussion_count` | Integer | Number of discussions | | `reasoning` | String | AI explanation (optional) | | `formed_at` | DateTime | When first discussed | | `last_reinforced_at` | DateTime | When last discussed | ### Unique Constraint Each `(guild_id, topic)` combination is unique. --- ## API Reference ### OpinionService ```python class OpinionService: def __init__(self, session: AsyncSession) async def get_opinion( self, topic: str, guild_id: int | None = None ) -> BotOpinion | None async def get_or_create_opinion( self, topic: str, guild_id: int | None = None ) -> BotOpinion async def record_topic_discussion( self, topic: str, guild_id: int | None, sentiment: float, engagement_level: float, ) -> BotOpinion async def set_opinion_reasoning( self, topic: str, guild_id: int | None, reasoning: str ) -> None async def get_top_interests( self, guild_id: int | None = None, limit: int = 5 ) -> list[BotOpinion] async def get_relevant_opinions( self, topics: list[str], guild_id: int | None = None ) -> list[BotOpinion] def get_opinion_prompt_modifier( self, opinions: list[BotOpinion] ) -> str async def get_all_opinions( self, guild_id: int | None = None ) -> list[BotOpinion] ``` ### Helper Function ```python def extract_topics_from_message(message: str) -> list[str] ``` --- ## Configuration | Variable | Default | Description | |----------|---------|-------------| | `OPINION_FORMATION_ENABLED` | `true` | Enable/disable opinion system | --- ## Example Usage ```python from daemon_boyfriend.services.opinion_service import ( OpinionService, extract_topics_from_message ) async with get_session() as session: opinion_service = OpinionService(session) # Extract topics from message message = "I've been coding a lot in Python lately!" topics = extract_topics_from_message(message) # topics = ["programming"] # Record discussion with positive sentiment for topic in topics: await opinion_service.record_topic_discussion( topic=topic, guild_id=123, sentiment=0.7, engagement_level=0.8 ) # Get top interests for prompt building interests = await opinion_service.get_top_interests(guild_id=123) print("Bot's top interests:") for interest in interests: print(f" {interest.topic}: sentiment={interest.sentiment:.2f}") # Get opinions relevant to current conversation relevant = await opinion_service.get_relevant_opinions( topics=["programming", "gaming"], guild_id=123 ) modifier = opinion_service.get_opinion_prompt_modifier(relevant) print(f"Prompt modifier: {modifier}") await session.commit() ``` --- ## Use in Conversation Flow ``` 1. User sends message │ ▼ 2. Extract topics from message topics = extract_topics_from_message(message) │ ▼ 3. Get relevant opinions opinions = await opinion_service.get_relevant_opinions(topics, guild_id) │ ▼ 4. Add to system prompt prompt += opinion_service.get_opinion_prompt_modifier(opinions) │ ▼ 5. Generate response │ ▼ 6. Record topic discussions (post-response) for topic in topics: await opinion_service.record_topic_discussion( topic, guild_id, sentiment, engagement ) ``` --- ## Design Considerations ### Why Per-Guild? - Different server contexts may lead to different opinions - Gaming server → gaming-positive opinions - Work server → professional topic preferences - Allows customization per community ### Why Keyword-Based Detection? - Fast and simple - No additional AI API calls - Predictable behavior - Easy to extend with more keywords ### Future Improvements For production, consider: - NLP-based topic extraction - LLM topic detection for nuanced topics - Hierarchical topic relationships - Opinion decay over time (for less-discussed topics)