feat: Enhance shell command extraction and response formatting in Teto AI
This commit is contained in:
parent
c747bd9f2f
commit
909616fb60
@ -21,8 +21,8 @@ def extract_shell_command(text):
|
||||
command
|
||||
```
|
||||
|
||||
Returns a tuple of (command, text_without_command) if a command is found,
|
||||
or (None, original_text) if no command is found.
|
||||
Returns a tuple of (command, text_without_command, text_before_command) if a command is found,
|
||||
or (None, original_text, None) if no command is found.
|
||||
"""
|
||||
pattern = r"```shell-command\n(.*?)\n```"
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
@ -30,11 +30,17 @@ def extract_shell_command(text):
|
||||
if match:
|
||||
print(f"[TETO DEBUG] Found shell command: {match.group(1)}")
|
||||
command = match.group(1).strip()
|
||||
|
||||
# Get the text before the command block
|
||||
start_idx = match.start()
|
||||
text_before_command = text[:start_idx].strip() if start_idx > 0 else None
|
||||
|
||||
# Remove the command block from the text
|
||||
text_without_command = re.sub(pattern, "", text, flags=re.DOTALL).strip()
|
||||
return command, text_without_command
|
||||
|
||||
return None, text
|
||||
return command, text_without_command, text_before_command
|
||||
|
||||
return None, text, None
|
||||
|
||||
# In-memory conversation history for Kasane Teto AI (keyed by channel id)
|
||||
_teto_conversations = {}
|
||||
@ -50,7 +56,7 @@ class TetoCog(commands.Cog):
|
||||
self._allow_shell_commands = False # Flag to control shell command tool usage
|
||||
|
||||
async def _execute_shell_command(self, command: str) -> str:
|
||||
"""Executes a shell command and returns its output."""
|
||||
"""Executes a shell command and returns its output, limited to first 5 lines."""
|
||||
try:
|
||||
# Use subprocess.run for simple command execution
|
||||
# Consider security implications of running arbitrary commands
|
||||
@ -63,9 +69,20 @@ class TetoCog(commands.Cog):
|
||||
|
||||
output = ""
|
||||
if stdout:
|
||||
output += f"Stdout:\n{stdout.decode()}\n"
|
||||
# Limit stdout to first 5 lines
|
||||
stdout_lines = stdout.decode().splitlines()
|
||||
limited_stdout = "\n".join(stdout_lines[:5])
|
||||
if len(stdout_lines) > 5:
|
||||
limited_stdout += "\n... (output truncated, showing first 5 lines)"
|
||||
output += f"Stdout:\n{limited_stdout}\n"
|
||||
|
||||
if stderr:
|
||||
output += f"Stderr:\n{stderr.decode()}\n"
|
||||
# Limit stderr to first 5 lines
|
||||
stderr_lines = stderr.decode().splitlines()
|
||||
limited_stderr = "\n".join(stderr_lines[:5])
|
||||
if len(stderr_lines) > 5:
|
||||
limited_stderr += "\n... (output truncated, showing first 5 lines)"
|
||||
output += f"Stderr:\n{limited_stderr}\n"
|
||||
|
||||
if not output:
|
||||
output = "Command executed successfully with no output."
|
||||
@ -136,7 +153,9 @@ class TetoCog(commands.Cog):
|
||||
"Reply to the user in a short, conversational manner, staying in character.\n\n"
|
||||
"You have the ability to run any arbitrary shell commands. \n"
|
||||
"If a user asks you to show the entire output of a command, you can and should do so. \n"
|
||||
"IMPORTANT: When you need to execute a shell command, include it in your response using this exact format:\n"
|
||||
"IMPORTANT: When you need to execute a shell command, you can include a message before the command block. \n"
|
||||
"For example, you can say 'Let me check that for you!' and then include the command block. \n"
|
||||
"Include the command in your response using this exact format:\n"
|
||||
"```shell-command\n"
|
||||
"your_command_here\n"
|
||||
"```\n"
|
||||
@ -166,20 +185,38 @@ class TetoCog(commands.Cog):
|
||||
|
||||
# Check for custom tool call format in the response
|
||||
if self._allow_shell_commands:
|
||||
command, _ = extract_shell_command(ai_content)
|
||||
command, _, text_before_command = extract_shell_command(ai_content)
|
||||
if command:
|
||||
if self._is_dangerous_command(command):
|
||||
tool_result = "Error: Execution was blocked due to a potentially dangerous command."
|
||||
tool_result = "❌ Error: Execution was blocked due to a potentially dangerous command."
|
||||
else:
|
||||
# Execute the shell command
|
||||
tool_result = await self._execute_shell_command(command)
|
||||
|
||||
# Format the response with the AI's message before the command (if any)
|
||||
formatted_response = ai_content
|
||||
if text_before_command:
|
||||
# Replace the original AI content with just the text before the command
|
||||
# plus a formatted command execution message
|
||||
if self._is_dangerous_command(command):
|
||||
formatted_response = f"{text_before_command}\n\n*❌ Command \"{command}\" blocked (potentially dangerous)*\n\n{tool_result}"
|
||||
else:
|
||||
formatted_response = f"{text_before_command}\n\n*✅ Command \"{command}\" executed successfully*\n\n{tool_result}"
|
||||
else:
|
||||
# If there was no text before the command, just show the command execution message
|
||||
if self._is_dangerous_command(command):
|
||||
formatted_response = f"*❌ Command \"{command}\" blocked (potentially dangerous)*\n\n{tool_result}"
|
||||
else:
|
||||
formatted_response = f"*✅ Command \"{command}\" executed successfully*\n\n{tool_result}"
|
||||
|
||||
# Append the original message and tool result to the conversation
|
||||
messages.append({"role": "assistant", "content": ai_content})
|
||||
messages.append({"role": "user", "content": f"Command output:\n{tool_result}"})
|
||||
|
||||
# Make another API call with the tool result
|
||||
return await self._teto_reply_ai_with_messages(messages)
|
||||
# Make another API call with the tool result, but return the formatted response
|
||||
# to be displayed in Discord
|
||||
ai_follow_up = await self._teto_reply_ai_with_messages(messages)
|
||||
return formatted_response + "\n\n" + ai_follow_up
|
||||
|
||||
return ai_content
|
||||
|
||||
@ -305,7 +342,17 @@ class TetoCog(commands.Cog):
|
||||
ai_reply = await self._teto_reply_ai_with_messages(messages=convo)
|
||||
ai_reply = strip_think_blocks(ai_reply)
|
||||
await message.reply(ai_reply)
|
||||
convo.append({"role": "assistant", "content": ai_reply})
|
||||
|
||||
# Extract the original AI content (without command execution formatting)
|
||||
# for storing in conversation history
|
||||
command, content_without_command, _ = extract_shell_command(ai_reply)
|
||||
if command:
|
||||
# If there was a command, store the original AI content without the formatted execution message
|
||||
convo.append({"role": "assistant", "content": content_without_command if content_without_command else ai_reply})
|
||||
else:
|
||||
# If there was no command, store the full reply
|
||||
convo.append({"role": "assistant", "content": ai_reply})
|
||||
|
||||
_teto_conversations[convo_key] = convo[-10:] # Keep last 10 interactions
|
||||
log.info("[TETO DEBUG] AI reply sent successfully.")
|
||||
except Exception as e:
|
||||
@ -365,7 +412,16 @@ async def teto_context_menu_ai_reply(interaction: discord.Interaction, message:
|
||||
ai_reply = strip_think_blocks(ai_reply)
|
||||
await message.reply(ai_reply)
|
||||
await interaction.followup.send("Teto AI replied desu~", ephemeral=True)
|
||||
convo.append({"role": "assistant", "content": ai_reply})
|
||||
|
||||
# Extract the original AI content (without command execution formatting)
|
||||
# for storing in conversation history
|
||||
command, content_without_command, _ = extract_shell_command(ai_reply)
|
||||
if command:
|
||||
# If there was a command, store the original AI content without the formatted execution message
|
||||
convo.append({"role": "assistant", "content": content_without_command if content_without_command else ai_reply})
|
||||
else:
|
||||
# If there was no command, store the full reply
|
||||
convo.append({"role": "assistant", "content": ai_reply})
|
||||
_teto_conversations[convo_key] = convo[-10:]
|
||||
except Exception as e:
|
||||
await interaction.followup.send(f"Teto AI reply failed: {e} desu~", ephemeral=True)
|
||||
|
Loading…
x
Reference in New Issue
Block a user