diff --git a/discord_glhf/handlers/tool_handler.py b/discord_glhf/handlers/tool_handler.py
index 5eb4501..43f5518 100644
--- a/discord_glhf/handlers/tool_handler.py
+++ b/discord_glhf/handlers/tool_handler.py
@@ -223,6 +223,45 @@ class ToolHandler:
except Exception as e:
logger.error(f"Tool Use - Create Thread: Failed to create thread: {e}")
+ def validate_tool_response(self, response: str) -> bool:
+ """Validate proper tool marker closure.
+
+ Args:
+ response: The response string to validate
+
+ Returns:
+ bool: True if all tool markers are properly closed, False otherwise
+ """
+ from .tools import TOOL_MARKERS
+ try:
+ stack = []
+
+ # Find all tool markers
+ pattern = f"{re.escape(TOOL_MARKERS['start'])}\\w+\\]|{re.escape(TOOL_MARKERS['end'])}"
+ markers = re.finditer(pattern, response)
+
+ for match in markers:
+ marker = match.group(0)
+ if TOOL_MARKERS['end'] in marker:
+ if not stack:
+ logger.warning(f"Tool Use - Validate: Found end marker without matching start: {marker}")
+ return False
+ start_marker = stack.pop()
+ logger.debug(f"Tool Use - Validate: Matched {start_marker} with {marker}")
+ else:
+ stack.append(marker)
+ logger.debug(f"Tool Use - Validate: Found start marker: {marker}")
+
+ if stack:
+ logger.warning(f"Tool Use - Validate: Unclosed tool markers found: {stack}")
+ return False
+
+ return True
+
+ except Exception as e:
+ logger.error(f"Tool Use - Validate: Error validating tool markers: {e}")
+ return False
+
def parse_tool_calls(
self,
response: str,
@@ -236,101 +275,87 @@ class ToolHandler:
message_id: Optional ID of the message being responded to
channel_id: Optional ID of the channel the message is in
"""
+ from .tools import TOOL_MARKERS
+
try:
+ # Validate tool markers first
+ if not self.validate_tool_response(response):
+ logger.warning("Tool Use - Parse: Invalid tool marker structure, skipping tool parsing")
+ return [], response, {}
+
tool_calls = []
- lines = response.split("\n")
- response_lines = []
- i = 0
+ final_response = response
- while i < len(lines):
- line = lines[i].strip()
- if not line:
- i += 1
- continue
-
- # Convert line to lowercase for command checking
- line_lower = line.lower()
- command_found = False
-
- # Process tools based on natural language patterns
+ # Process tool markers in order of appearance
+ for tool_type, markers in TOOL_MARKERS["patterns"].items():
+ pattern = f"{re.escape(markers['start'])}(.*?){re.escape(markers['end'])}"
+ matches = list(re.finditer(pattern, response, re.DOTALL))
- # 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
+ for match in matches:
+ content = match.group(1).strip()
+ if not content:
+ continue
- # Thread creation patterns
- thread_patterns = [
- (r'(?i)(.*?\bvs\.?\b.*?(?:debate|discussion)?)', r'\1 debate'), # X vs Y
- (r'(?i)(.*?\b(?:over|under)rated\b.*?(?:discussion)?)', r'\1 discussion'), # overrated/underrated
- (r'(?i)(.*?\b(?:safety|maintenance|review)\b.*?(?:discussion|thread)?)', r'\1 discussion') # Specific topics
- ]
-
- for pattern, name_format in thread_patterns:
- if re.search(pattern, line_lower):
- # Extract the topic from the line
- 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}'")
+ logger.info(f"Tool Use - Parse: Found {tool_type} tool usage: {content[:50]}...")
+
+ # Process based on tool type
+ if tool_type == "mention":
+ # Extract username from @mention
+ if "@" in content:
+ name = re.search(r"@(\w+(?:\s+\w+)*)", content)
+ if name:
+ name = name.group(1).strip()
+ tool_calls.append(("find_user", {"name": name}))
+ logger.info(f"Tool Use - Parse: Adding find_user tool call for '{name}'")
+
+ elif tool_type == "reaction":
+ # Process emoji reactions
+ emoji_pattern = r'([😀-🙏🌀-🗿]||:[a-zA-Z0-9_]+:)'
+ emoji_matches = re.finditer(emoji_pattern, content)
+ for emoji_match in emoji_matches:
+ emoji = emoji_match.group(1)
+ if emoji.strip():
+ tool_calls.append(("add_reaction", {
+ "emoji": emoji,
+ "message_id": message_id,
+ "channel_id": channel_id
+ }))
+ logger.info(f"Tool Use - Parse: Adding add_reaction tool call for emoji '{emoji}'")
+
+ elif tool_type == "embed":
+ # Process embed content
+ lines = content.strip().split("\n")
+ if lines:
+ title = lines[0]
+ description = "\n".join(lines[1:]) if len(lines) > 1 else ""
+ tool_calls.append(("create_embed", {
+ "title": title,
+ "description": description,
+ "color": 0xFF0000 # Default red color
+ }))
+ logger.info(f"Tool Use - Parse: Adding create_embed tool call with title '{title}'")
+
+ elif tool_type == "thread":
+ # Process thread creation
+ thread_name = content.strip()
+ if thread_name:
tool_calls.append(("create_thread", {
"channel_id": channel_id,
"name": thread_name,
"message_id": message_id
}))
- command_found = True
- break
+ logger.info(f"Tool Use - Parse: Adding create_thread tool call for '{thread_name}'")
- # Support emoji reactions (both explicit and from text)
- # 1. Match Unicode emojis, custom emojis, and standard Discord emojis
- emoji_pattern = r'([😀-🙏🌀-🗿]||:[a-zA-Z0-9_]+:)'
- emoji_matches = re.finditer(emoji_pattern, line)
- 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,
- "channel_id": channel_id
- }))
- command_found = True
+ # Remove the tool marker and its content from the final response
+ final_response = final_response.replace(match.group(0), "")
- # 2. Also detect emoticons and convert them to emojis
- emoticon_map = {
- r'(?:^|\s)[:;]-?[)D](?:\s|$)': '😊', # :) ;) :-) :D
- r'(?:^|\s)[:;]-?[(\[](?:\s|$)': '😢', # :( ;( :-( :[
- r'(?:^|\s)[:;]-?[pP](?:\s|$)': '😛', # :p ;p :-p
- r'(?:^|\s)[:;]-?[oO](?:\s|$)': '😮', # :o ;o :-o
- r'(?:^|\s)[xX][dD](?:\s|$)': '😂', # xD XD
- }
- 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,
- "channel_id": channel_id
- }))
- command_found = True
+ # Clean up the final response
+ final_response = "\n".join(line for line in final_response.split("\n") if line.strip())
- # Always include the line in the response, regardless of commands
- response_lines.append(line)
-
- i += 1
-
- # 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:
diff --git a/discord_glhf/handlers/tools.py b/discord_glhf/handlers/tools.py
index 135cc7e..af62732 100644
--- a/discord_glhf/handlers/tools.py
+++ b/discord_glhf/handlers/tools.py
@@ -1,5 +1,29 @@
"""Tools available for LLM use and their documentation."""
+# Tool Markers Configuration
+TOOL_MARKERS = {
+ "start": "[TOOL:",
+ "end": "[/TOOL]",
+ "patterns": {
+ "mention": {
+ "start": "[TOOL:mention]",
+ "end": "[/TOOL]"
+ },
+ "reaction": {
+ "start": "[TOOL:reaction]",
+ "end": "[/TOOL]"
+ },
+ "embed": {
+ "start": "[TOOL:embed]",
+ "end": "[/TOOL]"
+ },
+ "thread": {
+ "start": "[TOOL:thread]",
+ "end": "[/TOOL]"
+ }
+ }
+}
+
# Tool Definitions
TOOLS = {
"mention_user": {
@@ -7,8 +31,8 @@ TOOLS = {
"parameters": {
"name": "The username or nickname to mention",
},
- "example": "@username",
- "usage": "Simply include @ before the username in your response: @john"
+ "example": "[TOOL:mention]@username[/TOOL]",
+ "usage": "Wrap mentions with [TOOL:mention] markers: [TOOL:mention]@john[/TOOL]"
},
"add_reaction": {
@@ -16,8 +40,8 @@ TOOLS = {
"parameters": {
"emoji": "The emoji to add as a reaction. Can be Unicode emoji, custom emoji (<:name:id>), or standard emoji (:name:)"
},
- "example": "😊 or :smile: or <:custom:123456789>",
- "usage": "Include the emoji naturally in your response text"
+ "example": "[TOOL:reaction]😊[/TOOL] or [TOOL:reaction]:smile:[/TOOL]",
+ "usage": "Wrap emojis with [TOOL:reaction] markers"
},
"create_embed": {
@@ -28,13 +52,13 @@ TOOLS = {
"color": "Optional hex color code (defaults to red)"
},
"example": """
- [Embed]
+ [TOOL:embed]
Title Goes Here
This is the description content
It can span multiple lines
- [/Embed]
+ [/TOOL]
""",
- "usage": "Wrap embed content in [Embed] tags"
+ "usage": "Wrap embed content with [TOOL:embed] markers"
},
"create_thread": {
@@ -43,34 +67,36 @@ TOOLS = {
"name": "The name/topic for the thread",
"message_id": "Optional ID of message to create thread from"
},
- "example": "Let's discuss X vs Y or This coaster is overrated",
- "usage": "Use natural language patterns like 'X vs Y' or include keywords like 'overrated', 'safety', 'maintenance', 'review'"
+ "example": "[TOOL:thread]Let's discuss X vs Y[/TOOL] or [TOOL:thread]This coaster is overrated[/TOOL]",
+ "usage": "Wrap thread topics with [TOOL:thread] markers"
}
}
# Tool Usage Guidelines
USAGE_GUIDELINES = """
-Tools can be used naturally in conversation:
+Tools must be used with explicit markers in conversation:
-1. To mention a user: Just include @ before their name
- Example: "Hey @john, what do you think?"
+1. To mention a user:
+ Example: "Hey [TOOL:mention]@john[/TOOL], what do you think?"
-2. To add reactions: Simply include emojis in your response
- Example: "That's awesome! 👍"
+2. To add reactions:
+ Example: "That's a great point! [TOOL:reaction]👍[/TOOL]"
-3. To create embeds: Use [Embed] tags
- Example:
- [Embed]
- Poll Results
- First place: X
- Second place: Y
- [/Embed]
+3. To create embeds:
+ Example:
+ [TOOL:embed]
+ Poll Results
+ First place: X
+ Second place: Y
+ [/TOOL]
-4. To create threads: Use natural discussion patterns
- Examples:
- - "Let's compare X vs Y"
- - "Is this coaster overrated?"
- - "Time for a safety discussion"
+4. To create threads:
+ Examples:
+ - [TOOL:thread]Let's compare X vs Y[/TOOL]
+ - [TOOL:thread]Is this coaster overrated?[/TOOL]
+ - [TOOL:thread]Time for a safety discussion[/TOOL]
+
+Note: Always ensure tool markers are properly closed with their corresponding end tags.
"""
# Error Messages