Files
latte dbd534d860 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.
2026-01-14 18:08:35 +01:00
..

Services Reference

This document provides detailed API documentation for all services in the Daemon Boyfriend bot.

Table of Contents


Core Services

AIService

File: services/ai_service.py

Factory and facade for AI providers. Manages provider creation, switching, and provides a unified interface for generating responses.

Initialization

from loyal_companion.services.ai_service import AIService
from loyal_companion.config import settings

# Use default settings
ai_service = AIService()

# Or with custom settings
ai_service = AIService(config=custom_settings)

Properties

Property Type Description
provider AIProvider Current AI provider instance
provider_name str Name of current provider
model str Current model name

Methods

chat(messages, system_prompt) -> AIResponse

Generate a chat response.

from loyal_companion.services.providers import Message

response = await ai_service.chat(
    messages=[
        Message(role="user", content="Hello!"),
    ],
    system_prompt="You are a helpful assistant."
)

print(response.content)  # AI's response
print(response.model)    # Model used
print(response.usage)    # Token usage
get_system_prompt() -> str

Get the base system prompt for the bot.

prompt = ai_service.get_system_prompt()
# Returns: "You are Daemon, a friendly and helpful Discord bot..."
get_enhanced_system_prompt(...) -> str

Build system prompt with all personality modifiers.

enhanced_prompt = ai_service.get_enhanced_system_prompt(
    mood=mood_state,
    relationship=(relationship_level, relationship_record),
    communication_style=user_style,
    bot_opinions=relevant_opinions
)

Parameters:

Parameter Type Description
mood MoodState | None Current mood state
relationship tuple[RelationshipLevel, UserRelationship] | None Relationship info
communication_style UserCommunicationStyle | None User's preferences
bot_opinions list[BotOpinion] | None Relevant opinions

DatabaseService

File: services/database.py

Manages database connections and sessions.

Global Instance

from loyal_companion.services.database import db, get_db

# Get global instance
db_service = get_db()

Properties

Property Type Description
is_configured bool Whether DATABASE_URL is set
is_initialized bool Whether connection is initialized

Methods

init() -> None

Initialize database connection.

await db.init()
close() -> None

Close database connection.

await db.close()
create_tables() -> None

Create all tables from schema.sql.

await db.create_tables()
session() -> AsyncGenerator[AsyncSession, None]

Get a database session with automatic commit/rollback.

async with db.session() as session:
    # Use session for database operations
    user = await session.execute(select(User).where(...))
    # Auto-commits on exit, auto-rollbacks on exception

UserService

File: services/user_service.py

Service for user-related operations.

Initialization

from loyal_companion.services.user_service import UserService

async with db.session() as session:
    user_service = UserService(session)

Methods

get_or_create_user(discord_id, username, display_name) -> User

Get existing user or create new one.

user = await user_service.get_or_create_user(
    discord_id=123456789,
    username="john_doe",
    display_name="John"
)
get_user_by_discord_id(discord_id) -> User | None

Get a user by their Discord ID.

user = await user_service.get_user_by_discord_id(123456789)
set_custom_name(discord_id, custom_name) -> User | None

Set a custom name for a user.

user = await user_service.set_custom_name(123456789, "Johnny")
# Or clear custom name
user = await user_service.set_custom_name(123456789, None)
add_fact(user, fact_type, fact_content, source, confidence) -> UserFact

Add a fact about a user.

fact = await user_service.add_fact(
    user=user,
    fact_type="hobby",
    fact_content="enjoys playing chess",
    source="conversation",
    confidence=0.9
)
get_user_facts(user, fact_type, active_only) -> list[UserFact]

Get facts about a user.

# All active facts
facts = await user_service.get_user_facts(user)

# Only hobby facts
hobby_facts = await user_service.get_user_facts(user, fact_type="hobby")

# Including inactive facts
all_facts = await user_service.get_user_facts(user, active_only=False)
delete_user_facts(user) -> int

Soft-delete all facts for a user.

count = await user_service.delete_user_facts(user)
print(f"Deactivated {count} facts")
set_preference(user, key, value) -> UserPreference

Set a user preference.

await user_service.set_preference(user, "theme", "dark")
get_preference(user, key) -> str | None

Get a user preference value.

theme = await user_service.get_preference(user, "theme")
get_user_context(user) -> str

Build a context string about a user for the AI.

context = await user_service.get_user_context(user)
# Returns:
# User's preferred name: John
# (You should address them as: Johnny)
# 
# Known facts about this user:
# - [hobby] enjoys playing chess
# - [work] software developer
get_user_with_facts(discord_id) -> User | None

Get a user with their facts eagerly loaded.

user = await user_service.get_user_with_facts(123456789)
print(user.facts)  # Facts already loaded

ConversationManager

File: services/conversation.py

In-memory conversation history manager (used when no database).

Initialization

from loyal_companion.services.conversation import ConversationManager

conversation_manager = ConversationManager(max_history=50)

Methods

add_message(user_id, role, content) -> None

Add a message to a user's conversation history.

conversation_manager.add_message(
    user_id=123456789,
    role="user",
    content="Hello!"
)
get_history(user_id) -> list[dict]

Get conversation history for a user.

history = conversation_manager.get_history(123456789)
# Returns: [{"role": "user", "content": "Hello!"}, ...]
clear_history(user_id) -> None

Clear conversation history for a user.

