diff --git a/README.md b/README.md index f9f2f12..0f77a79 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,32 @@ uv pip install -r requirements.txt ## Configuration -All configuration is done through environment variables. Create a `.env` file: +### Environment Variables + +Create a `.env` file with required configuration: + +### System Prompt & Personality + +The bot's personality and behavior are configured through `system_prompt.yaml`. This defines: +- Core personality traits and behavior +- Response patterns and style +- Tool usage guidelines +- Context handling rules + +Example system_prompt.yaml structure: +```yaml +sections: + - title: "Personality" + content: "Define the bot's character and tone" + - title: "Tools" + content: "Define how the bot uses available tools" + - title: "Context" + content: "Define how the bot handles conversation context" +``` + +The system prompt works in conjunction with the available tools to create natural, contextual interactions. When using `!reset_memory`, the bot reloads this configuration while preserving user data and preferences. + +### Environment Variables ```env # Required Environment Variables @@ -191,10 +216,76 @@ The bot includes comprehensive monitoring: - System state 3. Performance Metrics - - Response times - - Queue latency - - API latency - - Database performance + - Response times + - Queue latency + - API latency + - Database performance + +4. Tool Usage Monitoring + - Detection of tool invocations + - Tool execution tracking + - Success/failure logging + - Comprehensive logging for: + - User mentions + - Thread creation + - Emoji reactions + - Emoticon conversions + - Tool usage summaries per message + +## Bot Commands + +### Owner Commands +- `!reset_memory` - Reset the bot's memory back to initial system prompt + - Tracks number of resets per user + - Preserves user interaction history and preferences + - Shows reset count in response + +## Bot Tools & Capabilities + +The bot uses a natural language tool system that allows it to perform actions based on conversation context: + +### Available Tools + +1. **User Mentions** (@username) + - Naturally mention users in conversation + - Example: "Hey @john, what do you think?" + - Automatically resolves nicknames and usernames + +2. **Emoji Reactions** + - Add emoji reactions to messages + - Supports Unicode emojis, custom emojis, and standard Discord emojis + - Example: "That's awesome! 👍" (will add thumbs up reaction) + - Can convert emoticons like :) to proper emojis + +3. **Rich Embeds** + - Create formatted embed messages + - Supports titles, descriptions, and custom colors + - Example: + ``` + [Embed] + Poll Results + First place: X + Second place: Y + [/Embed] + ``` + +4. **Threading** + - Automatically creates discussion threads + - Supports various conversation patterns: + * Comparisons: "X vs Y" + * Reviews: "This coaster is overrated" + * Topics: "Safety discussion" or "Maintenance review" + - Automatically formats thread names for consistency + +### Tool Usage + +The bot uses these tools naturally in conversation without requiring explicit commands. It can: +- Recognize when to mention users based on context +- Add appropriate emoji reactions to messages +- Create organized threads for discussions +- Format information in embeds for better readability + +All tools are used through natural language processing, making interactions feel more conversational and intuitive. ## Running the Bot diff --git a/discord_glhf/__pycache__/bot.cpython-313.pyc b/discord_glhf/__pycache__/bot.cpython-313.pyc index c8ceab0..da38c59 100644 Binary files a/discord_glhf/__pycache__/bot.cpython-313.pyc and b/discord_glhf/__pycache__/bot.cpython-313.pyc differ diff --git a/discord_glhf/bot.py b/discord_glhf/bot.py index 44b62bb..9adbb1d 100644 --- a/discord_glhf/bot.py +++ b/discord_glhf/bot.py @@ -57,11 +57,26 @@ class DiscordBot: try: async with self._init_lock: if not self._initialized: - # Initialize handlers first + # Initialize all handlers first + self.message_handler = MessageHandler(self.db_manager) + logger.info("Message handler initialized") + + self.image_handler = ImageHandler(self.api_manager) + logger.info("Image handler initialized") + self.tool_handler = ToolHandler(self.bot) + logger.info("Tool handler initialized") + self.event_handler = EventHandler( - self.bot, self.queue_manager, self.db_manager, self.api_manager + self.bot, + self.queue_manager, + self.db_manager, + self.api_manager, + message_handler=self.message_handler, + image_handler=self.image_handler, + tool_handler=self.tool_handler ) + logger.info("Event handler initialized with all handlers") # Start API manager if not self.api_manager.is_running: @@ -367,23 +382,40 @@ def run_bot(): """Handle shutdown signals.""" signame = signal.Signals(signum).name logger.info(f"Received signal {signame}") - if bot.bot and bot.bot.loop: - # Use the sync_close method to properly handle shutdown - bot.sync_close() - raise KeyboardInterrupt() # Break the event loop + + # Force stop the event loop + if bot.bot and bot.bot.loop and bot.bot.loop.is_running(): + bot.queue_manager.set_shutting_down() + # Stop the loop immediately + bot.bot.loop.stop() + # This will exit discord.py's run() method + sys.exit(0) # Register signal handlers signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) try: - # Start the bot + # Start the bot with discord.py's runner bot.bot.run(token) - except KeyboardInterrupt: - logger.info("Bot stopped by interrupt") + except (KeyboardInterrupt, SystemExit): + logger.info("Bot is shutting down...") except Exception as e: logger.error(f"Bot crashed: {e}") raise + finally: + # Final cleanup + try: + # Make sure QueueManager is stopped + bot.queue_manager.set_shutting_down() + # Run cleanup in a new event loop + cleanup_loop = asyncio.new_event_loop() + asyncio.set_event_loop(cleanup_loop) + cleanup_loop.run_until_complete(bot.stop()) + cleanup_loop.close() + except Exception as e: + logger.error(f"Error during final cleanup: {e}") + logger.info("Bot shutdown complete") if __name__ == "__main__": diff --git a/discord_glhf/handlers/__pycache__/event_handler.cpython-313.pyc b/discord_glhf/handlers/__pycache__/event_handler.cpython-313.pyc index 0ae6d3b..7cab6e0 100644 Binary files a/discord_glhf/handlers/__pycache__/event_handler.cpython-313.pyc and b/discord_glhf/handlers/__pycache__/event_handler.cpython-313.pyc differ diff --git a/discord_glhf/handlers/__pycache__/tool_handler.cpython-313.pyc b/discord_glhf/handlers/__pycache__/tool_handler.cpython-313.pyc index c312803..1a32f49 100644 Binary files a/discord_glhf/handlers/__pycache__/tool_handler.cpython-313.pyc and b/discord_glhf/handlers/__pycache__/tool_handler.cpython-313.pyc differ diff --git a/discord_glhf/handlers/event_handler.py b/discord_glhf/handlers/event_handler.py index 8aefe3c..80d0beb 100644 --- a/discord_glhf/handlers/event_handler.py +++ b/discord_glhf/handlers/event_handler.py @@ -19,14 +19,15 @@ from .tool_handler import ToolHandler class EventHandler: """Handles Discord events and their processing.""" - def __init__(self, bot, queue_manager, db_manager, api_manager): + def __init__(self, bot, queue_manager, db_manager, api_manager, message_handler=None, image_handler=None, tool_handler=None): self.bot = bot self.queue_manager = queue_manager self.db_manager = db_manager self.api_manager = api_manager - self.message_handler = MessageHandler(db_manager) - self.image_handler = ImageHandler(api_manager) - self.tool_handler = ToolHandler(bot) + # Use provided handlers or create new ones + self.message_handler = message_handler if message_handler else MessageHandler(db_manager) + self.image_handler = image_handler if image_handler else ImageHandler(api_manager) + self.tool_handler = tool_handler if tool_handler else ToolHandler(bot) # Set this handler as the queue's message processor self.queue_manager._message_handler = self._process_message diff --git a/discord_glhf/handlers/tool_handler.py b/discord_glhf/handlers/tool_handler.py index 2fc03fb..5eb4501 100644 --- a/discord_glhf/handlers/tool_handler.py +++ b/discord_glhf/handlers/tool_handler.py @@ -257,8 +257,10 @@ class ToolHandler: # Simple pattern matching for basic tool usage # Let the LLM decide most tool usage through natural language if "@" in line: # Basic mention support + logger.info(f"Tool Use - Parse: Detected mention pattern in line: '{line}'") for match in re.finditer(r"@(\w+(?:\s+\w+)*)", line): name = match.group(1).strip() + logger.info(f"Tool Use - Parse: Adding find_user tool call for '{name}'") tool_calls.append(("find_user", {"name": name})) command_found = True @@ -275,6 +277,7 @@ class ToolHandler: match = re.search(pattern, line, re.IGNORECASE) if match: thread_name = re.sub(pattern, name_format, match.group(1)) + logger.info(f"Tool Use - Parse: Adding create_thread tool call for '{thread_name}'") tool_calls.append(("create_thread", { "channel_id": channel_id, "name": thread_name, @@ -290,6 +293,7 @@ class ToolHandler: for match in emoji_matches: emoji = match.group(1) if emoji.strip(): + logger.info(f"Tool Use - Parse: Adding add_reaction tool call for emoji '{emoji}'") tool_calls.append(("add_reaction", { "emoji": emoji, "message_id": message_id, @@ -307,6 +311,7 @@ class ToolHandler: } for pattern, emoji in emoticon_map.items(): if re.search(pattern, line): + logger.info(f"Tool Use - Parse: Converting emoticon to emoji reaction '{emoji}'") tool_calls.append(("add_reaction", { "emoji": emoji, "message_id": message_id, @@ -321,6 +326,11 @@ class ToolHandler: # Join response lines, removing empty lines at start/end final_response = "\n".join(response_lines).strip() + + # Log summary of detected tools + if tool_calls: + logger.info(f"Tool Use - Parse: Detected {len(tool_calls)} tool calls: {[call[0] for call in tool_calls]}") + return tool_calls, final_response, self.mentioned_users except Exception as e: