Add tool markers configuration and update usage guidelines for explicit markers

This commit is contained in:
pacnpal
2025-02-24 20:07:52 -05:00
parent 185fe4ce73
commit 57eff11433
2 changed files with 156 additions and 105 deletions

View File

@@ -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?:[a-zA-Z0-9_]+:\d+>|:[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?:[a-zA-Z0-9_]+:\d+>|:[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:

View File

@@ -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