added technical documentation
This commit is contained in:
615
docs/services/README.md
Normal file
615
docs/services/README.md
Normal file
@@ -0,0 +1,615 @@
|
||||
# Services Reference
|
||||
|
||||
This document provides detailed API documentation for all services in the Daemon Boyfriend bot.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Core Services](#core-services)
|
||||
- [AIService](#aiservice)
|
||||
- [DatabaseService](#databaseservice)
|
||||
- [UserService](#userservice)
|
||||
- [ConversationManager](#conversationmanager)
|
||||
- [PersistentConversationManager](#persistentconversationmanager)
|
||||
- [SearXNGService](#searxngservice)
|
||||
- [MonitoringService](#monitoringservice)
|
||||
- [AI Providers](#ai-providers)
|
||||
- [Living AI Services](#living-ai-services)
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.services.ai_service import AIService
|
||||
from daemon_boyfriend.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.
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.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.
|
||||
|
||||
```python
|
||||
await db.init()
|
||||
```
|
||||
|
||||
##### `close() -> None`
|
||||
|
||||
Close database connection.
|
||||
|
||||
```python
|
||||
await db.close()
|
||||
```
|
||||
|
||||
##### `create_tables() -> None`
|
||||
|
||||
Create all tables from `schema.sql`.
|
||||
|
||||
```python
|
||||
await db.create_tables()
|
||||
```
|
||||
|
||||
##### `session() -> AsyncGenerator[AsyncSession, None]`
|
||||
|
||||
Get a database session with automatic commit/rollback.
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
# 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.
|
||||
|
||||
```python
|
||||
count = await user_service.delete_user_facts(user)
|
||||
print(f"Deactivated {count} facts")
|
||||
```
|
||||
|
||||
##### `set_preference(user, key, value) -> UserPreference`
|
||||
|
||||
Set a user preference.
|
||||
|
||||
```python
|
||||
await user_service.set_preference(user, "theme", "dark")
|
||||
```
|
||||
|
||||
##### `get_preference(user, key) -> str | None`
|
||||
|
||||
Get a user preference value.
|
||||
|
||||
```python
|
||||
theme = await user_service.get_preference(user, "theme")
|
||||
```
|
||||
|
||||
##### `get_user_context(user) -> str`
|
||||
|
||||
Build a context string about a user for the AI.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.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.
|
||||
|
||||
```python
|
||||
conversation_manager.add_message(
|
||||
user_id=123456789,
|
||||
role="user",
|
||||
content="Hello!"
|
||||
)
|
||||
```
|
||||
|
||||
##### `get_history(user_id) -> list[dict]`
|
||||
|
||||
Get conversation history for a user.
|
||||
|
||||
```python
|
||||
history = conversation_manager.get_history(123456789)
|
||||
# Returns: [{"role": "user", "content": "Hello!"}, ...]
|
||||
```
|
||||
|
||||
##### `clear_history(user_id) -> None`
|
||||
|
||||
Clear conversation history for a user.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
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.
|
||||
|
||||
```python
|
||||
messages = await pcm.get_recent_messages(
|
||||
session=session,
|
||||
conversation=conversation,
|
||||
limit=20
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### SearXNGService
|
||||
|
||||
**File:** `services/searxng.py`
|
||||
|
||||
Web search integration using SearXNG.
|
||||
|
||||
#### Initialization
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.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.
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.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
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.services.providers import (
|
||||
AIProvider, # Abstract base class
|
||||
Message, # Chat message
|
||||
AIResponse, # Response from provider
|
||||
ImageAttachment, # Image attachment
|
||||
)
|
||||
```
|
||||
|
||||
### Message Class
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Message:
|
||||
role: str # "user", "assistant", "system"
|
||||
content: str # Message content
|
||||
images: list[ImageAttachment] = [] # Optional image attachments
|
||||
```
|
||||
|
||||
### AIResponse Class
|
||||
|
||||
```python
|
||||
@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:
|
||||
|
||||
```python
|
||||
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
|
||||
|
||||
```python
|
||||
from daemon_boyfriend.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](../living-ai/README.md) for detailed coverage:
|
||||
|
||||
| Service | Documentation |
|
||||
|---------|--------------|
|
||||
| MoodService | [mood-system.md](../living-ai/mood-system.md) |
|
||||
| RelationshipService | [relationship-system.md](../living-ai/relationship-system.md) |
|
||||
| FactExtractionService | [fact-extraction.md](../living-ai/fact-extraction.md) |
|
||||
| OpinionService | [opinion-system.md](../living-ai/opinion-system.md) |
|
||||
| CommunicationStyleService | [Living AI README](../living-ai/README.md#5-communication-style-system) |
|
||||
| ProactiveService | [Living AI README](../living-ai/README.md#6-proactive-events-system) |
|
||||
| AssociationService | [Living AI README](../living-ai/README.md#7-association-system) |
|
||||
| SelfAwarenessService | [Living AI README](../living-ai/README.md#8-self-awareness-system) |
|
||||
|
||||
---
|
||||
|
||||
## 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() │
|
||||
└───────────────┘
|
||||
```
|
||||
Reference in New Issue
Block a user