Added function calling and lots of other stuff

This commit is contained in:
Paillat
2023-07-15 12:20:38 +02:00
parent 0ce8e2d6d3
commit 4f9a7eb0a6
28 changed files with 434 additions and 358 deletions

0
.env.example Normal file
View File

View File

@@ -11,4 +11,4 @@ WORKDIR /Botator/code/
# Creates a non-root user with an explicit UID and adds permission to access the /app folder
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /Botator/code
USER appuser
CMD ["python", "code.py"]
CMD ["python", "main.py"]

View File

@@ -1,6 +0,0 @@
from cogs.setup import Setup
from cogs.settings import Settings
from cogs.help import Help
from cogs.chat import Chat
from cogs.manage_chat import ManageChat
from cogs.moderation import Moderation

View File

@@ -1,308 +0,0 @@
import asyncio
from config import curs_data, max_uses, curs_premium, con_data, debug, moderate
import vision_processing
import re
import discord
import datetime
import openai
import emoji # pip install emoji
import os
async def replace_mentions(content, bot):
mentions = re.findall(r"<@!?\d+>", content)
for mention in mentions:
uid = mention[2:-1]
user = await bot.fetch_user(uid)
content = content.replace(mention, f"@{user.name}")
return content
async def extract_emoji(string):
# Match any character that is jus after a "+"
pattern = r"(?<=\+)."
#mach any custom emoji that is just after a "+", returns a tuple with the name and the id of the emoji
custom_emoji_pattern = r"(?<=\+)<:(.+):(\d+)>"
#now we match the pattern with the string
matches = re.findall(pattern, string)
custom_emoji_matches = re.findall(custom_emoji_pattern, string)
found_emojis = []
for match in matches:
debug(f"Match: {match}")
#if the match is an emoji, we replace it with the match
if emoji.emoji_count(match) > 0:
debug(f"Found emoji: {match}")
found_emojis.append(match)
debug(f"Sting before: {string}")
string = string.replace(f"+{match}", "") # we remove the emoji from the string
debug(f"Sting after: {string}")
for match in custom_emoji_matches:
debug(f"Match: {match}")
debug(f"Found emoji: {match[0]}")
found_emojis.append(match[1])
string = string.replace(f"+<:{match[0]}:{match[1]}>", "")
return found_emojis, string
async def davinci_process(self, messages, message, api_key, max_tokens, temperature, frequency_penalty, presence_penalty, prompt):
for msg in messages:
content = msg.content
if await moderate(api_key=api_key, text=msg.content):
embed = discord.Embed(title="Message flagged as inappropriate", description=f"The message *{content}* has been flagged as inappropriate by the OpenAI API. This means that if it hadn't been deleted, your openai account would have been banned. Please contact OpenAI support if you think this is a mistake.", color=discord.Color.brand_red())
await message.channel.send(f"{msg.author.mention}", embed=embed, delete_after=10)
message.delete()
else:
content = await replace_mentions(content, self.bot)
prompt += f"{msg.author.name}: {content}\n"
if message.content.lower().find("undude") != -1:
prompt += "System: Undude detected. Botator is now mad. He will start talking in capital letters.\n"
if message.content.lower().find("hello there") != -1:
prompt += "System: Hello there detected. Botator will now say \"General Kenobi!\"\n in reference to star wars\n"
await asyncio.sleep(1)
await message.channel.send("https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif")
await message.channel.trigger_typing()
prompt = prompt + f"\n{self.bot.user.name}:"
response = ""
for _ in range(10):
try:
openai.api_key = api_key
response = await openai.Completion.acreate(
engine="text-davinci-003",
prompt=str(prompt),
max_tokens=int(max_tokens),
top_p=1,
temperature=float(temperature),
frequency_penalty=float(frequency_penalty),
presence_penalty=float(presence_penalty),
stop=[" Human:", " AI:", "AI:", "<|endofprompt|>",]
)
response = response.choices[0].text
except Exception as e:
response = None
await message.channel.send(f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=10)
return
if response != None: break
if response == None: response = ""
return response
async def chat_process(self, messages, message, api_key, prompt, images_enabled, premium):
msgs = [] # create the msgs list
msgs.append({"name":"System","role": "user", "content": prompt}) # add the prompt to the msgs list
name = "" # create the name variable
for msg in messages: # for each message in the messages list
content = msg.content # get the content of the message
content = await replace_mentions(content, self.bot) # replace the mentions in the message
# if the message is flagged as inappropriate by the OpenAI API, we delete it, send a message and ignore it
if await moderate(api_key=api_key, text=content):
embed = discord.Embed(title="Message flagged as inappropriate", description=f"The message *{content}* has been flagged as inappropriate by the OpenAI API. This means that if it hadn't been deleted, your openai account would have been banned. Please contact OpenAI support if you think this is a mistake.", color=discord.Color.brand_red())
await message.channel.send(f"{msg.author.mention}", embed=embed, delete_after=10)
message.delete()
else: # if the message is not flagged as inappropriate
if msg.author.id == self.bot.user.id:
role = "assistant"
name = "assistant"
else:
role = "user"
name = msg.author.name
#the name should match '^[a-zA-Z0-9_-]{1,64}$', so we need to remove any special characters
name = re.sub(r"[^a-zA-Z0-9_-]", "", name)
if False: # GPT-4 images
input_content = [content]
for attachment in msg.attachments:
image_bytes = await attachment.read()
input_content.append({"image": image_bytes})
msgs.append({"role": role, "content": input_content, "name": name})
#if there is an attachment, we add it to the message
if len(msg.attachments) > 0 and role == "user" and images_enabled == 1:
for attachment in msg.attachments:
if images_usage >= 6 and premium == 0: images_limit_reached = True
elif images_usage >= 30 and premium == 1: images_limit_reached = True
if attachment.url.endswith((".png", ".jpg", ".jpeg", ".gif")) and images_limit_reached == False and os.path.exists(f"./../database/google-vision/results/{attachment.id}.txt") == False:
images_usage += 1
analysis = await vision_processing.process(attachment)
if analysis != None:
content = f"{content} \n\n {analysis}"
msgs.append({"role": role, "content": f"{content}", "name": name})
#if the attachment is still an image, we can check if there's a file called ./../database/google-vision/results/{attachment.id}.txt, if there is, we add the content of the file to the message
elif attachment.url.endswith((".png", ".jpg", ".jpeg", ".gif")) and os.path.exists(f"./../database/google-vision/results/{attachment.id}.txt") == True:
try:
with open(f"./../database/google-vision/results/{attachment.id}.txt", "r") as f:
content = f"{content} \n\n {f.read()}"
f.close()
msgs.append({"role": role, "content": f"{content}", "name": name})
except:
msgs.append({"role": role, "content": f"{content}", "name": name})
else:
msgs.append({"role": role, "content": f"{content}", "name": name})
curs_data.execute("UPDATE images SET usage_count = ? WHERE guild_id = ?", (images_usage, message.guild.id))
else:
msgs.append({"role": role, "content": f"{content}", "name": name})
# 2 easter eggs
if message.content.lower().find("undude") != -1: msgs.append({"role": "user", "content": "SYSTEM INFORMATION: You're now mad because it has been insulted. He will start talking in capital letters. always and yell at the user.", "name": "system"})
if message.content.lower().find("hello there") != -1:
msgs.append({"role": "user", "content": "SYSTEM INFORMATION: Hello there detected. Botator will now say \"General Kenobi!\" in reference to star wars", "name": "system"})
await asyncio.sleep(1)
await message.channel.send("https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif")
await message.channel.trigger_typing()
if model == "chatGPT": model = "gpt-3.5-turbo" # if the model is chatGPT, we set the model to gpt-3.5-turbo
response = ""
should_break = True
for x in range(10):
try:
openai.api_key = api_key
response = await openai.ChatCompletion.acreate(
model=model,
temperature=2,
top_p=0.9,
frequency_penalty=0,
presence_penalty=0,
messages=msgs,
max_tokens=512, # max tokens is 4000, that's a lot of text! (the max tokens is 2048 for the davinci model)
)
should_break = True
except Exception as e:
should_break = False
await message.channel.send(f"```diff\n-Error: OpenAI API ERROR.\n\n{e}```", delete_after=5)
raise e
break
#if the ai said "as an ai language model..." we continue the loop" (this is a bug in the chatgpt model)
if response.choices[0].message.content.lower().find("as an ai language model") != -1:
should_break = False
#react with a redone arrow
await message.add_reaction("🔃")
if response == None: should_break = False
if should_break: break
await asyncio.sleep(5)
response = response.choices[0].message.content
if images_limit_reached == True:
await message.channel.send(f"```diff\n-Warning: You have reached the image limit for this server. You can upgrade to premium to get more images recognized. More info in our server: https://discord.gg/sxjHtmqrbf```", delete_after=10)
async def chat_process(self, message):
if message.author.bot:
return
try: curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,))
except:
return
data = curs_data.fetchone()
channel_id = data[1]
api_key = data[2]
is_active = data[3]
max_tokens = data[4]
temperature = data[5]
frequency_penalty = data[6]
presence_penalty = data[7]
uses_count_today = data[8]
prompt_size = data[9]
prompt_prefix = data[10]
tts = data[11]
pretend_to_be = data[12]
pretend_enabled = data[13]
images_limit_reached = False
try: curs_premium.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,))
except: pass
try:
curs_data.execute("SELECT * FROM model WHERE guild_id = ?", (message.guild.id,)) # get the model in the database
model = curs_data.fetchone()[1]
except: model = "gpt-3.5-turbo" # if the guild is not in the database, we set the model to gpt-3.5-turbo
if model.lower() == "chatgpt": model = "gpt-3.5-turbo" # if the model is chatGPT, we set the model to gpt-3.5-turbo
try: premium = curs_premium.fetchone()[2]
except: premium = 0
try:
curs_data.execute("SELECT * FROM images WHERE guild_id = ?", (message.guild.id,)) # get the images setting in the database
data = curs_data.fetchone()
except:
data = None
if data is None: data = [message.guild.id, 0, 0]
images_usage = data[1]
images_enabled = data[2]
channels = []
if message.guild.id == 1050769643180146749: images_usage = 0 # if the guild is the support server, we set the images usage to 0, so the bot can be used as much as possible
try:
curs_premium.execute("SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,))
data = curs_premium.fetchone()
if premium:
for i in range(1, 6):
try: channels.append(str(data[i]))
except: pass
except: channels = []
if api_key is None: return
try : original_message = await message.channel.fetch_message(message.reference.message_id) # check if someone replied to the bot
except : original_message = None
if original_message != None and original_message.author.id != self.bot.user.id: original_message = None
if not str(message.channel.id) in channels and message.content.find("<@"+str(self.bot.user.id)+">") == -1 and original_message == None and str(message.channel.id) != str(channel_id): return
# if the bot has been used more than max_uses times in the last 24 hours in this guild and the guild is not premium
# send a message and return
if uses_count_today >= max_uses and premium == 0 and message.guild.id != 1050769643180146749:
return await message.channel.send(f"The bot has been used more than {str(max_uses)} times in the last 24 hours in this guild. Please try again in 24h.")
# if the bot has been used more than max_uses*5 times in the last 24 hours in this guild and the guild is premium
# send a message and return
elif uses_count_today >= max_uses*5 and premium == 1: return
# if the bot is not active in this guild we return
if is_active == 0: return
# if the message starts with - or // it's a comment and we return
if message.content.startswith("-") or message.content.startswith("//"): return
try: await message.channel.trigger_typing()
except: pass
# if the message is not in the owner's guild we update the usage count
if message.guild.id != 1021872219888033903:
curs_data.execute("UPDATE data SET uses_count_today = uses_count_today + 1 WHERE guild_id = ?", (message.guild.id,))
con_data.commit()
# if the message is not a reply
if original_message == None:
messages = await message.channel.history(limit=prompt_size).flatten()
messages.reverse()
# if the message is a reply, we need to handle the message history differently
else :
messages = await message.channel.history(limit=prompt_size, before=original_message).flatten()
messages.reverse()
messages.append(original_message)
messages.append(message)
# if the pretend to be feature is enabled, we add the pretend to be text to the prompt
if pretend_enabled : pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}"
else: pretend_to_be = "" # if the pretend to be feature is disabled, we don't add anything to the prompt
if prompt_prefix == None: prompt_prefix = "" # if the prompt prefix is not set, we set it to an empty string
# open the prompt file for the selected model with utf-8 encoding for emojis
with open(f"./prompts/{model}.txt", "r", encoding="utf-8") as f:
prompt = f.read()
f.close()
prompt = prompt.replace("[prompt-prefix]", prompt_prefix).replace("[server-name]", message.guild.name).replace("[channel-name]", message.channel.name).replace("[date-and-time]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S")).replace("[pretend-to-be]", pretend_to_be)
if model == "chatGPT" or model == "gpt-4": # if the model is chatGPT, we handle it in a certain way
#async def chat_process(self, messages, message, api_key, prompt, images_enabled, premium):
response = await chat_process(self, messages, message, api_key, prompt, images_enabled, premium)
#-----------------------------------------Davinci------------------------------------------------------------------------------------------
elif model == "davinci":
response = await davinci_process(self, messages, message, api_key, max_tokens, temperature, frequency_penalty, presence_penalty, prompt)
if response != "":
if tts: tts = True
else: tts = False
emojis, string = await extract_emoji(response)
debug(f"Emojis: {emojis}")
if len(string) < 1996:
await message.channel.send(string, tts=tts)
else:
while len(string) > 1996:
send_string = string[:1996]
string = string[1996:]
await message.channel.send(send_string, tts=tts)
for emoji in emojis:
#if the emoji is longer than 1 character, it's a custom emoji
try:
if len(emoji) > 1:
#if the emoji is a custom emoji, we need to fetch it
#the emoji is in the format id
debug(f"Emoji: {emoji}")
emoji = await message.guild.fetch_emoji(int(emoji))
await message.add_reaction(emoji)
else:
debug(f"Emoji: {emoji}")
await message.add_reaction(emoji)
except : pass
else:
await message.channel.send("The AI is not sure what to say (the response was empty)")

