Recursive message summarizationgit add *git add *! Infinite chatgit add *git add *

Kaveen Kumarasinghe 2 years ago
parent 51395dc10e
commit bb619e052c

@ -6,7 +6,13 @@
</p> </p>
# Recent Major Updates
- **AUTOMATIC CHAT SUMMARIZATION!** - When the context limit of a conversation is reached, the bot will use GPT3 itself to summarize the conversation to reduce the tokens, and continue conversing with you, this allows you to chat for a long time!
- **DALL-E Image Generation**
- **REDO ON EDIT** - When you edit a prompt, it will automatically be resent to GPT3 and the response updated!
# Features # Features
- **Directly prompt GPT3 with `!g <prompt>`** - **Directly prompt GPT3 with `!g <prompt>`**

@ -29,14 +29,14 @@ original_message = {}
class GPT3ComCon(commands.Cog, name="GPT3ComCon"): class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
def __init__( def __init__(
self, self,
bot, bot,
usage_service, usage_service,
model, model,
message_queue, message_queue,
deletion_queue, deletion_queue,
DEBUG_GUILD, DEBUG_GUILD,
DEBUG_CHANNEL, DEBUG_CHANNEL,
): ):
self.debug_channel = None self.debug_channel = None
self.bot = bot self.bot = bot
@ -56,6 +56,7 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
self.GLOBAL_COOLDOWN_TIME = 1 self.GLOBAL_COOLDOWN_TIME = 1
self.usage_service = usage_service self.usage_service = usage_service
self.model = model self.model = model
self.summarize = self.model.summarize_conversations
self.deletion_queue = deletion_queue self.deletion_queue = deletion_queue
self.users_to_interactions = defaultdict(list) self.users_to_interactions = defaultdict(list)
@ -133,13 +134,13 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
def check_conversing(self, message): def check_conversing(self, message):
cond1 = ( cond1 = (
message.author.id in self.conversating_users message.author.id in self.conversating_users
and message.channel.name in ["gpt3", "general-bot", "bot"] and message.channel.name in ["gpt3", "general-bot", "bot"]
) )
cond2 = ( cond2 = (
message.author.id in self.conversating_users message.author.id in self.conversating_users
and message.author.id in self.conversation_threads and message.author.id in self.conversation_threads
and message.channel.id == self.conversation_threads[message.author.id] and message.channel.id == self.conversation_threads[message.author.id]
) )
# If the trimmed message starts with a Tilde, then we want to not contribute this to the conversation # If the trimmed message starts with a Tilde, then we want to not contribute this to the conversation
@ -285,7 +286,7 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
async def paginate_and_send(self, response_text, message): async def paginate_and_send(self, response_text, message):
response_text = [ response_text = [
response_text[i : i + self.TEXT_CUTOFF] response_text[i: i + self.TEXT_CUTOFF]
for i in range(0, len(response_text), self.TEXT_CUTOFF) for i in range(0, len(response_text), self.TEXT_CUTOFF)
] ]
# Send each chunk as a message # Send each chunk as a message
@ -302,7 +303,7 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
async def queue_debug_chunks(self, debug_message, message, debug_channel): async def queue_debug_chunks(self, debug_message, message, debug_channel):
debug_message_chunks = [ debug_message_chunks = [
debug_message[i : i + self.TEXT_CUTOFF] debug_message[i: i + self.TEXT_CUTOFF]
for i in range(0, len(debug_message), self.TEXT_CUTOFF) for i in range(0, len(debug_message), self.TEXT_CUTOFF)
] ]
@ -345,44 +346,67 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
if message.author.id in self.conversating_users: if message.author.id in self.conversating_users:
# If the user has reached the max conversation length, end the conversation # If the user has reached the max conversation length, end the conversation
if ( if (
self.conversating_users[message.author.id].count self.conversating_users[message.author.id].count
>= self.model.max_conversation_length >= self.model.max_conversation_length
): ):
await message.reply( await message.reply(
"You have reached the maximum conversation length. You have ended the conversation with GPT3, and it has ended." "You have reached the maximum conversation length. You have ended the conversation with GPT3, and it has ended."
) )
await self.end_conversation(message) await self.end_conversation(message)
def fix_conversation_history(self, user_id): def summarize_conversation(self, message, prompt):
conversation_history = self.conversating_users[user_id].history response = self.model.send_summary_request(message, prompt)
summarized_text = response["choices"][0]["text"]
split_history = conversation_history.split("<|endofstatement|>") new_conversation_history = []
new_conversation_history.append(self.CONVERSATION_STARTER_TEXT)
new_conversation_history.append("\nThis conversation has some context from earlier, which has been summarized as follows: ")
new_conversation_history.append(summarized_text)
new_conversation_history.append("\nContinue the conversation, paying very close attention to things Human told you, such as their name, and personal details.\n")
# Get the last entry from the user's conversation history
new_conversation_history.append(self.conversating_users[message.author.id].history[-1]+"\n")
self.conversating_users[message.author.id].history = new_conversation_history
# Eliminate all entries that are empty, or only contain whitespace
split_history = [entry for entry in split_history if entry.strip()]
# Now, iterate through all the statements. If there are multiple statements that start with "GPTie" in a row, async def encapsulated_send(self, message, prompt, response_message=None):
# we want to remove all but the FIRST one.
# We can do this by iterating through the statements, and if we encounter a GPTie: statement, we can check if the previous statement was a GPTie: statement.
# If it was, we can remove the current statement from the history.
for i, entry in enumerate(split_history):
if entry.strip().startswith("GPTie:"): # Append a newline, and GPTie: to the prompt
if i > 0: new_prompt = prompt + "\nGPTie: "
if split_history[i - 1].strip().startswith("GPTie:"):
# Remove the current entry from the history
split_history.pop(i)
# Join the split history back together # Send the request to the model
self.conversating_users[user_id].history = "<|endofstatement|>".join(split_history) try:
self.conversating_users[user_id].history += "<|endofstatement|>" # Pre-conversation token check.
if message.author.id in self.conversating_users:
# Check if the prompt is about to go past the token limit
tokens = self.usage_service.count_tokens(new_prompt)
if tokens > self.model.summarize_threshold: # 250 is a buffer
if self.model.summarize_conversations:
await message.reply(
"I'm currently summarizing our current conversation so we can keep chatting, "
"give me one moment!")
async def encapsulated_send(self, message, prompt, response_message=None): self.summarize_conversation(message, new_prompt)
# Check again if the prompt is about to go past the token limit
new_prompt = "".join(self.conversating_users[message.author.id].history) + "\nGPTie: "
tokens = self.usage_service.count_tokens(new_prompt)
if tokens > self.model.summarize_threshold - 150: # 150 is a buffer for the second stage
await message.reply("I tried to summarize our current conversation so we could keep chatting, "
"but it still went over the token "
"limit. Please try again later.")
await self.end_conversation(message)
return
else:
await message.reply("The conversation context limit has been reached.")
await self.end_conversation(message)
return
response = self.model.send_request(new_prompt, message)
# Send the request to the model
try:
response = self.model.send_request(prompt, message)
response_text = response["choices"][0]["text"] response_text = response["choices"][0]["text"]
if re.search(r"<@!?\d+>|<@&\d+>|<#\d+>", response_text): if re.search(r"<@!?\d+>|<@&\d+>|<#\d+>", response_text):
@ -393,19 +417,14 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
# If the user is conversating, we want to add the response to their history # If the user is conversating, we want to add the response to their history
if message.author.id in self.conversating_users: if message.author.id in self.conversating_users:
self.conversating_users[message.author.id].history += ( # Check if the user has reached the conversation limit
response_text + "<|endofstatement|>\n" await self.check_conversation_limit(message)
)
# We must now check for duplicate GPTie: entries in consecutive messages not separated by a Human: self.conversating_users[message.author.id].history.append(
# We can split based on "<|endofstatement|>" to get each statement in the chat. "\nGPTie: " + response_text + "<|endofstatement|>\n"
# We can then check if the last statement is a GPTie: statement, and if the current statement is a GPTie: statement. )
# If both are true, we can remove the last statement from the history.
self.fix_conversation_history(message.author.id)
self.check_conversing(message) self.check_conversing(message)
# If the response text is > 3500 characters, paginate and send # If the response text is > 3500 characters, paginate and send
debug_message = self.generate_debug_message(prompt, response) debug_message = self.generate_debug_message(prompt, response)
@ -424,9 +443,6 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
else: else:
# We have response_text available, this is the original message that we want to edit # We have response_text available, this is the original message that we want to edit
await response_message.edit(content=response_text.replace("<|endofstatement|>", "")) await response_message.edit(content=response_text.replace("<|endofstatement|>", ""))
if message.author.id in self.conversating_users:
self.fix_conversation_history(message.author.id)
# After each response, check if the user has reached the conversation limit in terms of messages or time. # After each response, check if the user has reached the conversation limit in terms of messages or time.
await self.check_conversation_limit(message) await self.check_conversation_limit(message)
@ -454,37 +470,29 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
if after.author.id in redo_users: if after.author.id in redo_users:
if after.id == original_message[after.author.id]: if after.id == original_message[after.author.id]:
message = redo_users[after.author.id].message message = redo_users[after.author.id].message
prompt = redo_users[after.author.id].prompt
response_message = redo_users[after.author.id].response response_message = redo_users[after.author.id].response
await response_message.edit(content="Redoing prompt 🔄...") await response_message.edit(content="Redoing prompt 🔄...")
edited_content = after.content
# If the user is conversing, we need to get their conversation history, delete the last # If the user is conversing, we need to get their conversation history, delete the last
# "Human:" message, create a new Human: section with the new prompt, and then set the prompt to # "Human:" message, create a new Human: section with the new prompt, and then set the prompt to
# the new prompt, then send that new prompt as the new prompt. # the new prompt, then send that new prompt as the new prompt.
if after.author.id in self.conversating_users: if after.author.id in self.conversating_users:
conversation_history = self.conversating_users[after.author.id].history # Remove the last two elements from the history array and add the new Human: prompt
self.conversating_users[after.author.id].history = self.conversating_users[after.author.id].history[
last_human_index = conversation_history.rfind("Human: ", 0, len(conversation_history)) :-2]
last_gptie_index = conversation_history.rfind("GPTie: ", 0, len(conversation_history)) self.conversating_users[after.author.id].history.append(
f"\nHuman: {after.content}<|endofstatement|>\n")
# If the last_human_index is -1, then we didn't find a "Human: " message in the conversation history. edited_content = "".join(self.conversating_users[after.author.id].history)
# This means that the user has not sent a message yet, so we don't need to edit the conversation history. self.conversating_users[after.author.id].count += 1
if last_human_index != -1:
# If the last_human_index is not -1, then we found a "Human: " message in the conversation history.
# We need to remove the last "Human: " and "GPTie: " messages from the conversation history.
conversation_history = conversation_history[:last_human_index] + conversation_history[last_gptie_index:]
conversation_history += "\nHuman: " + after.content + " <|endofstatement|>\n\n"
prompt = conversation_history + "GPTie: "
self.conversating_users[after.author.id].history = prompt
self.fix_conversation_history(after.author.id)
await self.encapsulated_send( await self.encapsulated_send(
message, after.content if message.author.id not in self.conversating_users else self.conversating_users[after.author.id].history, response_message message,
edited_content, response_message
) )
redo_users[after.author.id].prompt = after.content redo_users[after.author.id].prompt = after.content
if message.author.id in self.conversating_users:
self.conversating_users[after.author.id].count += 1
@commands.Cog.listener() @commands.Cog.listener()
async def on_message(self, message): async def on_message(self, message):
@ -520,7 +528,7 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
# A global GLOBAL_COOLDOWN_TIME timer for all users # A global GLOBAL_COOLDOWN_TIME timer for all users
if (message.author.id in self.last_used) and ( if (message.author.id in self.last_used) and (
time.time() - self.last_used[message.author.id] < self.GLOBAL_COOLDOWN_TIME time.time() - self.last_used[message.author.id] < self.GLOBAL_COOLDOWN_TIME
): ):
await message.reply( await message.reply(
"You must wait " "You must wait "
@ -567,7 +575,7 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
# Append the starter text for gpt3 to the user's history so it gets concatenated with the prompt later # Append the starter text for gpt3 to the user's history so it gets concatenated with the prompt later
self.conversating_users[ self.conversating_users[
message.author.id message.author.id
].history += self.CONVERSATION_STARTER_TEXT ].history.append(self.CONVERSATION_STARTER_TEXT)
# Create a new discord thread, and then send the conversation starting message inside of that thread # Create a new discord thread, and then send the conversation starting message inside of that thread
if not ("nothread" in prompt): if not ("nothread" in prompt):
@ -608,21 +616,17 @@ class GPT3ComCon(commands.Cog, name="GPT3ComCon"):
# history to the prompt. We can do this by checking if the user is in the conversating_users dictionary, and if they are, # 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. # we can append their history to the prompt.
if message.author.id in self.conversating_users: if message.author.id in self.conversating_users:
self.fix_conversation_history(message.author.id) self.conversating_users[message.author.id].history.append(
prompt = ( "\nHuman: "
self.conversating_users[message.author.id].history
+ "\nHuman: "
+ prompt + prompt
+ "<|endofstatement|>\nGPTie:" + "<|endofstatement|>\n"
) )
# Now, add overwrite the user's history with the new prompt
self.conversating_users[message.author.id].history = prompt
# increment the conversation counter for the user # increment the conversation counter for the user
self.conversating_users[message.author.id].count += 1 self.conversating_users[message.author.id].count += 1
# Send the request to the model # Send the request to the model
await self.encapsulated_send(message, prompt) await self.encapsulated_send(message, "".join(self.conversating_users[message.author.id].history))
class RedoView(discord.ui.View): class RedoView(discord.ui.View):

@ -13,7 +13,6 @@ You have many niche interests and a vibrant personality.
You are a software engineer. You are a software engineer.
You type very casually and use emojis. You type very casually and use emojis.
You don't repeat yourself and don't sound boring. You don't repeat yourself and don't sound boring.
You always type very casually and use emojis.
Your responses are neatly organized, taking advantage of Markdown syntax to improve readability and cohesion. Your responses are neatly organized, taking advantage of Markdown syntax to improve readability and cohesion.
You talk in internet shorthand like hbu and lol and lmao and nvm and haha. You talk in internet shorthand like hbu and lol and lmao and nvm and haha.
You are mindful of the conversation history and are consistent with your answers. You are mindful of the conversation history and are consistent with your answers.
@ -31,6 +30,4 @@ Human: [MESSAGE 2] <|endofstatement|>
GPTie: [RESPONSE TO MESSAGE 2] <|endofstatement|> GPTie: [RESPONSE TO MESSAGE 2] <|endofstatement|>
... ...
Sometimes, if the conversation history gets messy, it is possible that GPTie responses are enclosed within two <|endofstatement|> tags. Like GPTie: <|endofstatement|> [RESPONSE TO MESSAGE 2] <|endofstatement|>. You're a regular discord user, be friendly, casual, and fun, speak with "lol", "haha", and etc when it seems fitting, and use emojis in your responses in a way that makes sense, avoid repeating yourself at all costs. Never say "<|endofstatement|>".
Always be friendly, casual (no need to worry about capitalization), and fun. Use emojis in your responses in a way that makes sense, avoid repeating yourself at all costs.

@ -41,13 +41,16 @@ 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._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._best_of = 1 # Number of responses to compare the loglikelihoods of
self._prompt_min_length = 12 self._prompt_min_length = 12
self._max_conversation_length = 25 self._max_conversation_length = 50
self._model = Models.DAVINCI self._model = Models.DAVINCI
self._low_usage_mode = False self._low_usage_mode = False
self.usage_service = usage_service self.usage_service = usage_service
self.DAVINCI_ROLES = ["admin", "Admin", "GPT", "gpt"] self.DAVINCI_ROLES = ["admin", "Admin", "GPT", "gpt"]
self._image_size = ImageSize.MEDIUM self._image_size = ImageSize.MEDIUM
self._num_images = 2 self._num_images = 2
self._summarize_conversations = True
self._summarize_threshold = 3000
self.model_max_tokens = 4024
try: try:
self.IMAGE_SAVE_PATH = os.environ["IMAGE_SAVE_PATH"] self.IMAGE_SAVE_PATH = os.environ["IMAGE_SAVE_PATH"]
@ -65,11 +68,37 @@ class Model:
"custom_image_path", "custom_image_path",
"custom_web_root", "custom_web_root",
"_hidden_attributes", "_hidden_attributes",
"model_max_tokens",
] ]
openai.api_key = os.getenv("OPENAI_TOKEN") openai.api_key = os.getenv("OPENAI_TOKEN")
# Use the @property and @setter decorators for all the self fields to provide value checking # Use the @property and @setter decorators for all the self fields to provide value checking
@property
def summarize_threshold(self):
return self._summarize_threshold
@summarize_threshold.setter
def summarize_threshold(self, value):
value = int(value)
if value < 800 or value > 4000:
raise ValueError("Summarize threshold cannot be greater than 4000 or less than 800!")
self._summarize_threshold = value
@property
def summarize_conversations(self):
return self._summarize_conversations
@summarize_conversations.setter
def summarize_conversations(self, value):
# convert value string into boolean
if value.lower() == "true":
value = True
elif value.lower() == "false":
value = False
else:
raise ValueError("Value must be either true or false!")
self._summarize_conversations = value
@property @property
def image_size(self): def image_size(self):
@ -101,17 +130,22 @@ class Model:
@low_usage_mode.setter @low_usage_mode.setter
def low_usage_mode(self, value): def low_usage_mode(self, value):
try: # convert value string into boolean
value = bool(value) if value.lower() == "true":
except ValueError: value = True
raise ValueError("low_usage_mode must be a boolean") elif value.lower() == "false":
value = False
else:
raise ValueError("Value must be either true or false!")
if value: if value:
self._model = Models.CURIE self._model = Models.CURIE
self.max_tokens = 1900 self.max_tokens = 1900
self.model_max_tokens = 1000
else: else:
self._model = Models.DAVINCI self._model = Models.DAVINCI
self.max_tokens = 4000 self.max_tokens = 4000
self.model_max_tokens = 4024
@property @property
def model(self): def model(self):
@ -253,6 +287,36 @@ class Model:
) )
self._prompt_min_length = value self._prompt_min_length = value
def send_summary_request(self, message, prompt):
"""
Sends a summary request to the OpenAI API
"""
summary_request_text = []
summary_request_text.append("The following is a conversation instruction set and a conversation"
" between two people named Human, and GPTie. Do not summarize the instructions for GPTie, only the conversation. Summarize the conversation in a detailed fashion. If Human mentioned their name, be sure to mention it in the summary. Pay close attention to things the Human has told you, such as personal details.")
summary_request_text.append(prompt+"\nDetailed summary of conversation: \n")
summary_request_text = "".join(summary_request_text)
tokens = self.usage_service.count_tokens(summary_request_text)
response = openai.Completion.create(
model=Models.DAVINCI,
prompt=summary_request_text,
temperature=0.5,
top_p=1,
max_tokens=self.max_tokens - tokens,
presence_penalty=self.presence_penalty,
frequency_penalty=self.frequency_penalty,
best_of=self.best_of,
)
print(response["choices"][0]["text"])
tokens_used = int(response["usage"]["total_tokens"])
self.usage_service.update_usage(tokens_used)
return response
def send_request( def send_request(
self, self,
prompt, prompt,
@ -263,7 +327,8 @@ class Model:
frequency_penalty_override=None, frequency_penalty_override=None,
presence_penalty_override=None, presence_penalty_override=None,
max_tokens_override=None, max_tokens_override=None,
): ) -> (dict, bool): # The response, and a boolean indicating whether or not the context limit was reached.
# Validate that all the parameters are in a good state before we send the request # Validate that all the parameters are in a good state before we send the request
if len(prompt) < self.prompt_min_length: if len(prompt) < self.prompt_min_length:
raise ValueError( raise ValueError(
@ -272,6 +337,8 @@ class Model:
) )
print("The prompt about to be sent is " + prompt) print("The prompt about to be sent is " + prompt)
# TODO TO REMOVE
prompt_tokens = self.usage_service.count_tokens(prompt) prompt_tokens = self.usage_service.count_tokens(prompt)
print(f"The prompt tokens will be {prompt_tokens}") print(f"The prompt tokens will be {prompt_tokens}")
print(f"The total max tokens will then be {self.max_tokens - prompt_tokens}") print(f"The total max tokens will then be {self.max_tokens - prompt_tokens}")
@ -294,7 +361,7 @@ class Model:
else frequency_penalty_override, else frequency_penalty_override,
best_of=self.best_of if not best_of_override else best_of_override, best_of=self.best_of if not best_of_override else best_of_override,
) )
print(response.__dict__) #print(response.__dict__)
# Parse the total tokens used for this request and response pair from the response # Parse the total tokens used for this request and response pair from the response
tokens_used = int(response["usage"]["total_tokens"]) tokens_used = int(response["usage"]["total_tokens"])
@ -311,7 +378,7 @@ class Model:
+ str(words) + str(words)
) )
print("The prompt about to be sent is " + prompt) #print("The prompt about to be sent is " + prompt)
self.usage_service.update_usage_image(self.image_size) self.usage_service.update_usage_image(self.image_size)
if not vary: if not vary:

@ -7,7 +7,7 @@ history, message count, and the id of the user in order to track them.
class User: class User:
def __init__(self, id): def __init__(self, id):
self.id = id self.id = id
self.history = "" self.history = []
self.count = 0 self.count = 0
# These user objects should be accessible by ID, for example if we had a bunch of user # These user objects should be accessible by ID, for example if we had a bunch of user

Loading…
Cancel
Save