- 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.
306 lines
11 KiB
Python
306 lines
11 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.client import 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")
|
|
|
|
# Check LLM availability
|
|
is_available = await llm_client.check_model_availability()
|
|
if not is_available:
|
|
logger.error("LLM model not available. Please check your LLM service.")
|
|
raise RuntimeError("LLM service unavailable")
|
|
|
|
logger.info(f"LLM model '{llm_client.model}' is available")
|
|
|
|
# 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 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)
|
|
|
|
# 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 clean up completed LLM requests"""
|
|
try:
|
|
while not self.shutdown_event.is_set():
|
|
await llm_client.cleanup_pending_requests()
|
|
pending_count = llm_client.get_pending_count()
|
|
|
|
if pending_count > 0:
|
|
logger.debug(f"LLM cleanup: {pending_count} pending background requests")
|
|
|
|
# Wait 30 seconds before next cleanup
|
|
await asyncio.sleep(30)
|
|
|
|
except asyncio.CancelledError:
|
|
logger.info("LLM cleanup 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()) |