Files
discord-fishbowl/src/main.py
root 10563900a3 Implement comprehensive LLM provider system with global cost protection
- 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
2025-07-08 07:35:48 -07:00

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())