From 6833f5b6011215bb4e7804cad9e79417af790dc4 Mon Sep 17 00:00:00 2001 From: Kaveen Kumarasinghe Date: Thu, 8 Dec 2022 07:29:07 -0500 Subject: [PATCH] Update bot.py --- bot.py | 164 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 137 insertions(+), 27 deletions(-) diff --git a/bot.py b/bot.py index 018c819..b10107f 100644 --- a/bot.py +++ b/bot.py @@ -8,10 +8,12 @@ from dotenv import load_dotenv load_dotenv() import os + class Mode: TOP_P = "top_p" TEMPERATURE = "temperature" + class Model: # An enum of two modes, TOP_P or TEMPERATURE def __init__(self, ): @@ -23,10 +25,23 @@ class Model: self._frequency_penalty = 0 # Penalize new tokens based on their existing frequency in the text so far. (Higher frequency = lower probability of being chosen.) self._best_of = 1 # Number of responses to compare the loglikelihoods of self._prompt_min_length = 25 + self._max_conversation_length = 5 openai.api_key = os.getenv('OPENAI_TOKEN') - # Use the @property and @setter decorators for all the self fields to provide value checking + + @property + def max_conversation_length(self): + return self._max_conversation_length + + @max_conversation_length.setter + def max_conversation_length(self, value): + if value < 1: + raise ValueError("Max conversation length must be greater than 1") + if value > 20: + raise ValueError("Max conversation length must be less than 20, this will start using credits quick.") + self._max_conversation_length = value + @property def mode(self): return self._mode @@ -36,11 +51,11 @@ class Model: if value not in [Mode.TOP_P, Mode.TEMPERATURE]: raise ValueError("mode must be either 'top_p' or 'temperature'") if value == Mode.TOP_P: - self._top_p = 0.5 - self._temp = 1 - elif value == Mode.TEMPERATURE: - self._top_p = 1 + self._top_p = 0.1 self._temp = 0.7 + elif value == Mode.TEMPERATURE: + self._top_p = 0.9 + self._temp = 0.6 self._mode = value @@ -53,8 +68,6 @@ class Model: value = float(value) if value < 0 or value > 1: raise ValueError("temperature must be greater than 0 and less than 1, it is currently " + str(value)) - if self._mode == Mode.TOP_P: - raise ValueError("Cannot set temperature when in top_p mode") self._temp = value @@ -67,8 +80,6 @@ class Model: value = float(value) if value < 0 or value > 1: raise ValueError("top_p must be greater than 0 and less than 1, it is currently " + str(value)) - if self._mode == Mode.TEMPERATURE: - raise ValueError("Cannot set top_p when in temperature mode") self._top_p = value @property @@ -110,7 +121,8 @@ class Model: def best_of(self, value): value = int(value) if value < 1 or value > 3: - raise ValueError("best_of must be greater than 0 and ideally less than 3 to save tokens, it is currently " + str(value)) + raise ValueError( + "best_of must be greater than 0 and ideally less than 3 to save tokens, it is currently " + str(value)) self._best_of = value @property @@ -121,7 +133,8 @@ class Model: def prompt_min_length(self, value): value = int(value) if value < 10 or value > 4096: - raise ValueError("prompt_min_length must be greater than 10 and less than 4096, it is currently " + str(value)) + raise ValueError( + "prompt_min_length must be greater than 10 and less than 4096, it is currently " + str(value)) self._prompt_min_length = value def send_request(self, prompt): @@ -149,6 +162,31 @@ bot = commands.Bot(command_prefix="gpt3 ") model = Model() last_used = {} GLOBAL_COOLDOWN_TIME = 5 # In seconds +conversating_users = {} + + +class User: + + def __init__(self, id): + self.id = id + self.history = "" + self.count = 0 + + # These user objects should be accessible by ID, for example if we had a bunch of user + # objects in a list, and we did `if 1203910293001 in user_list`, it would return True + # if the user with that ID was in the list + def __eq__(self, other): + return self.id == other.id + + def __hash__(self): + return hash(self.id) + + def __repr__(self): + return f"User(id={self.id}, history={self.history})" + + def __str__(self): + return self.__repr__() + class DiscordBot: @@ -158,8 +196,8 @@ class DiscordBot: self.last_used = {} @staticmethod - @bot.event # Using self gives u - async def on_ready(): # I can make self optional by + @bot.event # Using self gives u + async def on_ready(): # I can make self optional by print('We have logged in as {0.user}'.format(bot)) @staticmethod @@ -168,6 +206,10 @@ class DiscordBot: if message.author == bot.user: return + # Only allow the bot to be used by people who have the role "Admin" or "GPT" + if not any(role.name in ["admin", "Admin", "GPT", "gpt"] for role in message.author.roles): + return + if not message.content.startswith('!g'): return @@ -177,7 +219,8 @@ class DiscordBot: if message.author.id in last_used: if time.time() - last_used[message.author.id] < GLOBAL_COOLDOWN_TIME: # Tell the user the remaining global cooldown time, respond to the user's original message as a "reply" - await message.reply("You must wait " + str(round(GLOBAL_COOLDOWN_TIME - (time.time() - last_used[message.author.id]))) + " seconds before using the bot again") + await message.reply("You must wait " + str(round(GLOBAL_COOLDOWN_TIME - ( + time.time() - last_used[message.author.id]))) + " seconds before using the bot again") return last_used[message.author.id] = time.time() @@ -186,18 +229,29 @@ class DiscordBot: if message.content == "!g": # create a discord embed with help text embed = discord.Embed(title="GPT3Bot Help", description="The current commands", color=0x00ff00) - embed.add_field(name="!g ", value="Ask GPT3 something. Be clear, long, and concise in your prompt. Don't waste tokens.", inline=False) + embed.add_field(name="!g ", + value="Ask GPT3 something. Be clear, long, and concise in your prompt. Don't waste tokens.", + inline=False) + embed.add_field(name="!g converse", + value="Start a conversation with GPT3", + inline=False) + embed.add_field(name="!g end", + value="End a conversation with GPT3", + inline=False) embed.add_field(name="!gp", value="Print the current settings of the model", inline=False) - embed.add_field(name="!gs ", value="Change the parameter of the model named by to new value ", inline=False) + embed.add_field(name="!gs ", + value="Change the parameter of the model named by to new value ", + inline=False) embed.add_field(name="!g", value="See this help text", inline=False) await message.channel.send(embed=embed) elif message.content.startswith('!gp'): - embed = discord.Embed(title="GPT3Bot Settings", description="The current settings of the model", color=0x00ff00) + embed = discord.Embed(title="GPT3Bot Settings", description="The current settings of the model", + color=0x00ff00) for key, value in model.__dict__.items(): embed.add_field(name=key, value=value, inline=False) - await message.channel.send(embed=embed) + await message.reply(embed=embed) elif message.content.startswith('!gs'): # Extract the parameter and the value @@ -209,46 +263,102 @@ class DiscordBot: try: # Set the parameter to the value setattr(model, parameter, value) - await message.channel.send("Successfully set the parameter " + parameter + " to " + value) + await message.reply("Successfully set the parameter " + parameter + " to " + value) if parameter == "mode": - await message.channel.send( + await message.reply( "The mode has been set to " + value + ". This has changed the temperature top_p to the mode defaults of " + str( model.temp) + " and " + str(model.top_p)) except ValueError as e: - await message.channel.send(e) + await message.reply(e) else: - await message.channel.send("The parameter is not a valid parameter") + await message.reply("The parameter is not a valid parameter") # GPT3 command elif message.content.startswith('!g'): # Extract all the text after the !g and use it as the prompt. prompt = message.content[2:] + # Remove the extra space on the left + prompt = prompt.lstrip() + + # If the prompt is just "converse", start a conversation with GPT3 + if prompt == "converse": + # If the user is already conversating, don't let them start another conversation + if message.author.id in conversating_users: + await message.reply("You are already conversating with GPT3. End the conversation with !g end") + return + + # If the user is not already conversating, start a conversation with GPT3 + conversating_users[message.author.id] = User(message.author.id) + # Append the starter text for gpt3 to the user's history so it gets concatenated with the prompt later + conversating_users[message.author.id].history += "You are an artificial intelligence that is able to do anything, and answer any question, I want you to be my personal assisstant and help me with some tasks." + await message.reply("You are now conversing with GPT3. End the conversation with !g end") + return + + # If the prompt is just "end", end the conversation with GPT3 + if prompt == "end": + # If the user is not conversating, don't let them end the conversation + if message.author.id not in conversating_users: + await message.reply("You are not conversing with GPT3. Start a conversation with !g converse") + return + + # If the user is conversating, end the conversation + conversating_users.pop(message.author.id) + await message.reply("You have ended the conversation with GPT3. Start a conversation with !g converse") + return + + # We want to have conversationality functionality. To have gpt3 remember context, we need to append the conversation/prompt + # history to the prompt. We can do this by checking if the user is in the conversating_users dictionary, and if they are, + # we can append their history to the prompt. + if message.author.id in conversating_users: + prompt = conversating_users[message.author.id].history + "\nHuman: " + prompt + "\nAI:" + # Now, add overwrite the user's history with the new prompt + conversating_users[message.author.id].history = prompt + + # increment the conversation counter for the user + conversating_users[message.author.id].count += 1 + + # If the user has reached the max conversation length, end the conversation + if conversating_users[message.author.id].count >= model.max_conversation_length: + conversating_users.pop(message.author.id) + await message.reply("You have reached the maximum conversation length. You have ended the conversation with GPT3, and it has ended.") + return + + # Send the request to the model try: response = model.send_request(prompt) response_text = response["choices"][0]["text"] print(response_text) + # If the user is conversating, we want to add the response to their history + if message.author.id in conversating_users: + conversating_users[message.author.id].history += response_text + "\n" + # If the response text is > 3500 characters, paginate and send if len(response_text) > 1900: # Split the response text into 3500 character chunks response_text = [response_text[i:i + 1900] for i in range(0, len(response_text), 1900)] # Send each chunk as a message + first = False for chunk in response_text: - await message.channel.send(chunk) + if not first: + await message.reply(chunk) + first = True + else: + await message.channel.send(chunk) else: - await message.channel.send(response_text) + await message.reply(response_text) except ValueError as e: - await message.channel.send(e) + await message.reply(e) return except Exception as e: - await message.channel.send("Something went wrong, please try again later") + await message.reply("Something went wrong, please try again later") await message.channel.send(e) return + # Run the bot with a token taken from an environment file. if __name__ == "__main__": bot = DiscordBot(bot) -