- Fix remaining datetime timezone errors across all database operations - Implement dynamic vector database backend (Qdrant/ChromaDB) based on install.py configuration - Add LLM timeout handling with immediate fallback responses for slow self-hosted models - Use proper install.py configuration (2000 max tokens, 5min timeout, correct LLM endpoint) - Fix PostgreSQL schema to use timezone-aware columns throughout - Implement async LLM request handling with background processing - Add configurable prompt limits and conversation history controls - Start missing database services (PostgreSQL, Redis) automatically - Fix environment variable mapping between install.py and application code - Resolve all timezone-naive vs timezone-aware datetime conflicts System now properly uses Qdrant vector database as specified in install.py instead of hardcoded ChromaDB. Characters respond immediately with fallback messages during long LLM processing times. All database timezone errors resolved with proper timestamptz columns.
170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
"""
|
|
System service for monitoring and controlling the fishbowl system
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Dict, List, Any, Optional
|
|
import psutil
|
|
import json
|
|
|
|
from admin.models import SystemStatus, SystemStatusEnum, SystemConfiguration, LogEntry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class SystemService:
|
|
"""Service for system monitoring and control"""
|
|
|
|
def __init__(self):
|
|
self.system_state = SystemStatusEnum.RUNNING
|
|
self.start_time = datetime.now(timezone.utc)
|
|
self.error_count = 0
|
|
self.warnings_count = 0
|
|
self.log_buffer = []
|
|
|
|
@classmethod
|
|
async def initialize(cls):
|
|
"""Initialize system service"""
|
|
logger.info("System service initialized")
|
|
|
|
async def get_status(self) -> SystemStatus:
|
|
"""Get current system status"""
|
|
try:
|
|
uptime_seconds = (datetime.now(timezone.utc) - self.start_time).total_seconds()
|
|
uptime_str = self._format_uptime(uptime_seconds)
|
|
|
|
# Get resource usage
|
|
memory = psutil.virtual_memory()
|
|
cpu_percent = psutil.cpu_percent(interval=1)
|
|
|
|
resource_usage = {
|
|
"cpu_percent": cpu_percent,
|
|
"memory_total_mb": memory.total // (1024 * 1024),
|
|
"memory_used_mb": memory.used // (1024 * 1024),
|
|
"memory_percent": memory.percent
|
|
}
|
|
|
|
# Performance metrics
|
|
performance_metrics = {
|
|
"avg_response_time": 2.5, # Would track actual response times
|
|
"requests_per_minute": 30, # Would track actual request rate
|
|
"database_query_time": 0.05 # Would track actual DB performance
|
|
}
|
|
|
|
return SystemStatus(
|
|
status=self.system_state,
|
|
uptime=uptime_str,
|
|
version="1.0.0",
|
|
database_status="healthy",
|
|
redis_status="healthy",
|
|
llm_service_status="healthy",
|
|
discord_bot_status="connected",
|
|
active_processes=["main", "conversation_engine", "scheduler", "admin_interface"],
|
|
error_count=self.error_count,
|
|
warnings_count=self.warnings_count,
|
|
performance_metrics=performance_metrics,
|
|
resource_usage=resource_usage
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting system status: {e}")
|
|
return SystemStatus(
|
|
status=SystemStatusEnum.ERROR,
|
|
uptime="unknown",
|
|
version="1.0.0",
|
|
database_status="error",
|
|
redis_status="unknown",
|
|
llm_service_status="unknown",
|
|
discord_bot_status="unknown",
|
|
active_processes=[],
|
|
error_count=self.error_count + 1,
|
|
warnings_count=self.warnings_count,
|
|
performance_metrics={},
|
|
resource_usage={}
|
|
)
|
|
|
|
async def pause_system(self):
|
|
"""Pause the entire system"""
|
|
try:
|
|
logger.info("Pausing system operations")
|
|
self.system_state = SystemStatusEnum.PAUSED
|
|
# Would integrate with main application to pause operations
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error pausing system: {e}")
|
|
raise
|
|
|
|
async def resume_system(self):
|
|
"""Resume system operations"""
|
|
try:
|
|
logger.info("Resuming system operations")
|
|
self.system_state = SystemStatusEnum.RUNNING
|
|
# Would integrate with main application to resume operations
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error resuming system: {e}")
|
|
raise
|
|
|
|
async def get_configuration(self) -> SystemConfiguration:
|
|
"""Get system configuration"""
|
|
# Default configuration values
|
|
return SystemConfiguration(
|
|
conversation_frequency=0.5,
|
|
response_delay_min=1.0,
|
|
response_delay_max=5.0,
|
|
personality_change_rate=0.1,
|
|
memory_retention_days=90,
|
|
max_conversation_length=50,
|
|
creativity_boost=True,
|
|
conflict_resolution_enabled=True,
|
|
safety_monitoring=True,
|
|
auto_moderation=False,
|
|
backup_frequency_hours=24
|
|
)
|
|
|
|
async def update_configuration(self, config: Dict[str, Any]):
|
|
"""Update system configuration"""
|
|
try:
|
|
logger.info(f"Updating system configuration: {config}")
|
|
# Would integrate with main application to update configuration
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating configuration: {e}")
|
|
raise
|
|
|
|
async def get_logs(self, limit: int = 100, level: Optional[str] = None) -> List[LogEntry]:
|
|
"""Get system logs"""
|
|
try:
|
|
# In production, this would read from actual log files
|
|
sample_logs = [
|
|
LogEntry(
|
|
timestamp=datetime.now(timezone.utc) - timedelta(minutes=i),
|
|
level="INFO" if i % 3 != 0 else "DEBUG",
|
|
component="conversation_engine",
|
|
message=f"Sample log message {i}",
|
|
metadata={"log_id": i}
|
|
)
|
|
for i in range(min(limit, 50))
|
|
]
|
|
|
|
if level:
|
|
sample_logs = [log for log in sample_logs if log.level == level.upper()]
|
|
|
|
return sample_logs
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting logs: {e}")
|
|
return []
|
|
|
|
def _format_uptime(self, seconds: float) -> str:
|
|
"""Format uptime in human-readable format"""
|
|
days, remainder = divmod(int(seconds), 86400)
|
|
hours, remainder = divmod(remainder, 3600)
|
|
minutes, seconds = divmod(remainder, 60)
|
|
|
|
if days > 0:
|
|
return f"{days}d {hours}h {minutes}m"
|
|
elif hours > 0:
|
|
return f"{hours}h {minutes}m"
|
|
else:
|
|
return f"{minutes}m {seconds}s" |