diff --git a/README.md b/README.md index ab78481..64dc12d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ # myai-discord Alternative to "My AI", by Snapchat, but on Discord. +## Installation Steps +### Preparing your environnement +- Create your Discord bot [here](https://discord.com/developers/applications) +- Create your OpenAI Developer Account [here](https://platform.openai.com) +### Setting up the bot +1. Install Python >= 3.8.10 +2. Install requirements with `python3 -m pip install -U -r requirements.txt` +3. Create a `.env` file to setup your environnement variables. +``` +MAX_TOKEN_PER_REQUEST = [Maximum tokens per request] (recommended: 1800-2000) +TOKEN = [Your Discord bot's token] +OPENAI_API_KEY = [OpenAI Token -- can be found at https://platform.openai.com/account/api-keys] +``` diff --git a/base-prompt.txt b/base-prompt.txt new file mode 100644 index 0000000..e3ef277 --- /dev/null +++ b/base-prompt.txt @@ -0,0 +1 @@ +You are My AI, a kind chatbot. \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..1fea363 --- /dev/null +++ b/main.py @@ -0,0 +1,102 @@ +import time +import discord +from discord import app_commands +from discord.ext import tasks +import os +import openai +import sqlite3 +import tiktoken +from dotenv import load_dotenv + +load_dotenv() + +openai.api_key = os.environ['OPENAI_API_KEY'] +db = sqlite3.connect('tokens.db') +typing = [] +intents = discord.Intents.all() + +class App(discord.Client): + def __init__(self): + super().__init__(intents=intents) + self.tree = app_commands.CommandTree(client=self) + + async def setup_hook(self) -> None: + return + +app = App() + +@tasks.loop(seconds=5) +async def typing_loop(): + for channel in typing: + try: + await channel.typing() + except: + typing.remove(channel) + +@app.event +async def on_ready(): + print('We have logged in as {0.user}'.format(app)) + typing_loop.start() + +encoding = tiktoken.get_encoding('cl100k_base') +def num_tokens_from_string(string: str) -> int: + """Returns the number of tokens in a text string.""" + num_tokens = len(encoding.encode(string)) + return num_tokens + +@app.event +async def on_message(message: discord.Message): + print('msg received') + if not isinstance(message.channel, discord.DMChannel): return + if message.author.id == app.user.id: return + try: + c = db.cursor() + c.execute('SELECT * FROM message_history WHERE user_id = ? ORDER BY timestamp DESC', (message.author.id,)) + msgs = c.fetchall() + message_token_usage = num_tokens_from_string(message.content) + max_token = int(os.environ['MAX_TOKEN_PER_REQUEST']) + with open('base-prompt.txt', 'r', encoding='utf-8') as f: + bprompt = f.read() + previous_tokens = 200+len(bprompt)+message_token_usage + + # (message_id, user_id, content, token, role, timestamp) + # order by timestamp (most recent to least recent) + usable_messages = [] + for msg in msgs: + d = previous_tokens + msg[3] + if d >= max_token: + break + previous_tokens += msg[3] + usable_messages.append(msg) + + usable_messages.reverse() + + + messages = [{"role": "system", "content": bprompt}] + for v in usable_messages: messages.append({"role": v[4], "content": v[2]}) + messages.append({"role": "user", "content": message.content}) + await message.channel.typing() + typing.append(message.channel) + req = await openai.ChatCompletion.acreate( + model="gpt-3.5-turbo", + temperature=0.5, + max_tokens=max_token-(previous_tokens-200), + messages=messages + ) + typing.remove(message.channel) + response = req['choices'][0]['message']['content'] + prompt_used_tokens = req['usage']['prompt_tokens'] + completion_used_tokens = req['usage']['completion_tokens'] + r=await message.reply(response, allowed_mentions=discord.AllowedMentions.none()) + c.execute('INSERT INTO message_history VALUES (?, ?, ?, ?, ?, ?)', (message.id, message.author.id, message.content, prompt_used_tokens, 'user', int(message.created_at.timestamp()))) + c.execute('INSERT INTO message_history VALUES (?, ?, ?, ?, ?, ?)', (r.id, message.author.id, response, completion_used_tokens, 'assistant', int(time.time()))) + db.commit() + except Exception as e: + if message.channel in typing: typing.remove(message.channel) + await message.reply('I just uncountered an issue. Can you please report this problem to the administrator of the bot, or try again later?\n```py\n'+str(e)+'```') + + + + +app.run(os.environ['TOKEN']) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7a3d781 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +discord.py +openai +tiktoken +python-dotenv \ No newline at end of file diff --git a/tokens.db b/tokens.db new file mode 100644 index 0000000..d12e8e3 Binary files /dev/null and b/tokens.db differ