- Add multi-provider LLM architecture supporting OpenRouter, OpenAI, Gemini, and custom providers - Implement global LLM on/off switch with default DISABLED state for cost protection - Add per-character LLM configuration with provider-specific models and settings - Create performance-optimized caching system for LLM enabled status checks - Add API key validation before enabling LLM providers to prevent broken configurations - Implement audit logging for all LLM enable/disable actions for cost accountability - Create comprehensive admin UI with prominent cost warnings and confirmation dialogs - Add visual indicators in character list for custom AI model configurations - Build character-specific LLM client system with global fallback mechanism - Add database schema support for per-character LLM settings - Implement graceful fallback responses when LLM is globally disabled - Create provider testing and validation system for reliable connections
323 lines
12 KiB
Python
323 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Discord Fishbowl - Autonomous AI Character Chat System
|
|
Main entry point for the application
|
|
"""
|
|
|
|
import asyncio
|
|
import signal
|
|
import sys
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Add src to Python path
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
|
|
from utils.config import get_settings, validate_environment, setup_logging
|
|
from utils.logging import setup_logging_interceptor
|
|
from database.connection import init_database, create_tables, close_database
|
|
from bot.discord_client import FishbowlBot
|
|
from bot.message_handler import MessageHandler, CommandHandler
|
|
from conversation.engine import ConversationEngine
|
|
from conversation.scheduler import ConversationScheduler
|
|
from llm.multi_provider_client import multi_llm_client, initialize_llm_client
|
|
from rag.vector_store import vector_store_manager
|
|
from rag.community_knowledge import initialize_community_knowledge_rag
|
|
from rag.memory_sharing import MemorySharingManager
|
|
from collaboration.creative_projects import CollaborativeCreativeManager
|
|
from mcp_servers.self_modification_server import mcp_server
|
|
from mcp_servers.file_system_server import filesystem_server
|
|
from mcp_servers.calendar_server import calendar_server
|
|
from mcp_servers.memory_sharing_server import MemorySharingMCPServer
|
|
from mcp_servers.creative_projects_server import initialize_creative_projects_mcp_server
|
|
import logging
|
|
|
|
# Setup logging first
|
|
logger = setup_logging()
|
|
setup_logging_interceptor()
|
|
|
|
class FishbowlApplication:
|
|
"""Main application class"""
|
|
|
|
def __init__(self):
|
|
self.settings = None
|
|
self.conversation_engine = None
|
|
self.scheduler = None
|
|
self.discord_bot = None
|
|
self.message_handler = None
|
|
self.command_handler = None
|
|
self.shutdown_event = asyncio.Event()
|
|
|
|
# RAG and MCP systems
|
|
self.vector_store = None
|
|
self.community_knowledge = None
|
|
self.memory_sharing_manager = None
|
|
self.creative_manager = None
|
|
self.mcp_servers = []
|
|
|
|
async def initialize(self):
|
|
"""Initialize all components"""
|
|
try:
|
|
logger.info("Starting Discord Fishbowl initialization...")
|
|
|
|
# Validate environment
|
|
validate_environment()
|
|
|
|
# Load settings
|
|
self.settings = get_settings()
|
|
logger.info("Configuration loaded successfully")
|
|
|
|
# Initialize database
|
|
await init_database()
|
|
await create_tables()
|
|
logger.info("Database initialized")
|
|
|
|
# Initialize multi-provider LLM client
|
|
logger.info("Initializing multi-provider LLM system...")
|
|
await initialize_llm_client()
|
|
|
|
# Check provider health (non-blocking)
|
|
health_status = await multi_llm_client.health_check()
|
|
provider_info = multi_llm_client.get_provider_info()
|
|
|
|
healthy_providers = [name for name, healthy in health_status.items() if healthy]
|
|
if healthy_providers:
|
|
current_provider = multi_llm_client.get_current_provider()
|
|
logger.info(f"LLM providers available: {healthy_providers}")
|
|
logger.info(f"Current primary provider: {current_provider}")
|
|
else:
|
|
logger.warning("No LLM providers are healthy! Bot will continue and retry connections.")
|
|
|
|
# Initialize RAG systems
|
|
logger.info("Initializing RAG systems...")
|
|
|
|
# Initialize vector store
|
|
self.vector_store = vector_store_manager
|
|
character_names = ["Alex", "Sage", "Luna", "Echo"] # From config
|
|
await self.vector_store.initialize(character_names)
|
|
logger.info("Vector store initialized")
|
|
|
|
# Initialize community knowledge RAG
|
|
self.community_knowledge = initialize_community_knowledge_rag(self.vector_store)
|
|
await self.community_knowledge.initialize(character_names)
|
|
logger.info("Community knowledge RAG initialized")
|
|
|
|
# Initialize memory sharing manager
|
|
self.memory_sharing_manager = MemorySharingManager(self.vector_store)
|
|
await self.memory_sharing_manager.initialize(character_names)
|
|
logger.info("Memory sharing manager initialized")
|
|
|
|
# Initialize collaborative creative manager
|
|
self.creative_manager = CollaborativeCreativeManager(self.vector_store, self.memory_sharing_manager)
|
|
await self.creative_manager.initialize(character_names)
|
|
logger.info("Collaborative creative manager initialized")
|
|
|
|
# Initialize MCP servers
|
|
logger.info("Initializing MCP servers...")
|
|
|
|
# Initialize self-modification server
|
|
self.mcp_servers.append(mcp_server)
|
|
logger.info("Self-modification MCP server initialized")
|
|
|
|
# Initialize file system server
|
|
await filesystem_server.initialize(self.vector_store, character_names)
|
|
self.mcp_servers.append(filesystem_server)
|
|
logger.info("File system MCP server initialized")
|
|
|
|
# Initialize calendar/time awareness server
|
|
await calendar_server.initialize(character_names)
|
|
self.mcp_servers.append(calendar_server)
|
|
logger.info("Calendar/time awareness MCP server initialized")
|
|
|
|
# Initialize memory sharing MCP server
|
|
memory_sharing_mcp = MemorySharingMCPServer(self.memory_sharing_manager)
|
|
self.mcp_servers.append(memory_sharing_mcp)
|
|
logger.info("Memory sharing MCP server initialized")
|
|
|
|
# Initialize creative projects MCP server
|
|
creative_projects_mcp = initialize_creative_projects_mcp_server(self.creative_manager)
|
|
self.mcp_servers.append(creative_projects_mcp)
|
|
logger.info("Creative projects MCP server initialized")
|
|
|
|
# Initialize conversation engine with RAG and MCP systems
|
|
self.conversation_engine = ConversationEngine(
|
|
vector_store=self.vector_store,
|
|
memory_sharing_manager=self.memory_sharing_manager,
|
|
creative_manager=self.creative_manager,
|
|
mcp_servers=self.mcp_servers
|
|
)
|
|
logger.info("Conversation engine created with enhanced capabilities")
|
|
|
|
# Initialize scheduler
|
|
self.scheduler = ConversationScheduler(self.conversation_engine)
|
|
logger.info("Conversation scheduler created")
|
|
|
|
# Initialize Discord bot
|
|
self.discord_bot = FishbowlBot(self.conversation_engine)
|
|
|
|
# Set global bot instance for status messages
|
|
import bot.discord_client
|
|
bot.discord_client._discord_bot = self.discord_bot
|
|
|
|
# Initialize message and command handlers
|
|
self.message_handler = MessageHandler(self.discord_bot, self.conversation_engine)
|
|
self.command_handler = CommandHandler(self.discord_bot, self.conversation_engine)
|
|
|
|
logger.info("Discord bot and handlers initialized")
|
|
|
|
logger.info("✅ All components initialized successfully")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to initialize application: {e}")
|
|
raise
|
|
|
|
async def start(self):
|
|
"""Start the application"""
|
|
try:
|
|
logger.info("🚀 Starting Discord Fishbowl...")
|
|
|
|
# Start conversation engine
|
|
await self.conversation_engine.initialize(self.discord_bot)
|
|
logger.info("Conversation engine started")
|
|
|
|
# Start scheduler
|
|
await self.scheduler.start()
|
|
logger.info("Conversation scheduler started")
|
|
|
|
# Start LLM cleanup task
|
|
cleanup_task = asyncio.create_task(self._llm_cleanup_loop())
|
|
logger.info("LLM cleanup task started")
|
|
|
|
# Start Discord bot
|
|
bot_task = asyncio.create_task(
|
|
self.discord_bot.start(self.settings.discord.token)
|
|
)
|
|
|
|
# Setup signal handlers
|
|
self._setup_signal_handlers()
|
|
|
|
logger.info("🎉 Discord Fishbowl is now running!")
|
|
logger.info("Characters will start chatting autonomously...")
|
|
|
|
# Wait for shutdown signal or bot completion
|
|
done, pending = await asyncio.wait(
|
|
[bot_task, cleanup_task, asyncio.create_task(self.shutdown_event.wait())],
|
|
return_when=asyncio.FIRST_COMPLETED
|
|
)
|
|
|
|
# Cancel pending tasks
|
|
for task in pending:
|
|
task.cancel()
|
|
try:
|
|
await task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error during application startup: {e}")
|
|
raise
|
|
|
|
async def shutdown(self):
|
|
"""Graceful shutdown"""
|
|
try:
|
|
logger.info("🛑 Shutting down Discord Fishbowl...")
|
|
|
|
# Stop scheduler
|
|
if self.scheduler:
|
|
await self.scheduler.stop()
|
|
logger.info("Conversation scheduler stopped")
|
|
|
|
# Stop conversation engine
|
|
if self.conversation_engine:
|
|
await self.conversation_engine.stop()
|
|
logger.info("Conversation engine stopped")
|
|
|
|
# Close Discord bot
|
|
if self.discord_bot:
|
|
await self.discord_bot.close()
|
|
logger.info("Discord bot disconnected")
|
|
|
|
# Close database connections
|
|
await close_database()
|
|
logger.info("Database connections closed")
|
|
|
|
logger.info("✅ Shutdown completed successfully")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error during shutdown: {e}")
|
|
|
|
def _setup_signal_handlers(self):
|
|
"""Setup signal handlers for graceful shutdown"""
|
|
def signal_handler(signum, frame):
|
|
logger.info(f"Received signal {signum}, initiating shutdown...")
|
|
self.shutdown_event.set()
|
|
|
|
# Handle common shutdown signals
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
# On Windows, handle CTRL+C
|
|
if os.name == 'nt':
|
|
signal.signal(signal.SIGBREAK, signal_handler)
|
|
|
|
async def _llm_cleanup_loop(self):
|
|
"""Background task to monitor LLM provider health"""
|
|
try:
|
|
while not self.shutdown_event.is_set():
|
|
# Check provider health periodically
|
|
health_status = await multi_llm_client.health_check()
|
|
unhealthy_providers = [name for name, healthy in health_status.items() if not healthy]
|
|
|
|
if unhealthy_providers:
|
|
logger.debug(f"Unhealthy LLM providers: {unhealthy_providers}")
|
|
|
|
# Wait 60 seconds before next health check
|
|
await asyncio.sleep(60)
|
|
|
|
except asyncio.CancelledError:
|
|
logger.info("LLM monitoring task cancelled")
|
|
except Exception as e:
|
|
logger.error(f"Error in LLM cleanup loop: {e}")
|
|
|
|
async def main():
|
|
"""Main entry point"""
|
|
app = FishbowlApplication()
|
|
|
|
try:
|
|
# Initialize application
|
|
await app.initialize()
|
|
|
|
# Start application
|
|
await app.start()
|
|
|
|
except KeyboardInterrupt:
|
|
logger.info("Received keyboard interrupt")
|
|
except Exception as e:
|
|
logger.error(f"Application error: {e}")
|
|
return 1
|
|
finally:
|
|
# Ensure cleanup
|
|
await app.shutdown()
|
|
|
|
return 0
|
|
|
|
def cli_main():
|
|
"""CLI entry point"""
|
|
try:
|
|
# Check Python version
|
|
if sys.version_info < (3, 8):
|
|
print("Error: Python 3.8 or higher is required")
|
|
return 1
|
|
|
|
# Run the async main function
|
|
return asyncio.run(main())
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nApplication interrupted by user")
|
|
return 1
|
|
except Exception as e:
|
|
print(f"Fatal error: {e}")
|
|
return 1
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(cli_main()) |