added technical documentation

This commit is contained in:
2026-01-13 17:20:52 +00:00
parent ff394c9250
commit b29822efc7
12 changed files with 4953 additions and 1 deletions

View File

@@ -0,0 +1,418 @@
# 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)