conversation_manager.clear_history(123456789)

PersistentConversationManager

File: services/persistent_conversation.py

Database-backed conversation manager.

Key Features

  • Conversations persist across restarts
  • Timeout-based conversation separation (60 min default)
  • Per-guild conversation tracking

Methods

get_or_create_conversation(session, user, guild_id) -> Conversation

Get active conversation or create new one.

conversation = await pcm.get_or_create_conversation(
    session=session,
    user=user,
    guild_id=123456789
)
add_message(session, conversation, role, content) -> Message

Add a message to a conversation.

message = await pcm.add_message(
    session=session,
    conversation=conversation,
    role="user",
    content="Hello!"
)
get_recent_messages(session, conversation, limit) -> list[Message]

Get recent messages from a conversation.

messages = await pcm.get_recent_messages(
    session=session,
    conversation=conversation,
    limit=20
)

SearXNGService

File: services/searxng.py

Web search integration using SearXNG.

Initialization

from loyal_companion.services.searxng import SearXNGService

searxng = SearXNGService()

Properties

Property Type Description
is_configured bool Whether SEARXNG_URL is set

Methods

search(query, num_results) -> list[SearchResult]

Search the web for a query.

if searxng.is_configured:
    results = await searxng.search(
        query="Python 3.12 new features",
        num_results=5
    )
    
    for result in results:
        print(f"{result.title}: {result.url}")
        print(f"  {result.snippet}")

MonitoringService

File: services/monitoring.py

Health checks and metrics tracking.

Features

  • Request counting
  • Response time tracking
  • Error rate monitoring
  • Health status checks

Usage

from loyal_companion.services.monitoring import MonitoringService

monitoring = MonitoringService()

# Record a request
monitoring.record_request()

# Record response time
monitoring.record_response_time(0.5)  # 500ms

# Record an error
monitoring.record_error("API timeout")

# Get health status
status = monitoring.get_health_status()
print(status)
# {
#   "status": "healthy",
#   "uptime": 3600,
#   "total_requests": 100,
#   "avg_response_time": 0.3,
#   "error_rate": 0.02,
#   "recent_errors": [...]
# }

AI Providers

Location: services/providers/

Base Classes

from loyal_companion.services.providers import (
    AIProvider,      # Abstract base class
    Message,         # Chat message
    AIResponse,      # Response from provider
    ImageAttachment, # Image attachment
)

Message Class

@dataclass
class Message:
    role: str                    # "user", "assistant", "system"
    content: str                 # Message content
    images: list[ImageAttachment] = []  # Optional image attachments

AIResponse Class

@dataclass
class AIResponse:
    content: str           # Generated response
    model: str            # Model used
    usage: dict[str, int] # Token usage info

Available Providers

Provider Class Description
OpenAI OpenAIProvider GPT models via OpenAI API
OpenRouter OpenRouterProvider Multiple models via OpenRouter
Anthropic AnthropicProvider Claude models via Anthropic API
Gemini GeminiProvider Gemini models via Google API

Provider Interface

All providers implement:

class AIProvider(ABC):
    @abstractmethod
    async def generate(
        self,
        messages: list[Message],
        system_prompt: str | None = None,
        max_tokens: int = 1024,
        temperature: float = 0.7,
    ) -> AIResponse:
        pass
    
    @property
    @abstractmethod
    def provider_name(self) -> str:
        pass

Example: Direct Provider Usage

from loyal_companion.services.providers import OpenAIProvider, Message

provider = OpenAIProvider(
    api_key="sk-...",
    model="gpt-4"
)

response = await provider.generate(
    messages=[Message(role="user", content="Hello!")],
    system_prompt="You are helpful.",
    max_tokens=500,
    temperature=0.7
)

print(response.content)

Living AI Services

See Living AI Documentation for detailed coverage:

Service Documentation
MoodService mood-system.md
RelationshipService relationship-system.md
FactExtractionService fact-extraction.md
OpinionService opinion-system.md
CommunicationStyleService Living AI README
ProactiveService Living AI README
AssociationService Living AI README
SelfAwarenessService Living AI README

Service Dependency Graph

┌────────────────────────────────────────────────────────────────────────┐
│                          AIChatCog                                      │
└────────────────────────────────────────────────────────────────────────┘
                                │
        ┌───────────────────────┼───────────────────────┐
        │                       │                       │
        ▼                       ▼                       ▼
┌───────────────┐      ┌───────────────┐      ┌───────────────┐
│  UserService  │      │  AIService    │      │ Conversation  │
│               │      │               │      │   Manager     │
│ - get_user    │      │ - chat        │      │               │
│ - get_context │      │ - get_prompt  │      │ - get_history │
└───────────────┘      └───────────────┘      └───────────────┘
        │                       │                       │
        │              ┌────────┴────────┐              │
        │              │                 │              │
        │              ▼                 ▼              │
        │      ┌───────────────┐ ┌───────────────┐     │
        │      │   Provider    │ │ Living AI     │     │
        │      │   (OpenAI,    │ │ Services      │     │
        │      │   Anthropic)  │ │               │     │
        │      └───────────────┘ └───────────────┘     │
        │                                              │
        └──────────────────┬───────────────────────────┘
                           │
                           ▼
                   ┌───────────────┐
                   │   Database    │
                   │   Service     │
                   │               │
                   │ - session()   │
                   └───────────────┘