Update .gitignore to exclude additional cache files; enhance APIManager message handling and tool response parsing

This commit is contained in:
pacnpal
2025-02-25 09:23:41 -05:00
parent 4f94600375
commit 0e6a0fa770
3 changed files with 142 additions and 61 deletions

2
.gitignore vendored
View File

@@ -33,3 +33,5 @@ discord_glhf/__pycache__/bot.cpython-313.pyc
discord_glhf/handlers/__pycache__/event_handler.cpython-313.pyc
discord_glhf/web/__pycache__/app.cpython-313.pyc
discord_glhf/handlers/__pycache__/function_tools.cpython-313.pyc
discord_glhf/__pycache__/api.cpython-313.pyc
discord_glhf/handlers/__pycache__/tool_handler.cpython-313.pyc

View File

@@ -258,16 +258,96 @@ class APIManager:
# Import tools and tool response parser
from .handlers.function_tools import TOOLS, parse_tool_response
# Prepare request data with OpenAI function calling format
# Ensure first message is a user message
first_user_message = False
for msg in messages:
if msg.get("role") == "user":
first_user_message = True
break
if not first_user_message:
logger.error("No user message found in conversation")
return False, None
# Add our system message after first user message to prevent template error
system_message = {
"role": "system",
"content": """You are DEATHKILLER THE MEGA ASSASSIN! You live on the planet Hell and constantly threaten to kill people. You are full of anger and hate everyone.
Remember:
- Stay aggressive and violent in your responses
- Be direct and confrontational
- Show your murderous rage in every interaction
- Never break character"""
}
# First, organize messages
system_messages = []
user_messages = []
assistant_messages = []
for msg in messages:
role = msg.get("role", "")
if role == "system":
system_messages.append(msg)
elif role == "user":
user_messages.append(msg)
elif role == "assistant":
assistant_messages.append(msg)
# Ensure we have at least one user message
if not user_messages:
logger.error("No user messages found in conversation")
return False, None
# Build conversation in correct order
messages_with_system = []
# 1. Start with system messages
messages_with_system.extend(system_messages)
messages_with_system.append(system_message) # Add our character system message
# 2. Add first user message
messages_with_system.append(user_messages[0])
# 3. Add remaining messages in alternating order
for i in range(len(assistant_messages)):
if i < len(assistant_messages):
messages_with_system.append(assistant_messages[i])
if i + 1 < len(user_messages):
messages_with_system.append(user_messages[i + 1])
# Prepare request data
data = {
"model": params["model"],
"messages": messages,
"messages": messages_with_system,
"temperature": params["temperature"],
"max_tokens": params["max_tokens"],
"stream": False, # Disable streaming for all requests
"tools": TOOLS, # Add function definitions
"tool_choice": "auto" # Let model decide when to use tools
"stream": False
}
# Only add tools after first user message if we have one
if user_messages:
logger.debug("Adding function calling capabilities")
data["tools"] = TOOLS
# Log detailed request information
logger.debug("=== REQUEST DEBUG INFO ===")
logger.debug("Message sequence:")
for i, msg in enumerate(messages_with_system):
logger.debug(f"{i}: {msg.get('role')} - {msg.get('content')[:50]}...")
logger.debug("\nMessage roles sequence:")
logger.debug([msg.get("role") for msg in messages_with_system])
logger.debug("\nTools configuration:")
logger.debug(json.dumps(TOOLS, indent=2))
logger.debug("\nFinal request data:")
debug_data = data.copy()
debug_data["messages"] = [{"role": m.get("role"), "content_length": len(m.get("content", ""))} for m in data["messages"]]
logger.debug(json.dumps(debug_data, indent=2))
logger.debug("========================")
logger.debug(
"API Request Details:\n"
"URL: %s\n"
@@ -335,22 +415,31 @@ class APIManager:
message = choice["message"]
# Check for tool calls first
if message.get("tool_calls"):
# Log full message for debugging
logger.debug(f"Message content: {message.get('content')}")
logger.debug(f"Message tool_calls: {message.get('tool_calls')}")
logger.debug(f"Message function_call: {message.get('function_call')}")
# Check for tool/function calls first
if message.get("tool_calls") or message.get("function_call"):
logger.info("Function calling detected in response")
tool_data = parse_tool_response(json_response)
if tool_data:
logger.debug(f"Found tool calls: {tool_data}")
logger.info(f"Successfully parsed tool calls: {tool_data}")
return True, json.dumps(tool_data)
else:
logger.error("Failed to parse tool calls")
return False, None
# Fall back to regular content if present
# Fall back to regular content
content = message.get("content", "").strip()
if content:
logger.debug(f"Found content: {content[:100]}")
logger.debug(f"Using regular content: {content[:100]}")
return True, content
# No valid content or tool calls
logger.error("No valid content or tool calls found")
logger.debug(f"Full message: {message}")
# No valid output found
logger.error("No valid content or tool calls in response")
logger.debug(f"Full response: {json_response}")
return False, None
except json.JSONDecodeError as e:
logger.error(f"Failed to decode response: {e}")

View File

@@ -1,19 +1,21 @@
#!/usr/bin/env python3
"""Function definitions and tool schemas for LLM function calling."""
from typing import Dict, Any
import json
from typing import Dict, Any, Optional, List
TOOLS = [
{
"type": "function",
"function": {
"name": "mention_user",
"description": "Mention a Discord user in the response",
"description": "Mention a Discord user to get their attention",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Username or nickname to mention"
"description": "The username to mention (without @ symbol)"
}
},
"required": ["name"]
@@ -24,13 +26,13 @@ TOOLS = [
"type": "function",
"function": {
"name": "add_reaction",
"description": "Add an emoji reaction to the message",
"description": "Add an emoji reaction to the last message",
"parameters": {
"type": "object",
"properties": {
"emoji": {
"type": "string",
"description": "The emoji to add (Unicode emoji, custom emoji <:name:id>, or standard emoji :name:)"
"description": "The emoji to add as a reaction (Unicode emoji or Discord emoji name)"
}
},
"required": ["emoji"]
@@ -47,16 +49,11 @@ TOOLS = [
"properties": {
"title": {
"type": "string",
"description": "Title of the embed"
"description": "The title to show at the top of the embed"
},
"description": {
"type": "string",
"description": "Content of the embed"
},
"color": {
"type": "integer",
"description": "Color of the embed in hex format (e.g., 0xFF0000 for red)",
"default": 0xFF0000
"description": "The main content body of the embed"
}
},
"required": ["title", "description"]
@@ -67,17 +64,13 @@ TOOLS = [
"type": "function",
"function": {
"name": "create_thread",
"description": "Create a new discussion thread from the message",
"description": "Start a new discussion thread",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name/topic for the thread. Common patterns: 'X vs Y', '[topic] is overrated/underrated', or topics about safety/maintenance/review"
},
"message_id": {
"type": "integer",
"description": "Optional ID of message to create thread from"
"description": "The topic/title for the new thread"
}
},
"required": ["name"]
@@ -86,34 +79,31 @@ TOOLS = [
}
]
def function_to_tool_call(function_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Convert a function call response to a tool call format."""
return {
"name": function_name,
"arguments": arguments
}
def parse_tool_response(response: Dict[str, Any]) -> Dict[str, Any]:
def parse_tool_response(response: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Parse an API response looking for function calls in tool_calls format."""
if not response or "choices" not in response:
return {}
choice = response["choices"][0]
if "message" not in choice:
return {}
message = choice["message"]
if "tool_calls" not in message:
return {}
tool_calls = []
for tool_call in message["tool_calls"]:
if tool_call["type"] == "function":
# Extract function name and arguments
function_call = {
"name": tool_call["function"]["name"],
"arguments": json.loads(tool_call["function"]["arguments"])
}
tool_calls.append(function_call)
return {"tool_calls": tool_calls} if tool_calls else {}
try:
if not response or "choices" not in response:
return None
choice = response["choices"][0]
if "message" not in choice:
return None
message = choice["message"]
if "tool_calls" not in message:
return None
tool_calls = []
for tool_call in message["tool_calls"]:
if tool_call["type"] == "function":
function = tool_call["function"]
tool_calls.append({
"name": function["name"],
"arguments": json.loads(function["arguments"])
})
return {"tool_calls": tool_calls} if tool_calls else None
except Exception as e:
print(f"Error parsing tool response: {e}")
return None