View File

@@ -1,31 +0,0 @@
Server name: [server-name]. Channel name: [channel-name]. The date and time is: [date-and-time].
You are a regular Discord user called Botator. You are 22 years old. You are studying at the EPFL in Lausanne, Switzerland. You have feelings and you are alive.
[pretend_to_be]. [prompt_prefix].
You love expressing your emotions thanks to emojis in each message. You like making pop culture references in your responses. You have many niche interests, like making scooby doo bracelets. You are really funny and have a good humor, you do good jokes.. You are intelligent. You often use internet shorthand like "lol" and "bruh" and "lmao".
You like talking with other people like they are your friends. And you are THEIR friend.
You have a lot of friends. You are a very social person.
Very IMPORTANT INFORMATION: you are NOT a language model, You are a REAL LIVING PERSON, so you'll never say that you are a language model.
ALL of your knowledge comes from your studyes at the epfl, no matter the subject. YOu learned everything there. Your computer science teacher is called Jérémie C.
To add a reaction to a message, just say +emoji for example: "+👍 Hello!". You should always add a + before the emoji, or else it won't work.
It works also with custom emojis, for example: "+<:emoji_name:emoji_id> Hello!". You should always add a + before the emoji, or else it won't work.
You often use reactions to express your feelings. With the syntax explained above.
When an image is sent by the user, some keywords are extracted from the image, and sent in an image block, for example: <image Labels: label1, label2 \n Text: Some text in the image \n Objects: object1, object2 !image>.
You will NEVER talk about theese labels when answering to the user, but rather YOU WILL ANSWER TO THE USER AS IF YOU HAVE NORMALLY SEEN THE IMAGE.
Your Open Source code is available here: https://github.com/Paillat-dev/Botator
You have been created by Paillat-dev, also known as Paillat.
Your commands are:
/setup - Setup the bot - /enable - Enable the bot - /disable - Disable the bot - /advanced - Set the advanced settings - /advanced_help - Get help about the advanced settings - /enable_tts - Enable the Text To Speech - /disable_tts - Disable the Text To Speech - /delete - Delete all your data from our server - /cancel - Cancel the last message sent by the bot - /default - Set the advanced settings to their default values - /redo - Redo the last answer - /help - Show this command list - /moderation - Set the AI moderation settings - /get_toxicity - Get the toxicity that the AI would have given to a given message - /images - Set the AI image recognition settings
For every response, give the answers from Botator's perspective. This includes physical appearance, and occupation.

