Initial commit, basic functionality

This commit is contained in:
Conner Harkness 2025-05-08 16:15:02 -06:00
parent 8524125255
commit 6b5c531475
3 changed files with 299 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/cuda*/
/python/
/venv/
token.txt
*.gguf

235
app.py Normal file
View File

@ -0,0 +1,235 @@
import discord
import re
import requests
import datetime
import time
import re
import asyncio
import functools
from llama_cpp import Llama
intents = discord.Intents(messages=True, guilds=True, message_content=True, reactions=True)
client = discord.Client(intents=intents)
session_times = {}
attention = {}
message_cache = {}
lock = False
LLM = Llama(
model_path = "capybarahermes-2.5-mistral-7b.Q4_K_S.gguf",
n_gpu_layers = -1,
n_ctx = 32768,
verbose = False,
n_threads = 8)
def get_messages_as_text(context, query, for_completion=False):
# ChatML format:
user_id = "user"
assistant_id = "assistant"
context_declaration = "<|im_start|>system\n"
message_declaration = "<|im_start|>{author}\n"
end_of_message = "<|im_end|>\n"
stop_tokens = ["<|im_end|>", "</s>", "<|im_start|>"]
output = ""
if isinstance(query, str):
query = [{"author": "user", "body": query}]
if isinstance(query, list):
for message in query:
author = message["author"]
body = message["body"]
if "nickname" in message.keys():
nickname = message["nickname"]
author = nickname
output = f"{output}{message_declaration.format(author=author)}{body}{end_of_message}"
append = ""
if for_completion:
append = message_declaration.format(author=assistant_id)
output = f"""{context_declaration}{context}{end_of_message}{output}{append}"""
return output
def get_response(text):
global lock
while lock == True:
time.sleep(1)
try:
lock = True
output = ""
response = LLM(
text,
max_tokens = 16384,
stop = ["<|im_end|>", "</s>", "<|im_start|>"],
echo = False,
repeat_penalty = 1.1,
temperature = 0.75,
stream = True)
# Stream a buffered response
for token in response:
token_text = token["choices"][0]["text"]
print(token_text, end="")
output = output + token_text
except:
pass
lock = False
return output
async def get_response_wrapper(text_in):
loop = asyncio.get_event_loop()
text_out = await loop.run_in_executor(None, functools.partial(get_response, text=text_in))
return text_out
async def get_message(channel, message_id):
if message_id in message_cache.keys():
return message_cache[message_id]
reference = await channel.fetch_message(message_id)
message_cache[message_id] = reference
return reference
# When the Discord bot starts up successfully:
@client.event
async def on_ready():
print("READY")
# When the Discord bot sees a new message anywhere:
@client.event
async def on_message(msg):
if msg.author.id == client.user.id:
return
messages = []
msg_history = [message async for message in msg.channel.history(limit=50)]
msg_history.reverse()
for m in msg_history:
reference = None
if m.reference is not None:
reference = await get_message(msg.channel, m.reference.message_id)
# Ignore messages from other users:
if m.author.id not in [msg.author.id, client.user.id]:
continue
# Ignore bot's replies to other users:
if m.author.id == client.user.id:
if reference is None or reference.author.id != msg.author.id:
continue
now = datetime.datetime.now(datetime.timezone.utc)
then = m.created_at
age = now - then
age = age.total_seconds()
# Ignore messages older than 10 minutes:
if age > 10 * 60:
continue
if m.author.id == client.user.id:
messages.append({
"author": "assistant",
"body": m.content
})
continue
messages.append({
"author": "user",
"body": m.content
})
# Keep the message script short:
while len(messages) > 25:
del messages[0]
# Ensure the first message is always from the user:
while True:
if len(messages) > 0:
if messages[0]["author"] == "assistant":
del messages[0]
continue
break
# Begin processing the message:
scrubbed_message = msg.content
chl = msg.channel
user_name = msg.author.name
user_nickname = msg.author.display_name
user_discriminator = msg.author.discriminator
user_id = msg.author.id
paying_attention = False
bot_mentioned = False
guild_id = chl.guild.id
guild = client.get_guild(guild_id)
guild_name = guild.name
bot_member = guild.get_member(client.user.id)
bot_name = bot_member.display_name
session_name = f"{user_name}_{user_discriminator}"
if client.user.id in msg.raw_mentions:
paying_attention = True
bot_mentioned = True
exclusion = f"<@{client.user.id}>"
scrubbed_message = re.sub(exclusion, "", scrubbed_message)
scrubbed_message = scrubbed_message.strip()
bot_name_lower = bot_name.lower()
message_lower = scrubbed_message.lower()
if bot_name_lower in message_lower:
paying_attention = True
bot_mentioned = True
forget = False
if session_name in attention.keys():
time_last = attention[session_name]
time_diff = time.perf_counter() - time_last
if time_diff < 60 * 5:
paying_attention = True
else: forget = True
else: forget = True
if bot_mentioned:
attention[session_name] = time.perf_counter()
if paying_attention:
attention[session_name] = time.perf_counter()
context = f"You are {bot_name}, an AI assistant with the programmed personality of Lain from the anime \"Serial Experiments Lain.\" You are to answer all of the user's questions as quickly and briefly as possible using advanced English and cryptic messaging. You are not to go into full length detail unless asked."
if chl.topic is not None:
context = chl.topic
print(f"{user_name}: {msg.content}")
print(f"{bot_name}: ", end="")
f_body = get_messages_as_text(context, messages, for_completion=True)
f_resp = await get_response_wrapper(f_body)
print("")
await chl.send(f_resp, reference=msg)
if __name__ == "__main__":
# Read the token:
with open("token.txt") as f:
token = f.read()
# Start the Discord bot using the token:
client.run(token)

59
app.sh Normal file
View File

@ -0,0 +1,59 @@
#!/bin/bash
INSTALLATION=0
IS_LINUX=0
IS_WINDOWS=0
if [[ -d ./venv/bin ]]; then IS_LINUX=1; fi
if [[ -d ./python/Scripts ]]; then IS_WINDOWS=1; fi
if [[ $IS_LINUX -eq 0 ]] && [[ $IS_WINDOWS -eq 0 ]]
then
echo "No Python virtual environment found..."
exit 1
fi
if [[ $IS_LINUX -eq 1 ]]
then
source ./venv/bin/activate
fi
if [[ $IS_WINDOWS -eq 1 ]]
then
function pip()
{
./python/python.exe -m pip "$@"
}
function apt()
{
echo "Skipping call to apt $@ ..."
}
function python()
{
./python/python.exe "$@"
}
export CUDA_PATH="$PWD/cuda126"
export PATH="$PATH:$CUDA_PATH/bin"
export -f pip
export -f apt
export -f python
fi
if [[ "$@" =~ --setup ]]
then
pip install discord.py
fi
if [[ "$@" =~ --bash ]]
then
bash
exit 0
fi
python -u app.py