-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.py
339 lines (271 loc) · 14.9 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
from dotenv import load_dotenv
load_dotenv()
import utils
from src.server import keep_alive
from src.memory import Memory
from src.models import OpenAIModel
from src.chatgpt import ChatGPT, DALLE
from src.logger import logger
from src.discordBot import DiscordClient, Sender
from typing import Optional
from datetime import datetime
import asyncio
import random
import discord
from discord.ext import tasks
import os
from plugins import error_debugger
from plugins import gpt_llama_cpp
from plugins import deez_nuts
from plugins import chatbot
from plugins import reddit_bot
from plugins import google
from plugins import web
from plugins import pygmalion
from plugins import social_agi
bot_name = os.getenv('BOT_NAME')
model_name = os.getenv('MODEL_NAME')
models = OpenAIModel(api_key=os.getenv('OPENAI_API'),
model_engine=os.getenv('OPENAI_MODEL_ENGINE'))
memory = Memory(system_message=os.getenv('SYSTEM_MESSAGE'))
chatgpt = ChatGPT(models, memory)
dalle = DALLE(models)
def run():
client = DiscordClient()
sender = Sender()
# Utils
async def use_plugin(
user,
channel,
input,
prompt,
request_message=None, # User's original message
response_type: Optional[str] = 'completion', # 'completion' or 'chat'
show_prompt: Optional[bool] = False,
require_request_message: Optional[bool] = True,
response_prefix: Optional[str] = '',
**kwargs
):
async with channel.typing():
if user == client.user:
return
await client.request_queued()
if require_request_message and request_message is None:
preprompt = f"🤖\n> _**Prompt:** {prompt}_\n > \n" if show_prompt else '🤖\n'
request_message = await sender.send_message(f'{preprompt}> <@{user.id}>: _{input}_', channel, **kwargs)
if response_type == 'chat':
receive = await chatgpt.get_response_with_system(user, prompt, input, **kwargs)
else:
receive = await chatgpt.get_text_completion(prompt, **kwargs)
if require_request_message:
await sender.reply_message(request_message, response_prefix + receive)
else:
await sender.send_message(response_prefix + receive, channel)
await client.request_done()
# Commands available via "/""
@ client.tree.command(name="chat", description=f'Chat with {bot_name}')
async def chat(interaction: discord.Interaction, *, message: str, temperature: Optional[str] = None):
await interaction.response.defer()
await interaction.delete_original_response()
prompt = chatbot.get_prompt(bot_name)
await use_plugin(interaction.user, interaction.channel, message, prompt, response_type='chat', temperature=temperature)
@ client.tree.command(name="chat_pro", description="Have a custom chat with a system message")
async def chat_pro_cmd(interaction: discord.Interaction, *, system_message: str, message: str, think: Optional[str] = None, temperature: Optional[str] = None):
await interaction.response.defer()
await interaction.delete_original_response()
chatgpt.update_api_key(
'../llama.cpp/models/vicuna/7B/ggml-vicuna-7b-4bit-rev1.bin')
await use_plugin(interaction.user, interaction.channel, message, system_message, response_type='chat', think=think, show_prompt=True, temperature=temperature)
chatgpt.reset_api_key()
@ client.tree.command(name="autocomplete", description="Autocomplete your sentence")
async def autocomplete_cmd(interaction: discord.Interaction, *, prompt: str, stop_on: Optional[str] = None, same_line: bool = False):
await interaction.response.defer()
await interaction.delete_original_response()
await use_plugin(interaction.user, interaction.channel, prompt, prompt, stop=stop_on, same_line=same_line)
@ client.tree.command(name="ask", description=f"Ask {bot_name} about gpt-llama.cpp ")
async def ask_cmd(interaction: discord.Interaction, *, question: str):
await interaction.response.defer()
await interaction.delete_original_response()
prompt = gpt_llama_cpp.get_prompt(question, bot_name)
await use_plugin(interaction.user, interaction.channel, question, prompt, response_type="completion", stop='\n\n', same_line=True)
@ client.tree.command(name="reddit", description=f"talk about top news of a subreddit")
async def reddit_cmd(interaction: discord.Interaction, *, subreddit: str):
await interaction.response.defer()
await interaction.delete_original_response()
prompt = reddit_bot.get_prompt(subreddit, interaction.user.name)
await use_plugin(interaction.user, interaction.channel, subreddit, prompt, response_type="completion", stop='\n\n\n', same_line=True)
@ client.tree.command(name="google", description=f"google something")
async def google_cmd(interaction: discord.Interaction, *, question: str):
await interaction.response.defer()
await interaction.delete_original_response()
prompt = google.get_prompt(question)
await use_plugin(interaction.user, interaction.channel, question, prompt, response_type="completion", stop='\n\n', same_line=True)
@ client.tree.command(name="ask_website", description=f"get answers based on a source website")
async def web_ask_cmd(interaction: discord.Interaction, *, url: str, question: str):
await interaction.response.defer()
await interaction.delete_original_response()
prompt = web.get_prompt(url, question)
await use_plugin(interaction.user, interaction.channel, question, prompt, response_type="completion", stop='\n\n', same_line=True)
@ client.tree.command(name="debug", description="Debug your error log")
async def debug_cmd(interaction: discord.Interaction, *, error_message: str):
await interaction.response.defer()
await interaction.delete_original_response()
prompt = error_debugger.get_prompt(error_message)
await use_plugin(interaction.user, interaction.channel, error_message, prompt, stop='\n\n')
@ client.tree.command(name="roleplay", description="Roleplay conversation")
async def rp_cmd(interaction: discord.Interaction, *, character_name: str, persona: str, scenario: str, message: str):
await interaction.response.defer()
await interaction.delete_original_response()
chatgpt.update_api_key(
'../llama.cpp/models/pygmalion/7B/ggml-model-q5_1.bin')
prompt = pygmalion.get_prompt(character_name, persona, scenario, message)
await use_plugin(interaction.user, interaction.channel, message, prompt, temperature=0.95, stop=['You:'])
chatgpt.reset_api_key()
# CREATE A DISCORD BOT THAT JOINS CONVERSATIONS RANDOMLY
@ client.event
async def on_message(message):
channel = message.channel
blacklist = set(['announcements', 'rules', 'changelog', 'help-forum', 'sentient-bots'])
graylist = set(['bot-spam'])
# 0.1 = 10% chance of responding if not directly mentioning the bot
response_probability = 0.05
# don't react to system message or unreadable messages
if message.content == '':
return
# don't react to your own messages
if message.author == client.user:
return
# don't message in blacklisted channels
if channel.name in blacklist:
return
# respond if addressed
# if not addressed only respond to 10% of the messages
r = random.random()
message.type == "Message"
bot_mentioned = bot_name.lower() in message.content.lower(
) or f'<@{client.user.id}>' in message.content
is_reply_to_bot = message.reference is not None and message.reference.resolved.author == client.user
is_graylist = channel.name in graylist
# if not addressed (always reply when addressed)
if not (bot_mentioned or is_reply_to_bot):
# if channel is in graylist, never respond if not mentioned
if is_graylist:
return
# respond randomly to only 'response_probability' of messages
if r > response_probability:
return
print(f'{bot_name} auto-generating a message...')
# Get last 30 messages in the channel
messages = [message async for message in channel.history(limit=20)]
message_history = []
# Just get the last 20 before the current message
for i, msg in enumerate(messages):
if msg.id == message.id:
message_history = messages[i: i+15]
message_history.reverse() # they come in reversed order
# Get authors
authors_full_list = [message.author for message in message_history]
authors_name_to_id, authors_id_to_name = utils.create_id_name_hashmaps(
authors_full_list)
authors = [
message.author.name for message in message_history if message.author.name != bot_name]
authors = list(set(authors)) # get unique set of authors
author_names = ", ".join(authors)
# Replace author bot_name with 'You' for the prompt
# Replace bot responses (starting with 🤖) with "Bot" author. This is so we don't confuse "you" with being the bot
message_history_str = "\n\n".join(
f"{'DISCORD BOT' if m.content.startswith('🤖') else f'You ({bot_name})' if m.author.name == bot_name else m.author.name} [{m.created_at.strftime('%H:%M:%S %m-%d-%Y')}]:\n{utils.replace_string(m.content, authors_id_to_name)}" for m in message_history)
prompt = f"""You, {author_names} are users on a public #{message.channel.name} channel of a discord server. You are {bot_name}, an active and fun discord member of the server who's good at coding and is very interested in the recent ai text generation improvements. Each message is formatted in a "<NAME> [DATE]: <MESSAGE>" format, and you guys refer to each other with "@NAME". {bot_name} seems to be very capable at coding and answering questions despite being just human. You guys are having a fun, non-repetitive conversation:
{message_history_str}
You ({bot_name}) [{datetime.now().strftime('%H:%M:%S %m-%d-%Y')}]:"""
res_message = None
async with channel.typing():
await client.request_queued()
response = await chatgpt.get_text_completion_stream(prompt, '\n\n')
receive = ''
queued_chunks = 0
max_queue_chunks = 7
async for chunk in response:
if chunk['choices'][0]['finish_reason']:
# response with :no_mouth: if the response failed
receive = ':no_mouth:' if len(receive) == 0 else receive
break
curr_chunk = chunk['choices'][0]['delta']['content']
receive = receive + curr_chunk
queued_chunks += 1
if queued_chunks > max_queue_chunks:
queued_chunks = 0
if len(receive) > 0:
res_message = await sender.send_human_message_stream(receive, res_message, channel)
# send the final message
await client.request_done()
await sender.send_human_message_stream(utils.replace_string(receive, authors_name_to_id).lower(), res_message, channel)
# @ client.tree.command(name="reset", description="Reset ChatGPT conversation history")
# async def reset(interaction: discord.Interaction):
# user_id = interaction.user.id
# logger.info(f"resetting memory from {user_id}")
# try:
# await interaction.response.defer(ephemeral=True)
# chatgpt.clean_history(user_id)
# await interaction.followup.send(f'> Reset ChatGPT conversation history < - <@{user_id}>')
# except Exception as e:
# logger.error(f"Error resetting memory: {e}")
# await interaction.followup.send('> Oops! Something went wrong. <')
# REACTION PLUGINS
@ client.event
async def on_reaction_add(reaction, user):
message = reaction.message
content = message.content # username: user.name
channel = message.channel
# Max only 1 reply per reaction
if reaction.count > 1:
return
if reaction.emoji == '🦙':
prompt = gpt_llama_cpp.get_prompt(content, bot_name)
await use_plugin(message.author, channel, content, prompt, request_message=message, response_type="completion", stop='\n\n', same_line=True)
elif reaction.emoji == '➕':
await use_plugin(message.author, channel, message, content, request_message=message)
elif reaction.emoji == '🐞':
prompt = error_debugger.get_prompt(content)
await use_plugin(message.author, channel, content, prompt, request_message=message)
elif reaction.emoji == '🥜':
prompt = deez_nuts.get_prompt(content)
await use_plugin(message.author, channel, content, prompt, request_message=message, temperature=1)
# TASK LOOPS
@tasks.loop(seconds=60)
# sentient bots channel id 1103394781041786980
async def reddit_updates():
return
response_probability = 1
r = random.random()
if r > response_probability:
print('Skipping sending sentient message')
return
print('Sending sentient message')
chatgpt.update_api_key('../llama.cpp/models/pygmalion/7B/ggml-model-q5_1.bin')
channel = await client.fetch_channel(1103394781041786980)
user = await client.fetch_user(184534707000573952)
messages = [message async for message in channel.history(limit=2)]
response_author = messages[1].content.split(': ')[0]
prompt = await social_agi.get_prompt('ELIZA', 'ALICE', response_author, channel)
await use_plugin(user, channel, None, prompt, require_request_message=False, temperature=0.9, top_p=1.3, same_line=True, stop='\n', response_prefix=f"{response_author}: ")
chatgpt.reset_api_key()
return
# print('## SEARCHING AND POSTING REDDIT UPDATES')
# subreddit_list = ['localllama', 'machinelearning', 'chatgpt', 'pygmalionai', 'openai']
# subreddit = random.choice(subreddit_list)
# prompt = reddit_bot.get_prompt(subreddit, bot_name)
# channel = await client.fetch_channel(1099452096383832155)
# user = await client.fetch_user(184534707000573952)
# request_message = await sender.send_message(f'**Time to check out `r/{subreddit}` for news!**', channel)
# await use_plugin(user, channel, subreddit, prompt, response_type="completion", request_message=request_message, stop='\n\n\n', same_line=True)
# On bot ready
@client.event
async def on_ready():
reddit_updates.start()
await client.sync()
print("Bot ready.")
client.run(os.getenv('DISCORD_TOKEN'))
if __name__ == '__main__':
keep_alive()
run()