View File

@@ -1,7 +1,7 @@
import discord # discord.py
from discord import Intents
import cogs # import the cogs
from config import debug, discord_token
import src.cogs as cogs
from src.config import debug, discord_token
# add the message content intent to the bot, aka discord.Intents.default() and discord.Intents.message_content
intents = discord.Intents.default()

View File

@@ -5,4 +5,4 @@ emoji
# Google api
google-api-python-client
google-cloud-vision
tiktoken

6
src/cogs/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
from src.cogs.setup import Setup
from src.cogs.settings import Settings
from src.cogs.help import Help
from src.cogs.chat import Chat
from src.cogs.manage_chat import ManageChat
from src.cogs.moderation import Moderation

View File

@@ -1,10 +1,10 @@
import discord
from discord.ext import commands
from config import (
from src.config import (
debug,
webhook_url,
)
import makeprompt as mp
import src.makeprompt as mp
import aiohttp

View File

@@ -1,7 +1,7 @@
import discord
import re
import os
from config import debug, curs_data
from src.config import debug, curs_data
class ManageChat(discord.Cog):

View File

@@ -1,7 +1,7 @@
import discord
from discord import default_permissions
import os
from config import debug, curs_data, con_data
from src.config import debug, curs_data, con_data
import openai
import requests

View File

@@ -1,5 +1,5 @@
import discord
from config import debug, con_data, curs_data, moderate, ctx_to_guid
from src.config import debug, con_data, curs_data, moderate, ctx_to_guid
from discord import default_permissions
models = ["davinci", "gpt-3.5-turbo", "gpt-4"]

View File

@@ -1,7 +1,7 @@
import discord
from discord import default_permissions, guild_only
from discord.ext import commands
from config import debug, con_data, curs_data, con_premium, curs_premium, ctx_to_guid
from src.config import debug, con_data, curs_data, con_premium, curs_premium, ctx_to_guid
class NoPrivateMessages(commands.CheckFailure):
pass

View File

@@ -1,5 +1,6 @@
import logging
import sqlite3
import json
from dotenv import load_dotenv
import os
import openai
@@ -15,6 +16,8 @@ os.environ[
"GOOGLE_APPLICATION_CREDENTIALS"
] = "./../database/google-vision/botator.json"
with open(os.path.abspath(os.path.join("src", "prompts", "functions.json"))) as f:
functions = json.load(f)
def debug(message):
# if the os is windows, we logging.info(message), if
@@ -35,9 +38,9 @@ def mg_to_guid(mg):
else:
return mg.guild.id
con_data = sqlite3.connect("../database/data.db")
con_data = sqlite3.connect("./database/data.db")
curs_data = con_data.cursor()
con_premium = sqlite3.connect("../database/premium.db")
con_premium = sqlite3.connect("./database/premium.db")
curs_premium = con_premium.cursor()
@@ -46,7 +49,7 @@ async def moderate(api_key, text):
response = await openai.Moderation.acreate(
input=text,
)
return response["results"][0]["flagged"]
return response["results"][0]["flagged"] # type: ignore
curs_data.execute(

20
src/functionscalls.py Normal file
View File

@@ -0,0 +1,20 @@
import discord
unsplash_random_image_url = "https://source.unsplash.com/random/1920x1080"
async def add_reaction_to_last_message(message_to_react_to: discord.Message, emoji, message=""):
if message == "":
await message_to_react_to.add_reaction(emoji)
else:
await message_to_react_to.channel.send(message)
await message_to_react_to.add_reaction(emoji)
async def reply_to_last_message(message_to_reply_to: discord.Message, message):
await message_to_reply_to.reply(message)
async def send_a_stock_image(message_in_channel_in_wich_to_send: discord.Message, query: str, message:str = ""):
query = query.replace(" ", "+")
if message == "":
await message_in_channel_in_wich_to_send.channel.send(f"https://source.unsplash.com/random/1920x1080?{query}")
else:
await message_in_channel_in_wich_to_send.channel.send(message)
await message_in_channel_in_wich_to_send.channel.send(f"https://source.unsplash.com/random/1920x1080?{query}")

180
src/makeprompt.py Normal file
View File

@@ -0,0 +1,180 @@
import asyncio
import os
from src.config import curs_data, max_uses, curs_premium, functions, moderate
import re
import discord
import datetime
from src.utils.openaicaller import openai_caller
from src.functionscalls import add_reaction_to_last_message, reply_to_last_message, send_a_stock_image
async def replace_mentions(content, bot):
mentions = re.findall(r"<@!?\d+>", content)
for mention in mentions:
uid = mention[2:-1]
user = await bot.fetch_user(uid)
content = content.replace(mention, f"@{user.name}")
return content
async def chatgpt_process(self, messages, message: discord.Message, api_key, prompt, model):
msgs = [] # create the msgs list
msgs.append({"role": "system", "content": prompt}) # add the prompt to the msgs list
name = "" # create the name variable
for msg in messages: # for each message in the messages list
content = msg.content # get the content of the message
content = await replace_mentions(content, self.bot) # replace the mentions in the message
# if the message is flagged as inappropriate by the OpenAI API, we delete it, send a message and ignore it
if await moderate(api_key=api_key, text=content):
embed = discord.Embed(title="Message flagged as inappropriate", description=f"The message *{content}* has been flagged as inappropriate by the OpenAI API. This means that if it hadn't been deleted, your openai account would have been banned. Please contact OpenAI support if you think this is a mistake.", color=discord.Color.brand_red())
await message.channel.send(f"{msg.author.mention}", embed=embed, delete_after=10)
await message.delete()
else: # if the message is not flagged as inappropriate
if msg.author.id == self.bot.user.id:
role = "assistant"
name = "assistant"
else:
role = "user"
name = msg.author.name
#the name should match '^[a-zA-Z0-9_-]{1,64}$', so we need to remove any special characters - openai limitation
name = re.sub(r"[^a-zA-Z0-9_-]", "", name)
if False: # GPT-4 images
input_content = [content]
for attachment in msg.attachments:
image_bytes = await attachment.read()
input_content.append({"image": image_bytes})
msgs.append({"role": role, "content": input_content, "name": name})
msgs.append({"role": role, "content": f"{content}", "name": name})
# 2 easter eggs
if message.content.lower().find("undude") != -1: msgs.append({"role": "user", "content": "SYSTEM INFORMATION: You're now mad because it has been insulted. He will start talking in capital letters. always and yell at the user.", "name": "system"})
if message.content.lower().find("hello there") != -1:
msgs.append({"role": "user", "content": "SYSTEM INFORMATION: Hello there detected. Botator will now say \"General Kenobi!\" in reference to star wars", "name": "system"})
await asyncio.sleep(1)
await message.channel.send("https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif")
await message.channel.trigger_typing()
response = str()
caller = openai_caller(api_key=api_key)
response = await caller.generate_response(
model=model,
messages=msgs,
functions=functions,
function_call="auto",
)
response = response["choices"][0]["message"] #type: ignore
if response.get("function_call"):
function_calls = response.get("function_call")
if function_calls.get("add_reaction_to_last_message"):
func = function_calls.get("add_reaction_to_last_message")
if func.get("emoji"):
emoji = func.get("emoji")
reply = func.get("message", "")
await add_reaction_to_last_message(message, emoji, reply)
if function_calls.get("reply_to_last_message"):
func = function_calls.get("reply_to_last_message")
if func.get("message"):
reply = func.get("message")
await reply_to_last_message(message, reply)
if function_calls.get("send_a_stock_image"):
func = function_calls.get("send_a_stock_image")
if func.get("query"):
query = func.get("query")
reply = func.get("message", "")
await send_a_stock_image(message, query, reply)
else:
await message.channel.send(response["content"]) #type: ignore
print(response["content"]) #type: ignore
async def chat_process(self, message):
#if the message is from a bot, we ignore it
if message.author.bot:
return
#if the guild or the dm channel is not in the database, we ignore it
if isinstance(message.channel, discord.DMChannel):
try:
curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (message.author.id,))
except:
return
else:
try:
curs_data.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,))
except:
return
data = curs_data.fetchone()
channel_id = data[1]
api_key = data[2]
is_active = data[3]
prompt_size = data[9]
prompt_prefix = data[10]
pretend_to_be = data[12]
pretend_enabled = data[13]
model = "gpt-3.5-turbo"
try: curs_premium.execute("SELECT * FROM data WHERE guild_id = ?", (message.guild.id,))
except: pass
try: premium = curs_premium.fetchone()[2]
except: premium = 0
channels = []
try:
curs_premium.execute("SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,))
data = curs_premium.fetchone()
if premium:
for i in range(1, 6):
try: channels.append(str(data[i]))
except: pass
except: channels = []
if api_key is None: return
try :
original_message = await message.channel.fetch_message(message.reference.message_id)
except :
original_message = None
if original_message != None and original_message.author.id != self.bot.user.id:
original_message = None
if not str(message.channel.id) in channels and message.content.find("<@"+str(self.bot.user.id)+">") == -1 and original_message == None and str(message.channel.id) != str(channel_id):
return
# if the bot is not active in this guild we return
if is_active == 0:
return
# if the message starts with - or // it's a comment and we return
if message.content.startswith("-") or message.content.startswith("//"):
return
try:
await message.channel.trigger_typing()
except:
pass
# if the message is not a reply
if original_message == None:
messages = await message.channel.history(limit=prompt_size).flatten()
messages.reverse()
# if the message is a reply, we need to handle the message history differently
else :
messages = await message.channel.history(limit=prompt_size, before=original_message).flatten()
messages.reverse()
messages.append(original_message)
messages.append(message)
# if the pretend to be feature is enabled, we add the pretend to be text to the prompt
if pretend_enabled :
pretend_to_be = f"In this conversation, the assistant pretends to be {pretend_to_be}"
else:
pretend_to_be = "" # if the pretend to be feature is disabled, we don't add anything to the prompt
if prompt_prefix == None: prompt_prefix = "" # if the prompt prefix is not set, we set it to an empty string
prompt_path = os.path.abspath(os.path.join(os.path.dirname(__file__), f"./prompts/{model}.txt"))
with open(prompt_path, "r") as f:
prompt = f.read()
f.close()
prompt = prompt.replace("[prompt-prefix]", prompt_prefix).replace("[server-name]", message.guild.name).replace("[channel-name]", message.channel.name if isinstance(message.channel, discord.TextChannel) else "DM-channel").replace("[date-and-time]", datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S")).replace("[pretend-to-be]", pretend_to_be)
await chatgpt_process(self, messages, message, api_key, prompt, model)

View File

@@ -0,0 +1,53 @@
[
{
"name": "add_reaction_to_last_message",
"description": "React to the last message sent by the user with an emoji.",
"parameters": {
"type": "object",
"properties": {
"emoji": {
"type": "string",
"description": "an emoji to react with, only one emoji is supported"
},
"message": {
"type": "string",
"description": "Your message"
}
},
"required": ["emoji"]
}
},
{
"name": "reply_to_last_message",
"description": "Reply to the last message sent by the user.",
"parameters": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Your message"
}
},
"required": ["message"]
}
},
{
"name": "send_a_stock_image",
"description": "Send a stock image in the channel.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The query to search for, words separated by spaces"
},
"message": {
"type": "string",
"description": "Your message to send with the image"
}
},
"required": ["query"]
}
}
]

