# utils/conversation_history.py """ Centralized conversation history management for Miku bot. Tracks conversation context per server/DM channel. """ from collections import defaultdict, deque from datetime import datetime from typing import Optional, List, Dict, Tuple class ConversationHistory: """Manages conversation history per channel (server or DM).""" def __init__(self, max_messages: int = 8): """ Initialize conversation history manager. Args: max_messages: Maximum number of messages to keep per channel """ self.max_messages = max_messages # Key: channel_id (guild_id for servers, user_id for DMs) # Value: deque of (author_name, content, timestamp, is_bot) tuples self._histories: Dict[str, deque] = defaultdict(lambda: deque(maxlen=max_messages * 2)) def add_message(self, channel_id: str, author_name: str, content: str, is_bot: bool = False): """ Add a message to the conversation history. Args: channel_id: Server ID (for server messages) or user ID (for DMs) author_name: Display name of the message author content: Message content is_bot: Whether this message is from Miku """ # Skip empty messages if not content or not content.strip(): return timestamp = datetime.utcnow() self._histories[channel_id].append((author_name, content.strip(), timestamp, is_bot)) def get_recent_messages(self, channel_id: str, max_messages: Optional[int] = None) -> List[Tuple[str, str, bool]]: """ Get recent messages from a channel. Args: channel_id: Server ID or user ID max_messages: Number of messages to return (default: self.max_messages) Returns: List of (author_name, content, is_bot) tuples, oldest first """ if max_messages is None: max_messages = self.max_messages history = list(self._histories.get(channel_id, [])) # Return only the most recent messages (up to max_messages) recent = history[-max_messages * 2:] if len(history) > max_messages * 2 else history # Return without timestamp for simpler API return [(author, content, is_bot) for author, content, _, is_bot in recent] def format_for_llm(self, channel_id: str, max_messages: Optional[int] = None, max_chars_per_message: int = 500) -> List[Dict[str, str]]: """ Format conversation history for LLM consumption (OpenAI messages format). Args: channel_id: Server ID or user ID max_messages: Number of messages to include (default: self.max_messages) max_chars_per_message: Truncate messages longer than this Returns: List of {"role": "user"|"assistant", "content": str} dicts """ recent = self.get_recent_messages(channel_id, max_messages) messages = [] for author, content, is_bot in recent: # Truncate very long messages if len(content) > max_chars_per_message: content = content[:max_chars_per_message] + "..." # For bot messages, use "assistant" role if is_bot: messages.append({"role": "assistant", "content": content}) else: # For user messages, optionally include author name for multi-user context # Format: "username: message" to help Miku understand who said what if author: formatted_content = f"{author}: {content}" else: formatted_content = content messages.append({"role": "user", "content": formatted_content}) return messages def clear_channel(self, channel_id: str): """Clear all history for a specific channel.""" if channel_id in self._histories: del self._histories[channel_id] def get_channel_stats(self, channel_id: str) -> Dict[str, int]: """Get statistics about a channel's conversation history.""" history = self._histories.get(channel_id, deque()) total_messages = len(history) bot_messages = sum(1 for _, _, _, is_bot in history if is_bot) user_messages = total_messages - bot_messages return { "total_messages": total_messages, "bot_messages": bot_messages, "user_messages": user_messages } # Global instance conversation_history = ConversationHistory(max_messages=8)