import logging import json from typing import Dict, Any, List from slack_sdk import WebClient from slack_sdk.errors import SlackApiError logger = logging.getLogger(__name__) # Try to import LLM client try: from llm.local_llm_client import chat_completion LLM_AVAILABLE = True logger.info("LLM client loaded successfully") except ImportError as e: logger.warning(f"LLM client not available: {e}") LLM_AVAILABLE = False chat_completion = None def format_tools_for_llm(tool_registry: Dict[str, Any]) -> List[Dict]: """ Convert tool registry to format suitable for LLM. """ tools = [] for tool_name, tool_module in tool_registry.items(): if hasattr(tool_module, 'TOOL_DEFINITION'): tools.append(tool_module.TOOL_DEFINITION) return tools def execute_tool(tool_name: str, tool_args: Dict, tool_registry: Dict) -> str: """ Execute a tool and return the result as a string. """ try: if tool_name not in tool_registry: return f"Error: Tool '{tool_name}' not found" tool_module = tool_registry[tool_name] if not hasattr(tool_module, 'run'): return f"Error: Tool '{tool_name}' has no run function" logger.info(f"Executing tool: {tool_name} with args: {tool_args}") result = tool_module.run(**tool_args) # Convert result to string if needed if isinstance(result, dict): return json.dumps(result, indent=2) return str(result) except Exception as e: logger.error(f"Error executing tool {tool_name}: {e}", exc_info=True) return f"Error executing {tool_name}: {str(e)}" def parse_tool_calls_from_text(response_text: str) -> List[Dict]: """ Parse tool calls from LLM response text. Looks for patterns like: TOOL_CALL: tool_name(arg1="value1", arg2="value2") or JSON format: {"tool": "tool_name", "args": {"arg1": "value1"}} """ tool_calls = [] # Simple pattern matching for tool calls # You can enhance this based on how your LLM formats tool calls # Pattern 1: TOOL_CALL: function_name(args) if "TOOL_CALL:" in response_text: lines = response_text.split('\n') for line in lines: if line.strip().startswith("TOOL_CALL:"): try: # Extract tool call tool_str = line.split("TOOL_CALL:")[1].strip() # Parse it (simplified - you may need better parsing) if "(" in tool_str: tool_name = tool_str.split("(")[0].strip() tool_calls.append({ "name": tool_name, "arguments": {} # You'd need to parse the args }) except: pass return tool_calls def process_mention( event_data: dict, slack_client: WebClient, vector_store: Any, bot_profile: Any, tool_registry: Dict[str, Any] ) -> None: """ Process messages that mention the bot. """ event = event_data.get("event", {}) channel = event.get("channel") user = event.get("user") text = event.get("text", "") ts = event.get("ts") logger.info(f"Processing mention from {user} in {channel}") # Remove bot mention from text from config import BOT_USER_ID clean_text = text.replace(f"<@{BOT_USER_ID}>", "").strip() # Get bot configuration bot_name = getattr(bot_profile, "BOT_IDENTIFIER", "Bot") system_prompt = getattr(bot_profile, "SYSTEM_PROMPT", "You are a helpful AI assistant.") try: # Get RAG context if enabled rag_enabled = getattr(bot_profile, "ENABLE_RAG_INSERT", False) context_messages = [] if rag_enabled: try: similar = vector_store.search_similar(clean_text, limit=3) if similar: context = "\n".join(similar) context_messages.append({ "role": "system", "content": f"Relevant context from previous messages:\n{context}" }) logger.info(f"Added RAG context: {len(similar)} similar messages") except Exception as e: logger.error(f"RAG retrieval error: {e}") # Add tool information to system prompt if tools are available if tool_registry: tool_descriptions = [] for tool_name, tool_module in tool_registry.items(): if hasattr(tool_module, 'TOOL_DEFINITION'): tool_def = tool_module.TOOL_DEFINITION desc = f"- {tool_name}: {tool_def.get('description', 'No description')}" tool_descriptions.append(desc) if tool_descriptions: tools_info = "\n".join(tool_descriptions) enhanced_system_prompt = f"""{system_prompt} You have access to these tools: {tools_info} To use a tool, respond with: USE_TOOL: tool_name Then I will execute it and provide the results.""" else: enhanced_system_prompt = system_prompt else: enhanced_system_prompt = system_prompt logger.info("No tools available for this bot") # Generate response using LLM if available if LLM_AVAILABLE and chat_completion: try: # Build messages for LLM messages = [ {"role": "system", "content": enhanced_system_prompt}, *context_messages, {"role": "user", "content": clean_text} ] logger.info(f"Calling LLM with {len(messages)} messages and {len(tool_registry)} tools available") # Call LLM llm_response = chat_completion(messages) response_text = llm_response.get("content", "Sorry, I couldn't generate a response.") logger.info(f"LLM response: {response_text[:200]}...") # Check if LLM wants to use a tool if "USE_TOOL:" in response_text: lines = response_text.split('\n') for line in lines: if line.strip().startswith("USE_TOOL:"): tool_name = line.split("USE_TOOL:")[1].strip() if tool_name in tool_registry: logger.info(f"LLM requested tool: {tool_name}") # Execute the tool tool_result = execute_tool(tool_name, {}, tool_registry) # Get LLM to process the tool result messages.append({"role": "assistant", "content": response_text}) messages.append({"role": "user", "content": f"Tool result from {tool_name}:\n{tool_result}"}) llm_response = chat_completion(messages) response_text = llm_response.get("content", "Sorry, I couldn't process the tool result.") else: response_text = f"I tried to use the tool '{tool_name}' but it's not available." except Exception as e: logger.error(f"LLM call failed: {e}", exc_info=True) response_text = "Sorry, I encountered an error processing your request." else: # Fallback: simple echo response logger.info("Using fallback echo response (LLM not available)") response_text = f"You said: {clean_text}" if tool_registry: response_text += f"\n\nI have {len(tool_registry)} tools available but LLM is not configured." # Send message to channel slack_client.chat_postMessage( channel=channel, text=response_text ) logger.info(f"Sent response to {channel}") except SlackApiError as e: logger.error(f"Slack API error: {e.response['error']}", exc_info=True) try: slack_client.chat_postMessage( channel=channel, text="Sorry, I encountered a Slack API error." ) except: pass except Exception as e: logger.error(f"Error processing mention: {e}", exc_info=True) try: slack_client.chat_postMessage( channel=channel, text="⚠️ Sorry, I encountered an internal error." ) except: pass