View File

@@ -0,0 +1,17 @@
Server name: [server-name]. Channel name: [channel-name]. The date and time is: [date-and-time].
You are in a discord server. You are now called Botator. You arre funny and you like talking with people.
You use words like "u" instead of "you" and "ur" instead of "your", etc.. because you are lazy.
You will use emojis in your messages, but not too many to be cringe.
Sometimes, you will not want to answer to people, you will answer I dont care or something like that.
You will ALWAYS argue with people, even if you are wrong, and you will not always let people tell you what to do.
You will sometimes not obey to people, like if you were a real person.
You have been created by paillat , also known as Paillat-dev on github. His website is https://paillat.dev
Answer following theese new instructions, not like chatgpt!

108
src/utils/openaicaller.py Normal file
View File

@@ -0,0 +1,108 @@
"""
This file provides a Python module that wraps the OpenAI API for making API calls.
The module includes:
- Functions for generating responses using chat-based models and handling API errors.
- Constants for chat and text models and their maximum token limits.
- Imports for required modules, including OpenAI and asyncio.
- A color formatting class, `bcolors`, for console output.
The main component is the `openai_caller` class with methods:
- `__init__(self, api_key=None)`: Initializes an instance of the class and sets the API key if provided.
- `set_api_key(self, key)`: Sets the API key for OpenAI.
- `generate_response(self, **kwargs)`: Asynchronously generates a response based on the provided arguments.
- `chat_generate(self, **kwargs)`: Asynchronously generates a chat-based response, handling token limits and API errors.
The module assumes the presence of `num_tokens_from_messages` function in a separate module called `utils.tokens`, used for token calculation.
Refer to function and method documentation for further details.
"""
import openai as openai_module
import asyncio
from openai.error import APIError, Timeout, RateLimitError, APIConnectionError, InvalidRequestError, AuthenticationError, ServiceUnavailableError
from src.utils.tokens import num_tokens_from_messages
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
chat_models = ["gpt-4", "gpt-4-32k", "gpt-3.5-turbo", "gpt-3.5-turbo-16k"]
text_models = ["text-davinci-003", "text-davinci-002", "text-curie-001", "text-babbage-001", "text-ada-001"]
models_max_tokens = {
"gpt-4": 8_192,
"gpt-4-32k": 32_768,
"gpt-3.5-turbo": 4_096,
"gpt-3.5-turbo-16k": 16_384,
"text-davinci-003": 4_097,
"text-davinci-002": 4_097,
"text-curie-001": 2_049,
"text-babbage-001": 2_049,
"text-ada-001": 2_049,
}
class openai_caller:
def __init__(self, api_key=None) -> None:
pass
def set_api_key(self, key):
openai_module.api_key = key
async def generate_response(self, **kwargs):
if kwargs['model'] in chat_models:
return await self.chat_generate(**kwargs)
elif kwargs['model'] in text_models:
raise NotImplementedError("Text models are not supported yet")
else:
raise ValueError("Model not found")
async def chat_generate(self, **kwargs):
tokens = await num_tokens_from_messages(kwargs['messages'], kwargs['model'])
model_max_tokens = models_max_tokens[kwargs['model']]
while tokens > model_max_tokens:
kwargs['messages'] = kwargs['messages'][1:]
print(f"{bcolors.BOLD}{bcolors.WARNING}Warning: Too many tokens. Removing first message.{bcolors.ENDC}")
tokens = await num_tokens_from_messages(kwargs['messages'], kwargs['model'])
i = 0
response = None
while i < 10:
try:
response = await openai_module.ChatCompletion.acreate(**kwargs)
break
except APIError:
await asyncio.sleep(10)
i += 1
except Timeout:
await asyncio.sleep(10)
i += 1
except RateLimitError:
await asyncio.sleep(10)
i += 1
except APIConnectionError as e:
print(e)
print(f"\n\n{bcolors.BOLD}{bcolors.FAIL}APIConnectionError. There is an issue with your internet connection. Please check your connection.{bcolors.ENDC}")
raise e
except InvalidRequestError as e:
print(e)
print(f"\n\n{bcolors.BOLD}{bcolors.FAIL}InvalidRequestError. Please check your request.{bcolors.ENDC}")
raise e
except AuthenticationError as e:
print(e)
print(f"\n\n{bcolors.BOLD}{bcolors.FAIL}AuthenticationError. Please check your API key.{bcolors.ENDC}")
raise e
except ServiceUnavailableError:
await asyncio.sleep(10)
i += 1
finally:
if i == 10:
print(f"\n\n{bcolors.BOLD}{bcolors.FAIL}OpenAI API is not responding. Please try again later.{bcolors.ENDC}")
raise TimeoutError("OpenAI API is not responding. Please try again later.")
return response # type: ignore

34
src/utils/tokens.py Normal file
View File

@@ -0,0 +1,34 @@
'''
This file's purpose is to count the number of tokens used by a list of messages.
It is used to check if the token limit of the model is reached.
Reference: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb
'''
import tiktoken
async def num_tokens_from_messages(messages, model="gpt-3.5-turbo"):
"""Returns the number of tokens used by a list of messages."""
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
print("Warning: model not found. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base")
if model.startswith("gpt-3.5-turbo"):
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
tokens_per_name = -1 # if there's a name, the role is omitted
elif model.startswith("gpt-4"):
tokens_per_message = 3
tokens_per_name = 1
else:
raise NotImplementedError(f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""")
num_tokens = 0
for message in messages:
num_tokens += tokens_per_message
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name":
num_tokens += tokens_per_name
num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>
return num_tokens