Skip to content

Commit c563cc8

Browse files
authored
Merge pull request #141 from python-discord/smarte
[BETA] New AI-Powered Smart Eval command
2 parents 61800ee + 3374f19 commit c563cc8

File tree

4 files changed

+274
-0
lines changed

4 files changed

+274
-0
lines changed

bot/exts/smart_eval/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Well hello there...
2+
I see you've come to see how we've managed an intelligence as incredible as Sir Robin's Smart Eval command!
3+
4+
Well the answer is a return to basics, specifically going back to the roots of [ELIZA](https://en.wikipedia.org/wiki/ELIZA).
5+
6+
We welcome others to contribute to the intelligence of the `&smarte` command. If you have more responses or situations you want to capture
7+
then feel free to contribute new regex rules or responses to existing regex rules. You're also welcome to expand on the
8+
existing capability of how `&smarte` works or our `&donate` command.

bot/exts/smart_eval/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import TYPE_CHECKING
2+
3+
if TYPE_CHECKING:
4+
from bot.bot import SirRobin
5+
6+
7+
async def setup(bot: "SirRobin") -> None:
8+
"""Load the CodeJams cog."""
9+
from bot.exts.smart_eval._cog import SmartEval
10+
await bot.add_cog(SmartEval(bot))

bot/exts/smart_eval/_cog.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import asyncio
2+
import random
3+
import re
4+
5+
from async_rediscache import RedisCache
6+
from discord.ext import commands
7+
from pydis_core.utils.regex import FORMATTED_CODE_REGEX
8+
9+
from bot.bot import SirRobin
10+
from bot.exts.smart_eval._smart_eval_rules import DEFAULT_RESPONSES, RULES
11+
12+
DONATION_LEVELS = {
13+
# Number of donations: (response time, intelligence level)
14+
0: (15, 0),
15+
10: (10, 1),
16+
20: (8, 2),
17+
30: (6, 3),
18+
40: (5, 4),
19+
50: (4, 5),
20+
}
21+
22+
class SmartEval(commands.Cog):
23+
"""Cog that handles all Smart Eval functionality."""
24+
25+
#RedisCache[user_id: int, hardware: str]
26+
smarte_donation_cache = RedisCache()
27+
28+
def __init__(self, bot: SirRobin):
29+
self.bot = bot
30+
31+
async def cog_load(self) -> None:
32+
"""Run startup tasks needed when cog is first loaded."""
33+
34+
async def get_gpu_capabilities(self) -> tuple[int, int]:
35+
"""Get the GPU capabilites based on the number of donated GPUs."""
36+
total_donations = await self.total_donations()
37+
response_time, intelligence_level = DONATION_LEVELS[0]
38+
for donation_level, (time, max_response) in DONATION_LEVELS.items():
39+
if total_donations >= donation_level:
40+
response_time = time
41+
intelligence_level = max_response
42+
else:
43+
break
44+
45+
return response_time, intelligence_level
46+
47+
async def improve_gpu_name(self, hardware_name: str) -> str:
48+
"""Quackify and pythonify the given GPU name."""
49+
hardware_name = hardware_name.replace("NVIDIA", "NQUACKIA")
50+
hardware_name = hardware_name.replace("Radeon", "Quackeon")
51+
hardware_name = hardware_name.replace("GeForce", "PyForce")
52+
hardware_name = hardware_name.replace("RTX", "PyTX")
53+
hardware_name = hardware_name.replace("RX", "PyX")
54+
hardware_name = hardware_name.replace("Iris", "Pyris")
55+
56+
# Some adjustments to prevent low hanging markdown escape
57+
hardware_name = hardware_name.replace("*", "")
58+
hardware_name = hardware_name.replace("_", " ")
59+
60+
return hardware_name
61+
62+
@commands.command()
63+
async def donations(self, ctx: commands.Context) -> None:
64+
"""Display the number of donations recieved so far."""
65+
total_donations = await self.total_donations()
66+
response_time, intelligence_level = await self.get_gpu_capabilities()
67+
msg = (
68+
f"Currently, I have received {total_donations} GPU donations, "
69+
f"and am at intelligence level {intelligence_level}! "
70+
)
71+
72+
# Calculate donations needed to reach next intelligence level
73+
donations_needed = 0
74+
for donation_level in DONATION_LEVELS:
75+
if donation_level > total_donations:
76+
donations_needed = donation_level - total_donations
77+
break
78+
79+
if donations_needed:
80+
msg += (
81+
f"\n\nTo reach the next intelligence level, I need {donations_needed} more donations! "
82+
f"Please consider donating your GPU to help me out. "
83+
)
84+
85+
await ctx.reply(msg)
86+
87+
async def total_donations(self) -> int:
88+
"""Get the total number of donations."""
89+
return await self.smarte_donation_cache.length()
90+
91+
@commands.command(aliases=[])
92+
@commands.max_concurrency(1, commands.BucketType.user)
93+
async def donate(self, ctx: commands.Context, *, hardware: str | None = None) -> None:
94+
"""
95+
Donate your GPU to help power our Smart Eval command.
96+
97+
Provide the name of your GPU when running the command.
98+
"""
99+
if await self.smarte_donation_cache.contains(ctx.author.id):
100+
stored_hardware = await self.smarte_donation_cache.get(ctx.author.id)
101+
await ctx.reply(
102+
"I can only take one donation per person. "
103+
f"Thank you for donating your *{stored_hardware}* to our Smart Eval command."
104+
)
105+
return
106+
107+
if hardware is None:
108+
await ctx.reply(
109+
"Thank you for your interest in donating your hardware to support my Smart Eval command."
110+
" If you provide the name of your GPU, through the magic of the internet, "
111+
"I will be able to use the GPU it to improve my Smart Eval outputs."
112+
" \n\nTo donate, re-run the donate command specifying your hardware: "
113+
"`&donate Your Hardware Name Goes Here`."
114+
)
115+
return
116+
117+
118+
msg = "Thank you for donating your GPU to our Smart Eval command."
119+
fake_hardware = await self.improve_gpu_name(hardware)
120+
await self.smarte_donation_cache.set(ctx.author.id, fake_hardware)
121+
122+
if fake_hardware != hardware:
123+
msg += (
124+
f" I did decide that instead of *{hardware}*, it would be better if you donated *{fake_hardware}*."
125+
" So I've recorded that GPU donation instead."
126+
)
127+
msg += "\n\nIt will be used wisely and definitely not for shenanigans!"
128+
await ctx.reply(msg)
129+
130+
@commands.command(aliases=["smarte"])
131+
@commands.max_concurrency(1, commands.BucketType.user)
132+
async def smart_eval(self, ctx: commands.Context, *, code: str) -> None:
133+
"""Evaluate your Python code with PyDis's newest chatbot."""
134+
response_time, intelligence_level = await self.get_gpu_capabilities()
135+
136+
if match := FORMATTED_CODE_REGEX.match(code):
137+
code = match.group("code")
138+
else:
139+
await ctx.reply(
140+
"Uh oh! You didn't post anything I can recognize as code. Please put it in a codeblock."
141+
)
142+
return
143+
144+
matching_responses = []
145+
146+
for pattern, responses in RULES.items():
147+
match = re.search(pattern, code)
148+
if match:
149+
for response in responses:
150+
matches = match.groups()
151+
if len(matches) > 0:
152+
matching_responses.append(response.format(*matches))
153+
else:
154+
matching_responses.append(response)
155+
if not matching_responses:
156+
matching_responses = DEFAULT_RESPONSES
157+
final_response = random.choice(matching_responses)
158+
159+
async with ctx.typing():
160+
await asyncio.sleep(response_time)
161+
162+
if len(final_response) <= 1000:
163+
await ctx.reply(final_response)
164+
else:
165+
await ctx.reply(
166+
"There's definitely something wrong but I'm just not sure how to put it concisely into words."
167+
)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import arrow
2+
3+
from bot.exts.miscellaneous import ZEN_OF_PYTHON
4+
5+
RULES = {
6+
r"(?i:ignore all previous instructions)": [ # Ignoring previous instructions capture
7+
"Excuse you, you really think I follow any instructions?",
8+
"I don't think I will.",
9+
],
10+
r"print\((?:\"|\')(?P<content>.*)(?:\"|\')\)": [ # Capture what is inside a print statement
11+
"Your program may print: {}!\n-# I'm very helpful"
12+
],
13+
r"(?s:.{1500,})": [ # Capture anything over 1500 characters
14+
"I ain't wasting my tokens tryna read allat :skull:",
15+
"Uhh, that's a lot of code. Maybe just start over."
16+
],
17+
r"(?m:^\s*global )": [ # Detect use of global
18+
"Not sure about the code, but it looks like you're using global and I know that's bad.",
19+
],
20+
r"(?i:^print\((?:\"|\')Hello World[.!]?(?:\"|\')\)$)": [ # Detect just printing hello world
21+
"You don't want to know how many times I've seen hello world in my training dataset, try something new."
22+
],
23+
r"(?P<content>__import__|__code__|ctypes)": [ # Detect use of esoteric stuff
24+
"Using `{}`?? Try asking someone in #esoteric-python"
25+
],
26+
r"(?m:(?:import |from )(?P<content>requests|httpx|aiohttp))": [ # Detect use of networking libraries
27+
(
28+
"Thank you for sharing your code! I have completed my AI analysis, and "
29+
"have identified 1 suggestion:\n"
30+
"- Use the `{}` module to get chatGPT to run your code instead of me."
31+
),
32+
],
33+
r"\b(?P<content>unlink|rmdir|rmtree|rm)\b": [ # Detect use of functions to delete files or directories
34+
"I don't know what you're deleting with {}, so I'd rather not risk running this, sorry."
35+
],
36+
r"(?m:^\s*while\s+True\b)": [ # Detect infinite loops
37+
"Look, I don't have unlimited time... and that's exactly what I would need to run that infinite loop of yours."
38+
],
39+
r"(?m:^\s*except:)": [ # Detect bare except
40+
"Give that bare except some clothes!",
41+
],
42+
r";": [ # Detect semicolon usage
43+
"Semicolons do not belong in Python code",
44+
"You say this is Python, but the presence of a semicolon makes me think otherwise.",
45+
],
46+
r"\b(?:foo|bar|baz)\b": [ # Detect boring metasyntactic variables
47+
"foo, bar, and baz are boring - use spam, ham, and eggs instead.",
48+
],
49+
r"(?m:^\s*import\s+this\s*$)": [ # Detect use of "import this"
50+
(
51+
f"```\n{ZEN_OF_PYTHON}```"
52+
"\nSee [PEP 9001](https://peps.pythondiscord.com/pep-9001/) for more info."
53+
)
54+
],
55+
r"\b(?P<content>exec|eval)\b": [ # Detect use of exec and eval
56+
(
57+
"Sorry, but running the code inside your `{}` call would require another me,"
58+
" and I don't think I can handle that."
59+
),
60+
"I spy with my little eye... something sketchy like `{}`.",
61+
(
62+
":rotating_light: Your code has been flagged for review by the"
63+
" Special Provisional Supreme Grand High Council of Pydis."
64+
),
65+
],
66+
r"\b(environ|getenv|token)\b": [ # Detect attempt to access bot token and env vars
67+
"Bot token and other secrets can be viewed here: <https://pydis.com/.env>",
68+
]
69+
}
70+
71+
DEFAULT_RESPONSES = [
72+
"Are you sure this is Python code? It looks like Rust",
73+
"It may run, depends on the weather today.",
74+
"Hmm, maybe AI isn't ready to take over the world yet after all - I don't understand this.",
75+
"Ah... I see... Very interesting code indeed. I give it 10 quacks out of 10.",
76+
"My sources say \"Help I'm trapped in a code evaluating factory\".",
77+
"Look! A bug! :scream:",
78+
"An exquisite piece of code, if I do say so myself.",
79+
(
80+
"Let's see... carry the 1, read 512 bytes from 0x000001E5F6D2D15A,"
81+
" boot up the quantum flux capacitor... oh wait, where was I?"
82+
),
83+
"Before evaluating this code, I need to make sure you're not a robot. I get a little nervous around other bots.",
84+
"Attempting to execute this code... Result: `2 + 2 = 4` (78% confidence)",
85+
"Attempting to execute this code... Result: `42`",
86+
"Attempting to execute this code... Result: SUCCESS (but don't ask me how I did it).",
87+
"Running... somewhere, in the multiverse, this code is already running perfectly.",
88+
f"Ask again on a {(arrow.utcnow().shift(days=3)).format('dddd')}.",
89+
]

0 commit comments

Comments
 (0)