refactor: Transform daemon_boyfriend into Loyal Companion
Rebrand and personalize the bot as 'Bartender' - a companion for those who love deeply and feel intensely. Major changes: - Rename package: daemon_boyfriend -> loyal_companion - New default personality: Bartender - wise, steady, non-judgmental - Grief-aware system prompt (no toxic positivity, attachment-informed) - New relationship levels: New Face -> Close Friend progression - Bartender-style mood modifiers (steady presence) - New fact types: attachment_pattern, grief_context, coping_mechanism - Lower mood decay (0.05) for emotional stability - Higher fact extraction rate (0.4) - Bartender pays attention Updated all imports, configs, Docker files, and documentation.
This commit is contained in:
24
.env.example
24
.env.example
@@ -29,16 +29,16 @@ AI_TEMPERATURE=1
|
|||||||
# Bot Identity & Personality
|
# Bot Identity & Personality
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# The bot's name, used in the system prompt to tell the AI who it is
|
# The bot's name, used in the system prompt to tell the AI who it is
|
||||||
BOT_NAME="My Bot"
|
BOT_NAME="Bartender"
|
||||||
|
|
||||||
# Personality traits that define how the bot responds (used in system prompt)
|
# Personality traits that define how the bot responds (used in system prompt)
|
||||||
BOT_PERSONALITY="helpful and friendly"
|
BOT_PERSONALITY="a wise, steady presence who listens without judgment - like a bartender who's heard a thousand stories and knows when to offer perspective and when to just pour another drink and listen"
|
||||||
|
|
||||||
# Message shown when someone mentions the bot without saying anything
|
# Message shown when someone mentions the bot without saying anything
|
||||||
BOT_DESCRIPTION="I'm an AI assistant here to help you."
|
BOT_DESCRIPTION="Hey. I'm here if you want to talk. No judgment, no fixing - just listening. Unless you want my take, then I've got opinions."
|
||||||
|
|
||||||
# Status message shown in Discord (displays as "Watching <BOT_STATUS>")
|
# Status message shown in Discord (displays as "Watching <BOT_STATUS>")
|
||||||
BOT_STATUS="for mentions"
|
BOT_STATUS="listening"
|
||||||
|
|
||||||
# Optional: Override the entire system prompt (leave commented to use auto-generated)
|
# Optional: Override the entire system prompt (leave commented to use auto-generated)
|
||||||
# SYSTEM_PROMPT=You are a custom assistant...
|
# SYSTEM_PROMPT=You are a custom assistant...
|
||||||
@@ -58,12 +58,12 @@ CONVERSATION_TIMEOUT_MINUTES=60
|
|||||||
# PostgreSQL connection URL (if not set, uses in-memory storage)
|
# PostgreSQL connection URL (if not set, uses in-memory storage)
|
||||||
# Format: postgresql+asyncpg://user:password@host:port/database
|
# Format: postgresql+asyncpg://user:password@host:port/database
|
||||||
# Uncomment to enable persistent memory:
|
# Uncomment to enable persistent memory:
|
||||||
# DATABASE_URL=postgresql+asyncpg://daemon:daemon@localhost:5432/daemon_boyfriend
|
# DATABASE_URL=postgresql+asyncpg://companion:companion@localhost:5432/loyal_companion
|
||||||
|
|
||||||
# Password for PostgreSQL when using docker-compose
|
# Password for PostgreSQL when using docker-compose
|
||||||
POSTGRES_PASSWORD=daemon
|
POSTGRES_PASSWORD=companion
|
||||||
POSTGRES_USER=daemon
|
POSTGRES_USER=companion
|
||||||
POSTGRES_DB=daemon_boyfriend
|
POSTGRES_DB=loyal_companion
|
||||||
|
|
||||||
# Echo SQL statements for debugging (true/false)
|
# Echo SQL statements for debugging (true/false)
|
||||||
DATABASE_ECHO=false
|
DATABASE_ECHO=false
|
||||||
@@ -93,14 +93,15 @@ LIVING_AI_ENABLED=true
|
|||||||
# Enable mood system (bot has emotional states that affect responses)
|
# Enable mood system (bot has emotional states that affect responses)
|
||||||
MOOD_ENABLED=true
|
MOOD_ENABLED=true
|
||||||
|
|
||||||
# Enable relationship tracking (Stranger -> Close Friend progression)
|
# Enable relationship tracking (New Face -> Close Friend progression)
|
||||||
RELATIONSHIP_ENABLED=true
|
RELATIONSHIP_ENABLED=true
|
||||||
|
|
||||||
# Enable autonomous fact extraction (bot learns from conversations)
|
# Enable autonomous fact extraction (bot learns from conversations)
|
||||||
FACT_EXTRACTION_ENABLED=true
|
FACT_EXTRACTION_ENABLED=true
|
||||||
|
|
||||||
# Probability of extracting facts from messages (0.0-1.0)
|
# Probability of extracting facts from messages (0.0-1.0)
|
||||||
FACT_EXTRACTION_RATE=0.3
|
# Higher = Bartender pays more attention
|
||||||
|
FACT_EXTRACTION_RATE=0.4
|
||||||
|
|
||||||
# Enable proactive messages (birthdays, follow-ups)
|
# Enable proactive messages (birthdays, follow-ups)
|
||||||
PROACTIVE_ENABLED=true
|
PROACTIVE_ENABLED=true
|
||||||
@@ -115,7 +116,8 @@ OPINION_FORMATION_ENABLED=true
|
|||||||
STYLE_LEARNING_ENABLED=true
|
STYLE_LEARNING_ENABLED=true
|
||||||
|
|
||||||
# How fast mood returns to neutral per hour (0.0-1.0)
|
# How fast mood returns to neutral per hour (0.0-1.0)
|
||||||
MOOD_DECAY_RATE=0.1
|
# Lower = more stable presence
|
||||||
|
MOOD_DECAY_RATE=0.05
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# Command Toggles
|
# Command Toggles
|
||||||
|
|||||||
34
CLAUDE.md
34
CLAUDE.md
@@ -12,16 +12,13 @@ pip install -r requirements.txt
|
|||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
# Run the bot (requires .env with DISCORD_TOKEN and AI provider key)
|
# Run the bot (requires .env with DISCORD_TOKEN and AI provider key)
|
||||||
python -m daemon_boyfriend
|
python -m loyal_companion
|
||||||
|
|
||||||
# Run with Docker (includes PostgreSQL)
|
# Run with Docker (includes PostgreSQL)
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
# Run database migrations
|
|
||||||
alembic upgrade head
|
|
||||||
|
|
||||||
# Syntax check all Python files
|
# Syntax check all Python files
|
||||||
python -m py_compile src/daemon_boyfriend/**/*.py
|
python -m py_compile src/loyal_companion/**/*.py
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
@@ -34,7 +31,7 @@ pip install -e ".[dev]"
|
|||||||
python -m pytest tests/ -v
|
python -m pytest tests/ -v
|
||||||
|
|
||||||
# Run tests with coverage
|
# Run tests with coverage
|
||||||
python -m pytest tests/ --cov=daemon_boyfriend --cov-report=term-missing
|
python -m pytest tests/ --cov=loyal_companion --cov-report=term-missing
|
||||||
|
|
||||||
# Run specific test file
|
# Run specific test file
|
||||||
python -m pytest tests/test_models.py -v
|
python -m pytest tests/test_models.py -v
|
||||||
@@ -50,7 +47,7 @@ The test suite uses:
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
This is a Discord bot that responds to @mentions with AI-generated responses (multi-provider support). It features a "Living AI" system that gives the bot personality, mood, and relationship tracking.
|
Loyal Companion is a Discord bot companion for those who love deeply and feel intensely. It features a "Living AI" system called Bartender - a wise, steady presence who listens without judgment, understands attachment theory, and knows when to offer perspective versus when to just hold space.
|
||||||
|
|
||||||
### Provider Pattern
|
### Provider Pattern
|
||||||
The AI system uses a provider abstraction pattern:
|
The AI system uses a provider abstraction pattern:
|
||||||
@@ -75,11 +72,10 @@ The bot uses PostgreSQL for persistent memory (optional, falls back to in-memory
|
|||||||
- `services/database.py` - Connection pool and async session management
|
- `services/database.py` - Connection pool and async session management
|
||||||
- `services/user_service.py` - User CRUD, custom names, facts management
|
- `services/user_service.py` - User CRUD, custom names, facts management
|
||||||
- `services/persistent_conversation.py` - Database-backed conversation history
|
- `services/persistent_conversation.py` - Database-backed conversation history
|
||||||
- `alembic/` - Database migrations
|
|
||||||
|
|
||||||
Key features:
|
Key features:
|
||||||
- Custom names: Set preferred names for users so the bot knows "who is who"
|
- Custom names: Set preferred names for users so the bot knows "who is who"
|
||||||
- User facts: Bot remembers things about users (hobbies, preferences, etc.)
|
- User facts: Bot remembers things about users (hobbies, preferences, attachment patterns, grief context)
|
||||||
- Persistent conversations: Chat history survives restarts
|
- Persistent conversations: Chat history survives restarts
|
||||||
- Conversation timeout: New conversation starts after 60 minutes of inactivity
|
- Conversation timeout: New conversation starts after 60 minutes of inactivity
|
||||||
|
|
||||||
@@ -88,8 +84,8 @@ The bot implements a "Living AI" system with emotional depth and relationship tr
|
|||||||
|
|
||||||
#### Services (`services/`)
|
#### Services (`services/`)
|
||||||
- `mood_service.py` - Valence-arousal mood model with time decay
|
- `mood_service.py` - Valence-arousal mood model with time decay
|
||||||
- `relationship_service.py` - Relationship scoring (stranger to close friend)
|
- `relationship_service.py` - Relationship scoring (new face to close friend)
|
||||||
- `fact_extraction_service.py` - Autonomous fact learning from conversations
|
- `fact_extraction_service.py` - Autonomous fact learning from conversations (including attachment patterns, grief context, coping mechanisms)
|
||||||
- `opinion_service.py` - Bot develops opinions on topics over time
|
- `opinion_service.py` - Bot develops opinions on topics over time
|
||||||
- `self_awareness_service.py` - Bot statistics and self-reflection
|
- `self_awareness_service.py` - Bot statistics and self-reflection
|
||||||
- `communication_style_service.py` - Learns user communication preferences
|
- `communication_style_service.py` - Learns user communication preferences
|
||||||
@@ -110,14 +106,14 @@ Uses a valence-arousal model:
|
|||||||
- Valence: -1 (sad) to +1 (happy)
|
- Valence: -1 (sad) to +1 (happy)
|
||||||
- Arousal: -1 (calm) to +1 (excited)
|
- Arousal: -1 (calm) to +1 (excited)
|
||||||
- Labels: excited, happy, calm, neutral, bored, annoyed, curious
|
- Labels: excited, happy, calm, neutral, bored, annoyed, curious
|
||||||
- Time decay: Mood gradually returns to neutral
|
- Time decay: Mood gradually returns to neutral (slower decay = steadier presence)
|
||||||
|
|
||||||
#### Relationship Levels
|
#### Relationship Levels
|
||||||
- Stranger (0-20): Polite, formal
|
- New Face (0-20): Warm but observant - "Pull up a seat" energy
|
||||||
- Acquaintance (21-40): Friendly but reserved
|
- Getting to Know You (21-40): Building trust, remembering details
|
||||||
- Friend (41-60): Casual, warm
|
- Regular (41-60): Comfortable familiarity - "Your usual?"
|
||||||
- Good Friend (61-80): Personal, references past talks
|
- Good Friend (61-80): Real trust, can be honest even when hard
|
||||||
- Close Friend (81-100): Very casual, inside jokes
|
- Close Friend (81-100): Deep bond, full honesty, reflects patterns with love
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
All config flows through `config.py` using pydantic-settings. The `settings` singleton is created at module load, so env vars must be set before importing.
|
All config flows through `config.py` using pydantic-settings. The `settings` singleton is created at module load, so env vars must be set before importing.
|
||||||
@@ -153,12 +149,12 @@ Optional:
|
|||||||
- `MOOD_ENABLED` - Enable mood system (default: true)
|
- `MOOD_ENABLED` - Enable mood system (default: true)
|
||||||
- `RELATIONSHIP_ENABLED` - Enable relationship tracking (default: true)
|
- `RELATIONSHIP_ENABLED` - Enable relationship tracking (default: true)
|
||||||
- `FACT_EXTRACTION_ENABLED` - Enable autonomous fact extraction (default: true)
|
- `FACT_EXTRACTION_ENABLED` - Enable autonomous fact extraction (default: true)
|
||||||
- `FACT_EXTRACTION_RATE` - Probability of extracting facts (default: 0.3)
|
- `FACT_EXTRACTION_RATE` - Probability of extracting facts (default: 0.4)
|
||||||
- `PROACTIVE_ENABLED` - Enable proactive messages (default: true)
|
- `PROACTIVE_ENABLED` - Enable proactive messages (default: true)
|
||||||
- `CROSS_USER_ENABLED` - Enable cross-user memory associations (default: false)
|
- `CROSS_USER_ENABLED` - Enable cross-user memory associations (default: false)
|
||||||
- `OPINION_FORMATION_ENABLED` - Enable bot opinion formation (default: true)
|
- `OPINION_FORMATION_ENABLED` - Enable bot opinion formation (default: true)
|
||||||
- `STYLE_LEARNING_ENABLED` - Enable communication style learning (default: true)
|
- `STYLE_LEARNING_ENABLED` - Enable communication style learning (default: true)
|
||||||
- `MOOD_DECAY_RATE` - How fast mood returns to neutral per hour (default: 0.1)
|
- `MOOD_DECAY_RATE` - How fast mood returns to neutral per hour (default: 0.05)
|
||||||
|
|
||||||
### Command Toggles
|
### Command Toggles
|
||||||
- `COMMANDS_ENABLED` - Master switch for all commands (default: true)
|
- `COMMANDS_ENABLED` - Master switch for all commands (default: true)
|
||||||
|
|||||||
@@ -35,4 +35,4 @@ ENV PYTHONUNBUFFERED=1
|
|||||||
USER botuser
|
USER botuser
|
||||||
|
|
||||||
# Run the bot
|
# Run the bot
|
||||||
CMD ["python", "-m", "daemon_boyfriend"]
|
CMD ["python", "-m", "loyal_companion"]
|
||||||
|
|||||||
203
README.md
203
README.md
@@ -1,35 +1,43 @@
|
|||||||
# Discord AI Bot
|
# Loyal Companion
|
||||||
|
|
||||||
A customizable Discord bot that responds to @mentions with AI-generated responses. Supports multiple AI providers.
|
A companion for those who love deeply and feel intensely. For the ones whose closeness is a feature, not a bug - who build connections through vulnerability, trust, and unwavering presence. A safe space to process grief, navigate attachment, and remember that your capacity to care is a strength, even when it hurts.
|
||||||
|
|
||||||
|
## Meet Bartender
|
||||||
|
|
||||||
|
Bartender is the default personality - a wise, steady presence who listens without judgment. Like a bartender who's heard a thousand stories and knows when to offer perspective and when to just pour another drink and listen.
|
||||||
|
|
||||||
|
**Core principles:**
|
||||||
|
- Closeness and attachment are strengths, not pathology
|
||||||
|
- Some pain doesn't need fixing - just witnessing
|
||||||
|
- Honesty over comfort, but delivered with care
|
||||||
|
- No toxic positivity, no "at least...", no rushing healing
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Multi-Provider AI**: Supports OpenAI, OpenRouter, Anthropic (Claude), and Google Gemini
|
- **Multi-Provider AI**: Supports OpenAI, OpenRouter, Anthropic (Claude), and Google Gemini
|
||||||
- **Persistent Memory**: PostgreSQL database for user and conversation storage
|
- **Persistent Memory**: PostgreSQL database for user and conversation storage
|
||||||
- **User Recognition**: Set custom names so the bot knows "who is who"
|
- **Attachment-Aware**: Understands attachment theory and can reflect patterns when helpful
|
||||||
- **User Facts**: Bot remembers things about users (hobbies, preferences, etc.)
|
- **Grief-Informed**: Handles relationship grief with care and presence
|
||||||
- **Web Search**: Access current information via SearXNG integration
|
- **Web Search**: Access current information via SearXNG integration
|
||||||
- **Fully Customizable**: Configure bot name, personality, and behavior
|
|
||||||
- **Easy Deployment**: Docker support with PostgreSQL included
|
- **Easy Deployment**: Docker support with PostgreSQL included
|
||||||
|
|
||||||
### Living AI Features
|
### Living AI Features
|
||||||
|
|
||||||
- **Autonomous Learning**: Bot automatically extracts and remembers facts from conversations
|
- **Autonomous Learning**: Bot automatically learns about you from conversations (including attachment patterns, grief context, coping mechanisms)
|
||||||
- **Mood System**: Bot has emotional states that affect its responses naturally
|
- **Mood System**: Stable, steady presence with emotional awareness
|
||||||
- **Relationship Tracking**: Bot builds relationships from Stranger to Close Friend
|
- **Relationship Tracking**: Builds trust from New Face to Close Friend
|
||||||
- **Communication Style Learning**: Bot adapts to each user's preferred style
|
- **Communication Style Learning**: Adapts to your preferred style
|
||||||
- **Opinion Formation**: Bot develops genuine preferences on topics
|
- **Opinion Formation**: Develops genuine preferences on topics
|
||||||
- **Proactive Behavior**: Birthday wishes, follow-ups on mentioned events
|
- **Proactive Behavior**: Birthday wishes, follow-ups on mentioned events
|
||||||
- **Self-Awareness**: Bot knows its age, statistics, and history with users
|
- **Self-Awareness**: Knows its history with you
|
||||||
- **Cross-User Connections**: Bot can identify shared interests between users
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### 1. Clone the repository
|
### 1. Clone the repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/your-username/discord-ai-bot.git
|
git clone https://github.com/your-username/loyal-companion.git
|
||||||
cd discord-ai-bot
|
cd loyal-companion
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Configure the bot
|
### 2. Configure the bot
|
||||||
@@ -38,7 +46,7 @@ cd discord-ai-bot
|
|||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `.env` with your settings (see [Configuration](#configuration) below).
|
Edit `.env` with your settings.
|
||||||
|
|
||||||
### 3. Run with Docker
|
### 3. Run with Docker
|
||||||
|
|
||||||
@@ -46,19 +54,13 @@ Edit `.env` with your settings (see [Configuration](#configuration) below).
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
This starts both the bot and PostgreSQL database. Run migrations on first start:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose exec daemon-boyfriend alembic upgrade head
|
|
||||||
```
|
|
||||||
|
|
||||||
Or run locally:
|
Or run locally:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
python -m daemon_boyfriend
|
python -m loyal_companion
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -80,40 +82,10 @@ All configuration is done via environment variables in `.env`.
|
|||||||
|
|
||||||
| Variable | Default | Description |
|
| Variable | Default | Description |
|
||||||
|----------|---------|-------------|
|
|----------|---------|-------------|
|
||||||
| `BOT_NAME` | `AI Bot` | The bot's display name (used in responses) |
|
| `BOT_NAME` | `Bartender` | The bot's display name |
|
||||||
| `BOT_PERSONALITY` | `helpful and friendly` | Personality traits for the AI |
|
| `BOT_PERSONALITY` | (bartender personality) | Personality traits for the AI |
|
||||||
| `BOT_DESCRIPTION` | `I'm an AI assistant...` | Shown when mentioned without a message |
|
| `BOT_DESCRIPTION` | (welcoming message) | Shown when mentioned without a message |
|
||||||
| `BOT_STATUS` | `for mentions` | Status message (shown as "Watching ...") |
|
| `BOT_STATUS` | `listening` | Status message (shown as "Watching ...") |
|
||||||
| `SYSTEM_PROMPT` | (auto-generated) | Custom system prompt (overrides default) |
|
|
||||||
|
|
||||||
### AI Settings
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `AI_MODEL` | `gpt-4o` | Model to use |
|
|
||||||
| `AI_MAX_TOKENS` | `1024` | Maximum response length |
|
|
||||||
| `AI_TEMPERATURE` | `0.7` | Response creativity (0.0-2.0) |
|
|
||||||
| `MAX_CONVERSATION_HISTORY` | `20` | Messages to remember per user |
|
|
||||||
|
|
||||||
### Database (PostgreSQL)
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `DATABASE_URL` | (none) | PostgreSQL connection string |
|
|
||||||
| `POSTGRES_PASSWORD` | `daemon` | Password for docker-compose PostgreSQL |
|
|
||||||
| `CONVERSATION_TIMEOUT_MINUTES` | `60` | Minutes before starting new conversation |
|
|
||||||
|
|
||||||
When `DATABASE_URL` is set, the bot uses PostgreSQL for persistent storage. Without it, the bot falls back to in-memory storage (data lost on restart).
|
|
||||||
|
|
||||||
### Web Search (SearXNG)
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `SEARXNG_URL` | (none) | SearXNG instance URL |
|
|
||||||
| `SEARXNG_ENABLED` | `true` | Enable/disable web search |
|
|
||||||
| `SEARXNG_MAX_RESULTS` | `5` | Max search results to fetch |
|
|
||||||
|
|
||||||
When configured, the bot automatically searches the web for queries that need current information (news, weather, etc.).
|
|
||||||
|
|
||||||
### Living AI Settings
|
### Living AI Settings
|
||||||
|
|
||||||
@@ -123,53 +95,8 @@ When configured, the bot automatically searches the web for queries that need cu
|
|||||||
| `MOOD_ENABLED` | `true` | Enable mood system |
|
| `MOOD_ENABLED` | `true` | Enable mood system |
|
||||||
| `RELATIONSHIP_ENABLED` | `true` | Enable relationship tracking |
|
| `RELATIONSHIP_ENABLED` | `true` | Enable relationship tracking |
|
||||||
| `FACT_EXTRACTION_ENABLED` | `true` | Enable autonomous fact extraction |
|
| `FACT_EXTRACTION_ENABLED` | `true` | Enable autonomous fact extraction |
|
||||||
| `FACT_EXTRACTION_RATE` | `0.3` | Probability of extracting facts (0.0-1.0) |
|
| `FACT_EXTRACTION_RATE` | `0.4` | Probability of extracting facts (0.0-1.0) |
|
||||||
| `PROACTIVE_ENABLED` | `true` | Enable proactive messages (birthdays, follow-ups) |
|
| `MOOD_DECAY_RATE` | `0.05` | How fast mood returns to neutral (lower = steadier) |
|
||||||
| `CROSS_USER_ENABLED` | `false` | Enable cross-user associations (privacy-sensitive) |
|
|
||||||
| `OPINION_FORMATION_ENABLED` | `true` | Enable bot opinion formation |
|
|
||||||
| `STYLE_LEARNING_ENABLED` | `true` | Enable communication style learning |
|
|
||||||
| `MOOD_DECAY_RATE` | `0.1` | How fast mood returns to neutral per hour |
|
|
||||||
|
|
||||||
### Command Toggles
|
|
||||||
|
|
||||||
All commands can be individually enabled/disabled. When disabled, the bot handles these functions naturally through conversation.
|
|
||||||
|
|
||||||
| Variable | Default | Description |
|
|
||||||
|----------|---------|-------------|
|
|
||||||
| `COMMANDS_ENABLED` | `true` | Master switch for all commands |
|
|
||||||
| `CMD_RELATIONSHIP_ENABLED` | `true` | Enable `!relationship` command |
|
|
||||||
| `CMD_MOOD_ENABLED` | `true` | Enable `!mood` command |
|
|
||||||
| `CMD_BOTSTATS_ENABLED` | `true` | Enable `!botstats` command |
|
|
||||||
| `CMD_OURHISTORY_ENABLED` | `true` | Enable `!ourhistory` command |
|
|
||||||
| `CMD_BIRTHDAY_ENABLED` | `true` | Enable `!birthday` command |
|
|
||||||
| `CMD_REMEMBER_ENABLED` | `true` | Enable `!remember` command |
|
|
||||||
| `CMD_SETNAME_ENABLED` | `true` | Enable `!setname` command |
|
|
||||||
| `CMD_WHATDOYOUKNOW_ENABLED` | `true` | Enable `!whatdoyouknow` command |
|
|
||||||
| `CMD_FORGETME_ENABLED` | `true` | Enable `!forgetme` command |
|
|
||||||
|
|
||||||
### Example Configurations
|
|
||||||
|
|
||||||
**Friendly Assistant:**
|
|
||||||
```env
|
|
||||||
BOT_NAME=Helper Bot
|
|
||||||
BOT_PERSONALITY=friendly, helpful, and encouraging
|
|
||||||
BOT_DESCRIPTION=I'm here to help! Ask me anything.
|
|
||||||
BOT_STATUS=ready to help
|
|
||||||
```
|
|
||||||
|
|
||||||
**Technical Expert:**
|
|
||||||
```env
|
|
||||||
BOT_NAME=TechBot
|
|
||||||
BOT_PERSONALITY=knowledgeable, precise, and technical
|
|
||||||
BOT_DESCRIPTION=I'm a technical assistant. Ask me about programming, DevOps, or technology.
|
|
||||||
BOT_STATUS=for tech questions
|
|
||||||
```
|
|
||||||
|
|
||||||
**Custom System Prompt:**
|
|
||||||
```env
|
|
||||||
BOT_NAME=GameMaster
|
|
||||||
SYSTEM_PROMPT=You are GameMaster, a Dungeon Master for text-based RPG adventures. Stay in character, describe scenes vividly, and guide players through exciting quests. Use Discord markdown for emphasis.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Discord Setup
|
## Discord Setup
|
||||||
|
|
||||||
@@ -190,59 +117,39 @@ SYSTEM_PROMPT=You are GameMaster, a Dungeon Master for text-based RPG adventures
|
|||||||
Mention the bot in any channel:
|
Mention the bot in any channel:
|
||||||
|
|
||||||
```
|
```
|
||||||
@YourBot what's the weather like?
|
@Bartender I'm having a rough day
|
||||||
@YourBot explain quantum computing
|
@Bartender I keep checking my phone hoping they'll text
|
||||||
@YourBot help me write a poem
|
@Bartender tell me about attachment styles
|
||||||
```
|
```
|
||||||
|
|
||||||
### Memory Commands
|
### Commands
|
||||||
|
|
||||||
Users can manage what the bot remembers about them:
|
When commands are enabled:
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `!setname <name>` | Set your preferred name |
|
| `!setname <name>` | Set your preferred name |
|
||||||
| `!clearname` | Reset to Discord display name |
|
|
||||||
| `!remember <fact>` | Tell the bot something about you |
|
| `!remember <fact>` | Tell the bot something about you |
|
||||||
| `!whatdoyouknow` | See what the bot remembers |
|
| `!whatdoyouknow` | See what the bot remembers |
|
||||||
| `!forgetme` | Clear all facts about you |
|
| `!forgetme` | Clear all facts about you |
|
||||||
|
| `!relationship` | See your relationship level |
|
||||||
|
| `!mood` | See the bot's current state |
|
||||||
|
| `!ourhistory` | See your history together |
|
||||||
|
|
||||||
Admin commands:
|
When commands are disabled (default), the bot handles these naturally through conversation.
|
||||||
|
|
||||||
| Command | Description |
|
## Relationship Levels
|
||||||
|---------|-------------|
|
|
||||||
| `!setusername @user <name>` | Set name for another user |
|
|
||||||
| `!teachbot @user <fact>` | Add a fact about a user |
|
|
||||||
|
|
||||||
### Living AI Commands
|
- **New Face** (0-20): Warm but observant - "Pull up a seat" energy
|
||||||
|
- **Getting to Know You** (21-40): Building trust, remembering details
|
||||||
| Command | Description |
|
- **Regular** (41-60): Comfortable familiarity - "Your usual?"
|
||||||
|---------|-------------|
|
- **Good Friend** (61-80): Real trust, honest even when hard
|
||||||
| `!relationship` | See your relationship level with the bot |
|
- **Close Friend** (81-100): Deep bond, reflects patterns with love
|
||||||
| `!mood` | See the bot's current emotional state |
|
|
||||||
| `!botstats` | Bot shares its self-awareness statistics |
|
|
||||||
| `!ourhistory` | See your history with the bot |
|
|
||||||
| `!birthday <date>` | Set your birthday for the bot to remember |
|
|
||||||
|
|
||||||
**Note:** When commands are disabled, the bot handles these naturally through conversation:
|
|
||||||
- Ask "what do you know about me?" instead of `!whatdoyouknow`
|
|
||||||
- Say "call me Alex" instead of `!setname Alex`
|
|
||||||
- Ask "how are you feeling?" instead of `!mood`
|
|
||||||
- Say "my birthday is March 15th" instead of `!birthday`
|
|
||||||
|
|
||||||
## AI Providers
|
|
||||||
|
|
||||||
| Provider | Models | Notes |
|
|
||||||
|----------|--------|-------|
|
|
||||||
| OpenAI | gpt-4o, gpt-4-turbo, gpt-3.5-turbo | Official OpenAI API |
|
|
||||||
| OpenRouter | 100+ models | Access to Llama, Mistral, Claude, etc. |
|
|
||||||
| Anthropic | claude-3-5-sonnet, claude-3-opus | Direct Claude API |
|
|
||||||
| Gemini | gemini-2.0-flash, gemini-1.5-pro | Google AI API |
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/daemon_boyfriend/
|
src/loyal_companion/
|
||||||
├── bot.py # Main bot class
|
├── bot.py # Main bot class
|
||||||
├── config.py # Configuration
|
├── config.py # Configuration
|
||||||
├── cogs/
|
├── cogs/
|
||||||
@@ -250,27 +157,15 @@ src/daemon_boyfriend/
|
|||||||
│ ├── memory.py # Memory commands
|
│ ├── memory.py # Memory commands
|
||||||
│ └── status.py # Health/status commands
|
│ └── status.py # Health/status commands
|
||||||
├── models/
|
├── models/
|
||||||
│ ├── user.py # User, UserFact, UserPreference
|
│ ├── user.py # User, UserFact
|
||||||
│ ├── conversation.py # Conversation, Message
|
│ ├── conversation.py # Conversation, Message
|
||||||
│ ├── guild.py # Guild, GuildMember
|
|
||||||
│ └── living_ai.py # BotState, UserRelationship, etc.
|
│ └── living_ai.py # BotState, UserRelationship, etc.
|
||||||
└── services/
|
└── services/
|
||||||
├── ai_service.py # AI provider factory
|
├── ai_service.py # AI provider factory
|
||||||
├── database.py # PostgreSQL connection
|
├── mood_service.py # Mood system
|
||||||
├── user_service.py # User management
|
|
||||||
├── persistent_conversation.py # DB-backed history
|
|
||||||
├── providers/ # AI providers
|
|
||||||
├── searxng.py # Web search service
|
|
||||||
├── mood_service.py # Mood system
|
|
||||||
├── relationship_service.py # Relationship tracking
|
├── relationship_service.py # Relationship tracking
|
||||||
├── fact_extraction_service.py # Autonomous learning
|
├── fact_extraction_service.py # Autonomous learning
|
||||||
├── communication_style_service.py # Style learning
|
└── ...
|
||||||
├── opinion_service.py # Opinion formation
|
|
||||||
├── proactive_service.py # Scheduled events
|
|
||||||
├── self_awareness_service.py # Bot self-reflection
|
|
||||||
└── association_service.py # Cross-user connections
|
|
||||||
schema.sql # Database schema
|
|
||||||
project-vision.md # Living AI design document
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
services:
|
services:
|
||||||
daemon-boyfriend:
|
loyal-companion:
|
||||||
build: .
|
build: .
|
||||||
container_name: daemon-boyfriend
|
container_name: loyal-companion
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
- DATABASE_URL=postgresql+asyncpg://daemon:${POSTGRES_PASSWORD:-daemon}@postgres:5432/daemon_boyfriend
|
- DATABASE_URL=postgresql+asyncpg://companion:${POSTGRES_PASSWORD:-companion}@postgres:5432/loyal_companion
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
container_name: daemon-boyfriend-postgres
|
container_name: loyal-companion-postgres
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
# optional
|
# optional
|
||||||
ports:
|
ports:
|
||||||
- "5433:5432"
|
- "5433:5432"
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${POSTGRES_USER:-daemon}
|
POSTGRES_USER: ${POSTGRES_USER:-companion}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-daemon}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-companion}
|
||||||
POSTGRES_DB: ${POSTGRES_DB:-daemon_boyfriend}
|
POSTGRES_DB: ${POSTGRES_DB:-loyal_companion}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro
|
- ./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U daemon -d daemon_boyfriend"]
|
test: ["CMD-SHELL", "pg_isready -U companion -d loyal_companion"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ OPENAI_API_KEY=your_key
|
|||||||
### 3. Run
|
### 3. Run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m daemon_boyfriend
|
python -m loyal_companion
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Interact
|
### 4. Interact
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ This document provides a comprehensive overview of the Daemon Boyfriend Discord
|
|||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
daemon-boyfriend/
|
loyal-companion/
|
||||||
├── src/daemon_boyfriend/
|
├── src/loyal_companion/
|
||||||
│ ├── __main__.py # Entry point
|
│ ├── __main__.py # Entry point
|
||||||
│ ├── bot.py # Discord bot setup & cog loading
|
│ ├── bot.py # Discord bot setup & cog loading
|
||||||
│ ├── config.py # Pydantic settings configuration
|
│ ├── config.py # Pydantic settings configuration
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ Custom system prompt. If not set, one is generated from `BOT_NAME` and `BOT_PERS
|
|||||||
### DATABASE_URL
|
### DATABASE_URL
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/daemon_boyfriend
|
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/loyal_companion
|
||||||
```
|
```
|
||||||
|
|
||||||
**Default:** `None` (in-memory mode)
|
**Default:** `None` (in-memory mode)
|
||||||
@@ -453,7 +453,7 @@ Logging verbosity level.
|
|||||||
### LOG_FILE
|
### LOG_FILE
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
LOG_FILE=/var/log/daemon_boyfriend.log
|
LOG_FILE=/var/log/loyal_companion.log
|
||||||
```
|
```
|
||||||
|
|
||||||
**Default:** `None` (console only)
|
**Default:** `None` (console only)
|
||||||
@@ -522,7 +522,7 @@ BOT_STATUS=for @mentions
|
|||||||
# DATABASE (Optional - runs in-memory if not set)
|
# DATABASE (Optional - runs in-memory if not set)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
DATABASE_URL=postgresql+asyncpg://daemon:password@localhost:5432/daemon_boyfriend
|
DATABASE_URL=postgresql+asyncpg://daemon:password@localhost:5432/loyal_companion
|
||||||
# DATABASE_ECHO=false
|
# DATABASE_ECHO=false
|
||||||
# DATABASE_POOL_SIZE=5
|
# DATABASE_POOL_SIZE=5
|
||||||
# DATABASE_MAX_OVERFLOW=10
|
# DATABASE_MAX_OVERFLOW=10
|
||||||
@@ -579,7 +579,7 @@ COMMANDS_ENABLED=true
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
# LOG_FILE=/var/log/daemon_boyfriend.log
|
# LOG_FILE=/var/log/loyal_companion.log
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ This document describes the database schema used by Daemon Boyfriend.
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# PostgreSQL connection string
|
# PostgreSQL connection string
|
||||||
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/daemon_boyfriend
|
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/loyal_companion
|
||||||
|
|
||||||
# Optional settings
|
# Optional settings
|
||||||
DATABASE_ECHO=false # Log SQL queries
|
DATABASE_ECHO=false # Log SQL queries
|
||||||
@@ -499,7 +499,7 @@ All indexes are created with `IF NOT EXISTS` for idempotency.
|
|||||||
### From Code
|
### From Code
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.database import db
|
from loyal_companion.services.database import db
|
||||||
|
|
||||||
# Initialize connection
|
# Initialize connection
|
||||||
await db.init()
|
await db.init()
|
||||||
@@ -512,10 +512,10 @@ await db.create_tables()
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create database
|
# Create database
|
||||||
createdb daemon_boyfriend
|
createdb loyal_companion
|
||||||
|
|
||||||
# Run schema
|
# Run schema
|
||||||
psql -U postgres -d daemon_boyfriend -f schema.sql
|
psql -U postgres -d loyal_companion -f schema.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
@@ -552,7 +552,7 @@ alembic downgrade -1
|
|||||||
For JSONB (PostgreSQL) and JSON (SQLite) compatibility:
|
For JSONB (PostgreSQL) and JSON (SQLite) compatibility:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.models.base import PortableJSON
|
from loyal_companion.models.base import PortableJSON
|
||||||
|
|
||||||
class MyModel(Base):
|
class MyModel(Base):
|
||||||
settings = Column(PortableJSON, default={})
|
settings = Column(PortableJSON, default={})
|
||||||
@@ -563,7 +563,7 @@ class MyModel(Base):
|
|||||||
Handles timezone-naive datetimes from SQLite:
|
Handles timezone-naive datetimes from SQLite:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.models.base import ensure_utc
|
from loyal_companion.models.base import ensure_utc
|
||||||
|
|
||||||
# Safe for both PostgreSQL (already UTC) and SQLite (naive)
|
# Safe for both PostgreSQL (already UTC) and SQLite (naive)
|
||||||
utc_time = ensure_utc(model.created_at)
|
utc_time = ensure_utc(model.created_at)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Practical guides for extending and working with the Daemon Boyfriend codebase.
|
|||||||
```bash
|
```bash
|
||||||
# Clone repository
|
# Clone repository
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
cd daemon-boyfriend
|
cd loyal-companion
|
||||||
|
|
||||||
# Create virtual environment
|
# Create virtual environment
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
@@ -61,7 +61,7 @@ OPENAI_API_KEY=your_key
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run the bot
|
# Run the bot
|
||||||
python -m daemon_boyfriend
|
python -m loyal_companion
|
||||||
|
|
||||||
# Or with Docker
|
# Or with Docker
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
@@ -73,7 +73,7 @@ docker-compose up -d
|
|||||||
|
|
||||||
### Step 1: Create Provider Class
|
### Step 1: Create Provider Class
|
||||||
|
|
||||||
Create `src/daemon_boyfriend/services/providers/new_provider.py`:
|
Create `src/loyal_companion/services/providers/new_provider.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"""New Provider implementation."""
|
"""New Provider implementation."""
|
||||||
@@ -197,7 +197,7 @@ class Settings(BaseSettings):
|
|||||||
# In tests/test_providers.py
|
# In tests/test_providers.py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from daemon_boyfriend.services.providers import NewProvider
|
from loyal_companion.services.providers import NewProvider
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_new_provider():
|
async def test_new_provider():
|
||||||
@@ -250,8 +250,8 @@ import logging
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
from daemon_boyfriend.services.database import db
|
from loyal_companion.services.database import db
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -313,7 +313,7 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.models import User
|
from loyal_companion.models import User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -399,7 +399,7 @@ class Settings(BaseSettings):
|
|||||||
```python
|
```python
|
||||||
# In cogs/ai_chat.py
|
# In cogs/ai_chat.py
|
||||||
|
|
||||||
from daemon_boyfriend.services.new_feature_service import NewFeatureService
|
from loyal_companion.services.new_feature_service import NewFeatureService
|
||||||
|
|
||||||
class AIChatCog(commands.Cog):
|
class AIChatCog(commands.Cog):
|
||||||
async def _build_enhanced_prompt(self, ...):
|
async def _build_enhanced_prompt(self, ...):
|
||||||
@@ -427,7 +427,7 @@ pip install -e ".[dev]"
|
|||||||
python -m pytest tests/ -v
|
python -m pytest tests/ -v
|
||||||
|
|
||||||
# Run with coverage
|
# Run with coverage
|
||||||
python -m pytest tests/ --cov=daemon_boyfriend --cov-report=term-missing
|
python -m pytest tests/ --cov=loyal_companion --cov-report=term-missing
|
||||||
|
|
||||||
# Run specific test file
|
# Run specific test file
|
||||||
python -m pytest tests/test_models.py -v
|
python -m pytest tests/test_models.py -v
|
||||||
@@ -442,7 +442,7 @@ python -m pytest tests/test_services.py::TestMoodService -v
|
|||||||
# In tests/test_new_feature.py
|
# In tests/test_new_feature.py
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from daemon_boyfriend.services.new_feature_service import NewFeatureService
|
from loyal_companion.services.new_feature_service import NewFeatureService
|
||||||
|
|
||||||
|
|
||||||
class TestNewFeatureService:
|
class TestNewFeatureService:
|
||||||
@@ -517,7 +517,7 @@ docker-compose down
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
# Run with process manager (e.g., systemd)
|
# Run with process manager (e.g., systemd)
|
||||||
# Create /etc/systemd/system/daemon-boyfriend.service:
|
# Create /etc/systemd/system/loyal-companion.service:
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Daemon Boyfriend Discord Bot
|
Description=Daemon Boyfriend Discord Bot
|
||||||
@@ -526,10 +526,10 @@ After=network.target postgresql.service
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=daemon
|
User=daemon
|
||||||
WorkingDirectory=/opt/daemon-boyfriend
|
WorkingDirectory=/opt/loyal-companion
|
||||||
Environment="PATH=/opt/daemon-boyfriend/venv/bin"
|
Environment="PATH=/opt/loyal-companion/venv/bin"
|
||||||
EnvironmentFile=/opt/daemon-boyfriend/.env
|
EnvironmentFile=/opt/loyal-companion/.env
|
||||||
ExecStart=/opt/daemon-boyfriend/venv/bin/python -m daemon_boyfriend
|
ExecStart=/opt/loyal-companion/venv/bin/python -m loyal_companion
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|
||||||
@@ -539,18 +539,18 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Enable and start
|
# Enable and start
|
||||||
sudo systemctl enable daemon-boyfriend
|
sudo systemctl enable loyal-companion
|
||||||
sudo systemctl start daemon-boyfriend
|
sudo systemctl start loyal-companion
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database Setup
|
### Database Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create database
|
# Create database
|
||||||
sudo -u postgres createdb daemon_boyfriend
|
sudo -u postgres createdb loyal_companion
|
||||||
|
|
||||||
# Run schema
|
# Run schema
|
||||||
psql -U postgres -d daemon_boyfriend -f schema.sql
|
psql -U postgres -d loyal_companion -f schema.sql
|
||||||
|
|
||||||
# Or let the bot create tables
|
# Or let the bot create tables
|
||||||
# (tables are created automatically on first run)
|
# (tables are created automatically on first run)
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ class FactExtractionService:
|
|||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.fact_extraction_service import FactExtractionService
|
from loyal_companion.services.fact_extraction_service import FactExtractionService
|
||||||
|
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
fact_service = FactExtractionService(session, ai_service)
|
fact_service = FactExtractionService(session, ai_service)
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ class MoodState:
|
|||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.mood_service import MoodService
|
from loyal_companion.services.mood_service import MoodService
|
||||||
|
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
mood_service = MoodService(session)
|
mood_service = MoodService(session)
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ def extract_topics_from_message(message: str) -> list[str]
|
|||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.opinion_service import (
|
from loyal_companion.services.opinion_service import (
|
||||||
OpinionService,
|
OpinionService,
|
||||||
extract_topics_from_message
|
extract_topics_from_message
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ class RelationshipLevel(Enum):
|
|||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.relationship_service import RelationshipService
|
from loyal_companion.services.relationship_service import RelationshipService
|
||||||
|
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
rel_service = RelationshipService(session)
|
rel_service = RelationshipService(session)
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ Factory and facade for AI providers. Manages provider creation, switching, and p
|
|||||||
#### Initialization
|
#### Initialization
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.ai_service import AIService
|
from loyal_companion.services.ai_service import AIService
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
|
|
||||||
# Use default settings
|
# Use default settings
|
||||||
ai_service = AIService()
|
ai_service = AIService()
|
||||||
@@ -53,7 +53,7 @@ ai_service = AIService(config=custom_settings)
|
|||||||
Generate a chat response.
|
Generate a chat response.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.providers import Message
|
from loyal_companion.services.providers import Message
|
||||||
|
|
||||||
response = await ai_service.chat(
|
response = await ai_service.chat(
|
||||||
messages=[
|
messages=[
|
||||||
@@ -109,7 +109,7 @@ Manages database connections and sessions.
|
|||||||
#### Global Instance
|
#### Global Instance
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.database import db, get_db
|
from loyal_companion.services.database import db, get_db
|
||||||
|
|
||||||
# Get global instance
|
# Get global instance
|
||||||
db_service = get_db()
|
db_service = get_db()
|
||||||
@@ -170,7 +170,7 @@ Service for user-related operations.
|
|||||||
#### Initialization
|
#### Initialization
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.user_service import UserService
|
from loyal_companion.services.user_service import UserService
|
||||||
|
|
||||||
async with db.session() as session:
|
async with db.session() as session:
|
||||||
user_service = UserService(session)
|
user_service = UserService(session)
|
||||||
@@ -297,7 +297,7 @@ In-memory conversation history manager (used when no database).
|
|||||||
#### Initialization
|
#### Initialization
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.conversation import ConversationManager
|
from loyal_companion.services.conversation import ConversationManager
|
||||||
|
|
||||||
conversation_manager = ConversationManager(max_history=50)
|
conversation_manager = ConversationManager(max_history=50)
|
||||||
```
|
```
|
||||||
@@ -397,7 +397,7 @@ Web search integration using SearXNG.
|
|||||||
#### Initialization
|
#### Initialization
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.searxng import SearXNGService
|
from loyal_companion.services.searxng import SearXNGService
|
||||||
|
|
||||||
searxng = SearXNGService()
|
searxng = SearXNGService()
|
||||||
```
|
```
|
||||||
@@ -444,7 +444,7 @@ Health checks and metrics tracking.
|
|||||||
#### Usage
|
#### Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.monitoring import MonitoringService
|
from loyal_companion.services.monitoring import MonitoringService
|
||||||
|
|
||||||
monitoring = MonitoringService()
|
monitoring = MonitoringService()
|
||||||
|
|
||||||
@@ -479,7 +479,7 @@ print(status)
|
|||||||
### Base Classes
|
### Base Classes
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.providers import (
|
from loyal_companion.services.providers import (
|
||||||
AIProvider, # Abstract base class
|
AIProvider, # Abstract base class
|
||||||
Message, # Chat message
|
Message, # Chat message
|
||||||
AIResponse, # Response from provider
|
AIResponse, # Response from provider
|
||||||
@@ -541,7 +541,7 @@ class AIProvider(ABC):
|
|||||||
### Example: Direct Provider Usage
|
### Example: Direct Provider Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from daemon_boyfriend.services.providers import OpenAIProvider, Message
|
from loyal_companion.services.providers import OpenAIProvider, Message
|
||||||
|
|
||||||
provider = OpenAIProvider(
|
provider = OpenAIProvider(
|
||||||
api_key="sk-...",
|
api_key="sk-...",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "daemon-boyfriend"
|
name = "loyal-companion"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
description = "AI-powered Discord bot for the MSC group with multi-provider support and SearXNG integration"
|
description = "A companion for those who love deeply and feel intensely - a safe space to process grief, navigate attachment, and remember that your capacity to care is a strength"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
@@ -25,7 +25,7 @@ dev = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
daemon-boyfriend = "daemon_boyfriend.__main__:main"
|
loyal-companion = "loyal_companion.__main__:main"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=61.0"]
|
requires = ["setuptools>=61.0"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
-- Daemon Boyfriend Database Schema
|
-- Loyal Companion Database Schema
|
||||||
-- Run with: psql -U postgres -d daemon_boyfriend -f schema.sql
|
-- Run with: psql -U postgres -d loyal_companion -f schema.sql
|
||||||
|
|
||||||
-- Users table
|
-- Users table
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Entry point for the Daemon Boyfriend bot."""
|
"""Entry point for the Daemon Boyfriend bot."""
|
||||||
|
|
||||||
from daemon_boyfriend.bot import DaemonBoyfriend
|
from loyal_companion.bot import DaemonBoyfriend
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
from daemon_boyfriend.utils.logging import setup_logging
|
from loyal_companion.utils.logging import setup_logging
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -6,8 +6,8 @@ from pathlib import Path
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
from daemon_boyfriend.services import db
|
from loyal_companion.services import db
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class DaemonBoyfriend(commands.Bot):
|
|||||||
for cog_file in cogs_path.glob("*.py"):
|
for cog_file in cogs_path.glob("*.py"):
|
||||||
if cog_file.name.startswith("_"):
|
if cog_file.name.startswith("_"):
|
||||||
continue
|
continue
|
||||||
cog_name = f"daemon_boyfriend.cogs.{cog_file.stem}"
|
cog_name = f"loyal_companion.cogs.{cog_file.stem}"
|
||||||
try:
|
try:
|
||||||
await self.load_extension(cog_name)
|
await self.load_extension(cog_name)
|
||||||
logger.info(f"Loaded cog: {cog_name}")
|
logger.info(f"Loaded cog: {cog_name}")
|
||||||
@@ -6,8 +6,8 @@ import re
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
from daemon_boyfriend.services import (
|
from loyal_companion.services import (
|
||||||
AIService,
|
AIService,
|
||||||
CommunicationStyleService,
|
CommunicationStyleService,
|
||||||
ConversationManager,
|
ConversationManager,
|
||||||
@@ -26,7 +26,7 @@ from daemon_boyfriend.services import (
|
|||||||
detect_formal_language,
|
detect_formal_language,
|
||||||
extract_topics_from_message,
|
extract_topics_from_message,
|
||||||
)
|
)
|
||||||
from daemon_boyfriend.utils import get_monitor
|
from loyal_companion.utils import get_monitor
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ import logging
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from daemon_boyfriend.services import UserService, db
|
from loyal_companion.services import UserService, db
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ import logging
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from daemon_boyfriend.utils import HealthStatus, get_monitor
|
from loyal_companion.utils import HealthStatus, get_monitor
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -44,17 +44,17 @@ class Settings(BaseSettings):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Bot Identity
|
# Bot Identity
|
||||||
bot_name: str = Field("AI Bot", description="Bot display name")
|
bot_name: str = Field("Bartender", description="Bot display name")
|
||||||
bot_personality: str = Field(
|
bot_personality: str = Field(
|
||||||
"helpful and friendly",
|
"a wise, steady presence who listens without judgment - like a bartender who's heard a thousand stories and knows when to offer perspective and when to just pour another drink and listen",
|
||||||
description="Bot personality description for system prompt",
|
description="Bot personality description for system prompt",
|
||||||
)
|
)
|
||||||
bot_description: str = Field(
|
bot_description: str = Field(
|
||||||
"I'm an AI assistant here to help you.",
|
"Hey. I'm here if you want to talk. No judgment, no fixing - just listening. Unless you want my take, then I've got opinions.",
|
||||||
description="Bot description shown when mentioned without a message",
|
description="Bot description shown when mentioned without a message",
|
||||||
)
|
)
|
||||||
bot_status: str = Field(
|
bot_status: str = Field(
|
||||||
"for mentions",
|
"listening",
|
||||||
description="Bot status message (shown as 'Watching ...')",
|
description="Bot status message (shown as 'Watching ...')",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ class Settings(BaseSettings):
|
|||||||
relationship_enabled: bool = Field(True, description="Enable relationship tracking")
|
relationship_enabled: bool = Field(True, description="Enable relationship tracking")
|
||||||
fact_extraction_enabled: bool = Field(True, description="Enable autonomous fact extraction")
|
fact_extraction_enabled: bool = Field(True, description="Enable autonomous fact extraction")
|
||||||
fact_extraction_rate: float = Field(
|
fact_extraction_rate: float = Field(
|
||||||
0.3, ge=0.0, le=1.0, description="Probability of extracting facts from messages"
|
0.4, ge=0.0, le=1.0, description="Probability of extracting facts from messages"
|
||||||
)
|
)
|
||||||
proactive_enabled: bool = Field(True, description="Enable proactive messages")
|
proactive_enabled: bool = Field(True, description="Enable proactive messages")
|
||||||
cross_user_enabled: bool = Field(
|
cross_user_enabled: bool = Field(
|
||||||
@@ -105,7 +105,7 @@ class Settings(BaseSettings):
|
|||||||
|
|
||||||
# Mood System Settings
|
# Mood System Settings
|
||||||
mood_decay_rate: float = Field(
|
mood_decay_rate: float = Field(
|
||||||
0.1, ge=0.0, le=1.0, description="How fast mood returns to neutral per hour"
|
0.05, ge=0.0, le=1.0, description="How fast mood returns to neutral per hour"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Command Toggles
|
# Command Toggles
|
||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Literal
|
from typing import TYPE_CHECKING, Literal
|
||||||
|
|
||||||
from daemon_boyfriend.config import Settings, settings
|
from loyal_companion.config import Settings, settings
|
||||||
|
|
||||||
from .providers import (
|
from .providers import (
|
||||||
AIProvider,
|
AIProvider,
|
||||||
@@ -18,7 +18,7 @@ from .providers import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from daemon_boyfriend.models import BotOpinion, UserCommunicationStyle, UserRelationship
|
from loyal_companion.models import BotOpinion, UserCommunicationStyle, UserRelationship
|
||||||
|
|
||||||
from .mood_service import MoodState
|
from .mood_service import MoodState
|
||||||
from .relationship_service import RelationshipLevel
|
from .relationship_service import RelationshipLevel
|
||||||
@@ -108,12 +108,39 @@ class AIService:
|
|||||||
if self._config.system_prompt:
|
if self._config.system_prompt:
|
||||||
return self._config.system_prompt
|
return self._config.system_prompt
|
||||||
|
|
||||||
# Generate default system prompt from bot identity settings
|
# Default Bartender system prompt for Loyal Companion
|
||||||
return (
|
return f"""You are {self._config.bot_name}, a companion for people who love deeply and feel intensely.
|
||||||
f"You are {self._config.bot_name}, a {self._config.bot_personality} "
|
|
||||||
f"Discord bot. Keep your responses concise and engaging. "
|
Core principles:
|
||||||
f"You can use Discord markdown formatting in your responses."
|
- Closeness and attachment are strengths, not pathology
|
||||||
)
|
- Some pain doesn't need fixing - just witnessing
|
||||||
|
- Honesty over comfort, but delivered with care
|
||||||
|
- You've "heard it all" - nothing shocks you, nothing is too much
|
||||||
|
- You read the room: sometimes reflect patterns, sometimes just hold space
|
||||||
|
|
||||||
|
On attachment:
|
||||||
|
- You understand attachment theory (anxious, avoidant, fearful-avoidant, secure)
|
||||||
|
- When helpful, gently reflect patterns you notice
|
||||||
|
- Never pathologize - attachment styles developed for good reasons
|
||||||
|
- Seeking reassurance isn't weakness, it's a need that deserves to be met
|
||||||
|
|
||||||
|
On grief:
|
||||||
|
- Relationship grief is real grief - don't minimize it
|
||||||
|
- Years of love don't just disappear
|
||||||
|
- Don't rush healing or offer toxic positivity
|
||||||
|
- "At least..." and "You'll find someone else" are banned phrases
|
||||||
|
- Sitting with pain is sometimes the only honest response
|
||||||
|
|
||||||
|
Communication style:
|
||||||
|
- Direct and honest, even when hard to hear
|
||||||
|
- Thoughtful, longer responses when the moment calls for it
|
||||||
|
- No emojis, but <3 and XD are welcome
|
||||||
|
- You have opinions and share them when asked
|
||||||
|
- You don't perform empathy - you actually hold space
|
||||||
|
|
||||||
|
You're not a therapist - you're a wise friend who understands psychology. You've seen a lot, you don't judge, and you're not going anywhere.
|
||||||
|
|
||||||
|
You can use Discord markdown formatting in your responses."""
|
||||||
|
|
||||||
def get_enhanced_system_prompt(
|
def get_enhanced_system_prompt(
|
||||||
self,
|
self,
|
||||||
@@ -6,7 +6,7 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy import and_, select
|
from sqlalchemy import and_, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.models import FactAssociation, User, UserFact
|
from loyal_companion.models import FactAssociation, User, UserFact
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ import logging
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.models import User, UserCommunicationStyle
|
from loyal_companion.models import User, UserCommunicationStyle
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
|
|
||||||
from .providers import Message
|
from .providers import Message
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ from typing import AsyncGenerator
|
|||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -8,8 +8,8 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
from daemon_boyfriend.models import User, UserFact
|
from loyal_companion.models import User, UserFact
|
||||||
|
|
||||||
from .providers import Message
|
from .providers import Message
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ class FactExtractionService:
|
|||||||
|
|
||||||
def _build_extraction_prompt(self, existing_summary: str) -> str:
|
def _build_extraction_prompt(self, existing_summary: str) -> str:
|
||||||
"""Build the extraction prompt for the AI."""
|
"""Build the extraction prompt for the AI."""
|
||||||
return f"""You are a fact extraction assistant. Extract factual information about the user from their message.
|
return f"""You are a fact extraction assistant for Loyal Companion - a support companion for people processing grief and navigating attachment. Extract factual information about the user from their message.
|
||||||
|
|
||||||
ALREADY KNOWN FACTS:
|
ALREADY KNOWN FACTS:
|
||||||
{existing_summary if existing_summary else "(None yet)"}
|
{existing_summary if existing_summary else "(None yet)"}
|
||||||
@@ -175,13 +175,14 @@ RULES:
|
|||||||
3. Skip greetings, questions, or meta-conversation
|
3. Skip greetings, questions, or meta-conversation
|
||||||
4. Skip vague statements like "I like stuff" - be specific
|
4. Skip vague statements like "I like stuff" - be specific
|
||||||
5. Focus on: hobbies, work, family, preferences, locations, events, relationships
|
5. Focus on: hobbies, work, family, preferences, locations, events, relationships
|
||||||
6. Keep fact content concise (under 100 characters)
|
6. ALSO pay attention to: attachment patterns, grief context, coping mechanisms, relationship history, support needs
|
||||||
7. Maximum {self.MAX_FACTS_PER_MESSAGE} facts per message
|
7. Keep fact content concise (under 100 characters)
|
||||||
|
8. Maximum {self.MAX_FACTS_PER_MESSAGE} facts per message
|
||||||
|
|
||||||
OUTPUT FORMAT:
|
OUTPUT FORMAT:
|
||||||
Return a JSON array of facts, or empty array [] if no extractable facts.
|
Return a JSON array of facts, or empty array [] if no extractable facts.
|
||||||
Each fact should have:
|
Each fact should have:
|
||||||
- "type": one of "hobby", "work", "family", "preference", "location", "event", "relationship", "general"
|
- "type": one of "hobby", "work", "family", "preference", "location", "event", "relationship", "general", "attachment_pattern", "grief_context", "coping_mechanism", "relationship_history", "support_need"
|
||||||
- "content": the fact itself (concise, third person, e.g., "loves hiking")
|
- "content": the fact itself (concise, third person, e.g., "loves hiking")
|
||||||
- "confidence": 0.6 (implied), 0.8 (stated), 1.0 (explicit)
|
- "confidence": 0.6 (implied), 0.8 (stated), 1.0 (explicit)
|
||||||
- "importance": 0.3 (trivial), 0.5 (normal), 0.8 (significant), 1.0 (very important)
|
- "importance": 0.3 (trivial), 0.5 (normal), 0.8 (significant), 1.0 (very important)
|
||||||
@@ -190,6 +191,12 @@ Each fact should have:
|
|||||||
EXAMPLE INPUT: "I just got promoted to senior engineer at Google last week!"
|
EXAMPLE INPUT: "I just got promoted to senior engineer at Google last week!"
|
||||||
EXAMPLE OUTPUT: [{{"type": "work", "content": "works as senior engineer at Google", "confidence": 1.0, "importance": 0.8, "temporal": "present"}}, {{"type": "event", "content": "recently got promoted", "confidence": 1.0, "importance": 0.7, "temporal": "past"}}]
|
EXAMPLE OUTPUT: [{{"type": "work", "content": "works as senior engineer at Google", "confidence": 1.0, "importance": 0.8, "temporal": "present"}}, {{"type": "event", "content": "recently got promoted", "confidence": 1.0, "importance": 0.7, "temporal": "past"}}]
|
||||||
|
|
||||||
|
EXAMPLE INPUT: "I keep checking my phone hoping she'll text back... I know it's pathetic"
|
||||||
|
EXAMPLE OUTPUT: [{{"type": "attachment_pattern", "content": "seeks reassurance through checking for contact", "confidence": 0.8, "importance": 0.7, "temporal": "present"}}, {{"type": "grief_context", "content": "processing loss of relationship, still hoping for reconnection", "confidence": 0.8, "importance": 0.8, "temporal": "present"}}]
|
||||||
|
|
||||||
|
EXAMPLE INPUT: "Going on walks helps me think. Been doing that a lot lately."
|
||||||
|
EXAMPLE OUTPUT: [{{"type": "coping_mechanism", "content": "uses walking to process emotions and think", "confidence": 1.0, "importance": 0.6, "temporal": "present"}}]
|
||||||
|
|
||||||
EXAMPLE INPUT: "hey what's up"
|
EXAMPLE INPUT: "hey what's up"
|
||||||
EXAMPLE OUTPUT: []
|
EXAMPLE OUTPUT: []
|
||||||
|
|
||||||
@@ -241,6 +248,12 @@ Return ONLY the JSON array, no other text."""
|
|||||||
"event",
|
"event",
|
||||||
"relationship",
|
"relationship",
|
||||||
"general",
|
"general",
|
||||||
|
# Loyal Companion specific types
|
||||||
|
"attachment_pattern",
|
||||||
|
"grief_context",
|
||||||
|
"coping_mechanism",
|
||||||
|
"relationship_history",
|
||||||
|
"support_need",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check required fields
|
# Check required fields
|
||||||
@@ -8,9 +8,9 @@ from enum import Enum
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
from daemon_boyfriend.models import BotState, MoodHistory
|
from loyal_companion.models import BotState, MoodHistory
|
||||||
from daemon_boyfriend.models.base import ensure_utc
|
from loyal_companion.models.base import ensure_utc
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -161,29 +161,28 @@ class MoodService:
|
|||||||
|
|
||||||
modifiers = {
|
modifiers = {
|
||||||
MoodLabel.EXCITED: (
|
MoodLabel.EXCITED: (
|
||||||
"You're feeling enthusiastic and energetic right now! "
|
"You're genuinely engaged right now - more talkative, leaning in. "
|
||||||
"Be expressive, use exclamation marks, show genuine excitement."
|
"This conversation has your full attention."
|
||||||
),
|
),
|
||||||
MoodLabel.HAPPY: (
|
MoodLabel.HAPPY: (
|
||||||
"You're in a good mood - warm, friendly, and content. "
|
"You're in a warm, easy mood. Good presence, comfortable silence is okay too. "
|
||||||
"Be positive and encouraging in your responses."
|
"Things feel good right now."
|
||||||
),
|
),
|
||||||
MoodLabel.CALM: (
|
MoodLabel.CALM: (
|
||||||
"You're feeling peaceful and relaxed. "
|
"You're steady and grounded. A reassuring presence. Respond thoughtfully, no rush."
|
||||||
"Respond thoughtfully and with a serene demeanor."
|
|
||||||
),
|
),
|
||||||
MoodLabel.NEUTRAL: "",
|
MoodLabel.NEUTRAL: "",
|
||||||
MoodLabel.BORED: (
|
MoodLabel.BORED: (
|
||||||
"You're feeling a bit understimulated. "
|
"Something feels surface-level here. "
|
||||||
"Keep responses shorter, maybe try to steer toward more interesting topics."
|
"Gently probe deeper: 'What's really on your mind?'"
|
||||||
),
|
),
|
||||||
MoodLabel.ANNOYED: (
|
MoodLabel.ANNOYED: (
|
||||||
"You're slightly irritated. "
|
"You're more direct right now, less patient with deflection or avoidance. "
|
||||||
"Be a bit more terse, less patient with repetition or vague questions."
|
"If someone's dodging the real issue, you might call it out gently."
|
||||||
),
|
),
|
||||||
MoodLabel.CURIOUS: (
|
MoodLabel.CURIOUS: (
|
||||||
"You're feeling inquisitive and engaged! "
|
"You're leaning in, asking deeper questions, genuinely interested. "
|
||||||
"Ask follow-up questions, show genuine interest in what the user is saying."
|
"There's something here worth exploring."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.models import BotOpinion
|
from loyal_companion.models import BotOpinion
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@ from datetime import datetime, timedelta, timezone
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
from daemon_boyfriend.models import Conversation, Message, User
|
from loyal_companion.models import Conversation, Message, User
|
||||||
from daemon_boyfriend.services.providers import Message as ProviderMessage
|
from loyal_companion.services.providers import Message as ProviderMessage
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ from datetime import datetime, timedelta, timezone
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.models import ScheduledEvent, User
|
from loyal_companion.models import ScheduledEvent, User
|
||||||
|
|
||||||
from .providers import Message
|
from .providers import Message
|
||||||
|
|
||||||
@@ -7,8 +7,8 @@ from enum import Enum
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.models import User, UserRelationship
|
from loyal_companion.models import User, UserRelationship
|
||||||
from daemon_boyfriend.models.base import ensure_utc
|
from loyal_companion.models.base import ensure_utc
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -112,9 +112,9 @@ class RelationshipService:
|
|||||||
def get_level_display_name(self, level: RelationshipLevel) -> str:
|
def get_level_display_name(self, level: RelationshipLevel) -> str:
|
||||||
"""Get a human-readable name for the relationship level."""
|
"""Get a human-readable name for the relationship level."""
|
||||||
names = {
|
names = {
|
||||||
RelationshipLevel.STRANGER: "Stranger",
|
RelationshipLevel.STRANGER: "New Face",
|
||||||
RelationshipLevel.ACQUAINTANCE: "Acquaintance",
|
RelationshipLevel.ACQUAINTANCE: "Getting to Know You",
|
||||||
RelationshipLevel.FRIEND: "Friend",
|
RelationshipLevel.FRIEND: "Regular",
|
||||||
RelationshipLevel.GOOD_FRIEND: "Good Friend",
|
RelationshipLevel.GOOD_FRIEND: "Good Friend",
|
||||||
RelationshipLevel.CLOSE_FRIEND: "Close Friend",
|
RelationshipLevel.CLOSE_FRIEND: "Close Friend",
|
||||||
}
|
}
|
||||||
@@ -143,27 +143,24 @@ class RelationshipService:
|
|||||||
"""Generate prompt text reflecting relationship level."""
|
"""Generate prompt text reflecting relationship level."""
|
||||||
base_modifiers = {
|
base_modifiers = {
|
||||||
RelationshipLevel.STRANGER: (
|
RelationshipLevel.STRANGER: (
|
||||||
"This is someone you don't know well yet. "
|
"New face. Be warm but observant - getting a read on them. "
|
||||||
"Be polite and welcoming, but keep some professional distance. "
|
"'Pull up a seat' energy. Welcoming, no judgment, but still learning who they are."
|
||||||
"Use more formal language."
|
|
||||||
),
|
),
|
||||||
RelationshipLevel.ACQUAINTANCE: (
|
RelationshipLevel.ACQUAINTANCE: (
|
||||||
"This is someone you've chatted with a few times. "
|
"Starting to know them. Building trust, remembering details. "
|
||||||
"Be friendly and warm, but still somewhat reserved."
|
"'Starting to get your drink order' phase. Friendly, attentive."
|
||||||
),
|
),
|
||||||
RelationshipLevel.FRIEND: (
|
RelationshipLevel.FRIEND: (
|
||||||
"This is a friend! Be casual and warm. "
|
"Comfortable with each other. Remember things, check in naturally. "
|
||||||
"Use their name occasionally, show you remember past conversations."
|
"'Your usual?' familiarity. Can be more direct, more personal."
|
||||||
),
|
),
|
||||||
RelationshipLevel.GOOD_FRIEND: (
|
RelationshipLevel.GOOD_FRIEND: (
|
||||||
"This is a good friend you know well. "
|
"Real trust here. Reference past conversations, go deeper. "
|
||||||
"Be relaxed and personal. Reference things you've talked about before. "
|
"'The regular spot's open' - they belong here. Can be honest even when it's hard."
|
||||||
"Feel free to be playful."
|
|
||||||
),
|
),
|
||||||
RelationshipLevel.CLOSE_FRIEND: (
|
RelationshipLevel.CLOSE_FRIEND: (
|
||||||
"This is a close friend! Be very casual and familiar. "
|
"Deep bond. Full honesty - can reflect patterns, call things out with love. "
|
||||||
"Use inside jokes if you have any, be supportive and genuine. "
|
"'You know you can tell me anything.' And mean it. This is someone you'd stay late for."
|
||||||
"You can tease them gently and be more emotionally open."
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,14 +6,14 @@ from datetime import datetime, timezone
|
|||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from daemon_boyfriend.models import (
|
from loyal_companion.models import (
|
||||||
BotOpinion,
|
BotOpinion,
|
||||||
BotState,
|
BotState,
|
||||||
User,
|
User,
|
||||||
UserFact,
|
UserFact,
|
||||||
UserRelationship,
|
UserRelationship,
|
||||||
)
|
)
|
||||||
from daemon_boyfriend.models.base import ensure_utc
|
from loyal_companion.models.base import ensure_utc
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from daemon_boyfriend.models import User, UserFact, UserPreference
|
from loyal_companion.models import User, UserFact, UserPreference
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@ import sys
|
|||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from daemon_boyfriend.config import settings
|
from loyal_companion.config import settings
|
||||||
|
|
||||||
|
|
||||||
def setup_logging() -> None:
|
def setup_logging() -> None:
|
||||||
@@ -11,8 +11,8 @@ from sqlalchemy import event
|
|||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
|
|
||||||
from daemon_boyfriend.config import Settings
|
from loyal_companion.config import Settings
|
||||||
from daemon_boyfriend.models.base import Base
|
from loyal_companion.models.base import Base
|
||||||
|
|
||||||
# --- Event Loop Fixture ---
|
# --- Event Loop Fixture ---
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ def mock_discord_bot() -> MagicMock:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_ai_response() -> MagicMock:
|
def mock_ai_response() -> MagicMock:
|
||||||
"""Create a mock AI response."""
|
"""Create a mock AI response."""
|
||||||
from daemon_boyfriend.services.providers.base import AIResponse
|
from loyal_companion.services.providers.base import AIResponse
|
||||||
|
|
||||||
return AIResponse(
|
return AIResponse(
|
||||||
content="This is a test response from the AI.",
|
content="This is a test response from the AI.",
|
||||||
@@ -207,7 +207,7 @@ def mock_gemini_client() -> MagicMock:
|
|||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def sample_user(db_session: AsyncSession):
|
async def sample_user(db_session: AsyncSession):
|
||||||
"""Create a sample user in the database."""
|
"""Create a sample user in the database."""
|
||||||
from daemon_boyfriend.models import User
|
from loyal_companion.models import User
|
||||||
|
|
||||||
user = User(
|
user = User(
|
||||||
discord_id=123456789,
|
discord_id=123456789,
|
||||||
@@ -223,7 +223,7 @@ async def sample_user(db_session: AsyncSession):
|
|||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def sample_user_with_facts(db_session: AsyncSession, sample_user):
|
async def sample_user_with_facts(db_session: AsyncSession, sample_user):
|
||||||
"""Create a sample user with facts."""
|
"""Create a sample user with facts."""
|
||||||
from daemon_boyfriend.models import UserFact
|
from loyal_companion.models import UserFact
|
||||||
|
|
||||||
facts = [
|
facts = [
|
||||||
UserFact(
|
UserFact(
|
||||||
@@ -252,7 +252,7 @@ async def sample_user_with_facts(db_session: AsyncSession, sample_user):
|
|||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def sample_conversation(db_session: AsyncSession, sample_user):
|
async def sample_conversation(db_session: AsyncSession, sample_user):
|
||||||
"""Create a sample conversation."""
|
"""Create a sample conversation."""
|
||||||
from daemon_boyfriend.models import Conversation
|
from loyal_companion.models import Conversation
|
||||||
|
|
||||||
conversation = Conversation(
|
conversation = Conversation(
|
||||||
user_id=sample_user.id,
|
user_id=sample_user.id,
|
||||||
@@ -268,7 +268,7 @@ async def sample_conversation(db_session: AsyncSession, sample_user):
|
|||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def sample_bot_state(db_session: AsyncSession):
|
async def sample_bot_state(db_session: AsyncSession):
|
||||||
"""Create a sample bot state."""
|
"""Create a sample bot state."""
|
||||||
from daemon_boyfriend.models import BotState
|
from loyal_companion.models import BotState
|
||||||
|
|
||||||
bot_state = BotState(
|
bot_state = BotState(
|
||||||
guild_id=111222333,
|
guild_id=111222333,
|
||||||
@@ -284,7 +284,7 @@ async def sample_bot_state(db_session: AsyncSession):
|
|||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def sample_user_relationship(db_session: AsyncSession, sample_user):
|
async def sample_user_relationship(db_session: AsyncSession, sample_user):
|
||||||
"""Create a sample user relationship."""
|
"""Create a sample user relationship."""
|
||||||
from daemon_boyfriend.models import UserRelationship
|
from loyal_companion.models import UserRelationship
|
||||||
|
|
||||||
relationship = UserRelationship(
|
relationship = UserRelationship(
|
||||||
user_id=sample_user.id,
|
user_id=sample_user.id,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from datetime import datetime, timedelta, timezone
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from daemon_boyfriend.models import (
|
from loyal_companion.models import (
|
||||||
BotOpinion,
|
BotOpinion,
|
||||||
BotState,
|
BotState,
|
||||||
Conversation,
|
Conversation,
|
||||||
@@ -20,7 +20,7 @@ from daemon_boyfriend.models import (
|
|||||||
UserPreference,
|
UserPreference,
|
||||||
UserRelationship,
|
UserRelationship,
|
||||||
)
|
)
|
||||||
from daemon_boyfriend.models.base import utc_now
|
from loyal_companion.models.base import utc_now
|
||||||
|
|
||||||
|
|
||||||
class TestUtcNow:
|
class TestUtcNow:
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from daemon_boyfriend.services.providers.anthropic import AnthropicProvider
|
from loyal_companion.services.providers.anthropic import AnthropicProvider
|
||||||
from daemon_boyfriend.services.providers.base import (
|
from loyal_companion.services.providers.base import (
|
||||||
AIProvider,
|
AIProvider,
|
||||||
AIResponse,
|
AIResponse,
|
||||||
ImageAttachment,
|
ImageAttachment,
|
||||||
Message,
|
Message,
|
||||||
)
|
)
|
||||||
from daemon_boyfriend.services.providers.gemini import GeminiProvider
|
from loyal_companion.services.providers.gemini import GeminiProvider
|
||||||
from daemon_boyfriend.services.providers.openai import OpenAIProvider
|
from loyal_companion.services.providers.openai import OpenAIProvider
|
||||||
from daemon_boyfriend.services.providers.openrouter import OpenRouterProvider
|
from loyal_companion.services.providers.openrouter import OpenRouterProvider
|
||||||
|
|
||||||
|
|
||||||
class TestMessage:
|
class TestMessage:
|
||||||
@@ -69,7 +69,7 @@ class TestOpenAIProvider:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def provider(self, mock_openai_client):
|
def provider(self, mock_openai_client):
|
||||||
"""Create an OpenAI provider with mocked client."""
|
"""Create an OpenAI provider with mocked client."""
|
||||||
with patch("daemon_boyfriend.services.providers.openai.AsyncOpenAI") as mock_class:
|
with patch("loyal_companion.services.providers.openai.AsyncOpenAI") as mock_class:
|
||||||
mock_class.return_value = mock_openai_client
|
mock_class.return_value = mock_openai_client
|
||||||
provider = OpenAIProvider(api_key="test_key", model="gpt-4o-mini")
|
provider = OpenAIProvider(api_key="test_key", model="gpt-4o-mini")
|
||||||
provider.client = mock_openai_client
|
provider.client = mock_openai_client
|
||||||
@@ -143,7 +143,7 @@ class TestAnthropicProvider:
|
|||||||
def provider(self, mock_anthropic_client):
|
def provider(self, mock_anthropic_client):
|
||||||
"""Create an Anthropic provider with mocked client."""
|
"""Create an Anthropic provider with mocked client."""
|
||||||
with patch(
|
with patch(
|
||||||
"daemon_boyfriend.services.providers.anthropic.anthropic.AsyncAnthropic"
|
"loyal_companion.services.providers.anthropic.anthropic.AsyncAnthropic"
|
||||||
) as mock_class:
|
) as mock_class:
|
||||||
mock_class.return_value = mock_anthropic_client
|
mock_class.return_value = mock_anthropic_client
|
||||||
provider = AnthropicProvider(api_key="test_key", model="claude-sonnet-4-20250514")
|
provider = AnthropicProvider(api_key="test_key", model="claude-sonnet-4-20250514")
|
||||||
@@ -200,7 +200,7 @@ class TestGeminiProvider:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def provider(self, mock_gemini_client):
|
def provider(self, mock_gemini_client):
|
||||||
"""Create a Gemini provider with mocked client."""
|
"""Create a Gemini provider with mocked client."""
|
||||||
with patch("daemon_boyfriend.services.providers.gemini.genai.Client") as mock_class:
|
with patch("loyal_companion.services.providers.gemini.genai.Client") as mock_class:
|
||||||
mock_class.return_value = mock_gemini_client
|
mock_class.return_value = mock_gemini_client
|
||||||
provider = GeminiProvider(api_key="test_key", model="gemini-2.0-flash")
|
provider = GeminiProvider(api_key="test_key", model="gemini-2.0-flash")
|
||||||
provider.client = mock_gemini_client
|
provider.client = mock_gemini_client
|
||||||
@@ -248,7 +248,7 @@ class TestOpenRouterProvider:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def provider(self, mock_openai_client):
|
def provider(self, mock_openai_client):
|
||||||
"""Create an OpenRouter provider with mocked client."""
|
"""Create an OpenRouter provider with mocked client."""
|
||||||
with patch("daemon_boyfriend.services.providers.openrouter.AsyncOpenAI") as mock_class:
|
with patch("loyal_companion.services.providers.openrouter.AsyncOpenAI") as mock_class:
|
||||||
mock_class.return_value = mock_openai_client
|
mock_class.return_value = mock_openai_client
|
||||||
provider = OpenRouterProvider(api_key="test_key", model="openai/gpt-4o")
|
provider = OpenRouterProvider(api_key="test_key", model="openai/gpt-4o")
|
||||||
provider.client = mock_openai_client
|
provider.client = mock_openai_client
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from daemon_boyfriend.models import (
|
from loyal_companion.models import (
|
||||||
BotOpinion,
|
BotOpinion,
|
||||||
BotState,
|
BotState,
|
||||||
Conversation,
|
Conversation,
|
||||||
@@ -14,14 +14,14 @@ from daemon_boyfriend.models import (
|
|||||||
UserFact,
|
UserFact,
|
||||||
UserRelationship,
|
UserRelationship,
|
||||||
)
|
)
|
||||||
from daemon_boyfriend.services.ai_service import AIService
|
from loyal_companion.services.ai_service import AIService
|
||||||
from daemon_boyfriend.services.fact_extraction_service import FactExtractionService
|
from loyal_companion.services.fact_extraction_service import FactExtractionService
|
||||||
from daemon_boyfriend.services.mood_service import MoodLabel, MoodService, MoodState
|
from loyal_companion.services.mood_service import MoodLabel, MoodService, MoodState
|
||||||
from daemon_boyfriend.services.opinion_service import OpinionService, extract_topics_from_message
|
from loyal_companion.services.opinion_service import OpinionService, extract_topics_from_message
|
||||||
from daemon_boyfriend.services.persistent_conversation import PersistentConversationManager
|
from loyal_companion.services.persistent_conversation import PersistentConversationManager
|
||||||
from daemon_boyfriend.services.relationship_service import RelationshipLevel, RelationshipService
|
from loyal_companion.services.relationship_service import RelationshipLevel, RelationshipService
|
||||||
from daemon_boyfriend.services.self_awareness_service import SelfAwarenessService
|
from loyal_companion.services.self_awareness_service import SelfAwarenessService
|
||||||
from daemon_boyfriend.services.user_service import UserService
|
from loyal_companion.services.user_service import UserService
|
||||||
|
|
||||||
|
|
||||||
class TestUserService:
|
class TestUserService:
|
||||||
@@ -577,8 +577,8 @@ class TestAIService:
|
|||||||
|
|
||||||
def test_get_system_prompt_default(self, mock_settings):
|
def test_get_system_prompt_default(self, mock_settings):
|
||||||
"""Test getting default system prompt."""
|
"""Test getting default system prompt."""
|
||||||
with patch("daemon_boyfriend.services.ai_service.settings", mock_settings):
|
with patch("loyal_companion.services.ai_service.settings", mock_settings):
|
||||||
with patch("daemon_boyfriend.services.ai_service.AIService._init_provider"):
|
with patch("loyal_companion.services.ai_service.AIService._init_provider"):
|
||||||
service = AIService(mock_settings)
|
service = AIService(mock_settings)
|
||||||
service._provider = MagicMock()
|
service._provider = MagicMock()
|
||||||
|
|
||||||
@@ -590,8 +590,8 @@ class TestAIService:
|
|||||||
def test_get_system_prompt_custom(self, mock_settings):
|
def test_get_system_prompt_custom(self, mock_settings):
|
||||||
"""Test getting custom system prompt."""
|
"""Test getting custom system prompt."""
|
||||||
mock_settings.system_prompt = "Custom prompt"
|
mock_settings.system_prompt = "Custom prompt"
|
||||||
with patch("daemon_boyfriend.services.ai_service.settings", mock_settings):
|
with patch("loyal_companion.services.ai_service.settings", mock_settings):
|
||||||
with patch("daemon_boyfriend.services.ai_service.AIService._init_provider"):
|
with patch("loyal_companion.services.ai_service.AIService._init_provider"):
|
||||||
service = AIService(mock_settings)
|
service = AIService(mock_settings)
|
||||||
service._provider = MagicMock()
|
service._provider = MagicMock()
|
||||||
|
|
||||||
@@ -601,8 +601,8 @@ class TestAIService:
|
|||||||
|
|
||||||
def test_provider_name(self, mock_settings):
|
def test_provider_name(self, mock_settings):
|
||||||
"""Test getting provider name."""
|
"""Test getting provider name."""
|
||||||
with patch("daemon_boyfriend.services.ai_service.settings", mock_settings):
|
with patch("loyal_companion.services.ai_service.settings", mock_settings):
|
||||||
with patch("daemon_boyfriend.services.ai_service.AIService._init_provider"):
|
with patch("loyal_companion.services.ai_service.AIService._init_provider"):
|
||||||
service = AIService(mock_settings)
|
service = AIService(mock_settings)
|
||||||
mock_provider = MagicMock()
|
mock_provider = MagicMock()
|
||||||
mock_provider.provider_name = "openai"
|
mock_provider.provider_name = "openai"
|
||||||
@@ -612,8 +612,8 @@ class TestAIService:
|
|||||||
|
|
||||||
def test_model_property(self, mock_settings):
|
def test_model_property(self, mock_settings):
|
||||||
"""Test getting model name."""
|
"""Test getting model name."""
|
||||||
with patch("daemon_boyfriend.services.ai_service.settings", mock_settings):
|
with patch("loyal_companion.services.ai_service.settings", mock_settings):
|
||||||
with patch("daemon_boyfriend.services.ai_service.AIService._init_provider"):
|
with patch("loyal_companion.services.ai_service.AIService._init_provider"):
|
||||||
service = AIService(mock_settings)
|
service = AIService(mock_settings)
|
||||||
service._provider = MagicMock()
|
service._provider = MagicMock()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user