discollama/discollama.py

128 lines
3.7 KiB
Python
Raw Normal View History

2023-07-28 20:00:40 -07:00
import os
import json
import aiohttp
import msgpack
import discord
import argparse
from redislite import Redis
from pathlib import Path
import logging
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
@client.event
async def on_ready():
2023-08-03 23:49:00 -07:00
logging.info(
'Ready! Invite URL: %s',
discord.utils.oauth_url(
client.application_id,
permissions=discord.Permissions(read_messages=True, send_messages=True),
scopes=['bot']))
2023-07-28 20:00:40 -07:00
2023-08-01 19:58:36 -07:00
async def generate_response(prompt, context=[]):
2023-07-28 20:00:40 -07:00
body = {
key: value
for key, value in {
2023-07-31 10:25:09 -07:00
'model': args.ollama_model,
2023-07-28 20:00:40 -07:00
'prompt': prompt,
'context': context,
}.items() if value
}
async with aiohttp.ClientSession() as session:
async with session.post(
2023-07-31 10:25:09 -07:00
f'http://{args.ollama_host}:{args.ollama_port}/api/generate',
2023-07-28 20:00:40 -07:00
json=body) as r:
async for line in r.content:
yield json.loads(line)
def save_session(response, chunk):
context = msgpack.packb(chunk['context'])
redis.hset(f'ollama:{response.id}', 'context', context)
redis.expire(f'ollama:{response.id}', 60 * 60 * 24 * 7)
2023-08-01 19:58:36 -07:00
logging.info('saving message=%s: len(context)=%d', response.id, len(chunk['context']))
2023-07-28 20:00:40 -07:00
def load_session(reference):
kwargs = {}
if reference:
context = redis.hget(f'ollama:{reference.message_id}', 'context')
kwargs['context'] = msgpack.unpackb(context) if context else []
2023-08-01 19:58:36 -07:00
if kwargs.get('context'):
2023-07-28 20:00:40 -07:00
logging.info(
2023-08-01 19:58:36 -07:00
'loading message=%s: len(context)=%d',
2023-07-28 20:00:40 -07:00
reference.message_id,
len(kwargs['context']))
return kwargs
@client.event
async def on_message(message):
if message.author == client.user:
return
if client.user.id in message.raw_mentions:
raw_content = message.content.replace(f'<@{client.user.id}>', '').strip()
if raw_content.strip() == '':
raw_content = 'Tell me about yourself.'
2023-07-28 20:00:40 -07:00
# TODO: discord has a 2000 character limit, so we need to split the response
2023-08-04 00:42:08 -07:00
response = None
2023-07-31 10:25:09 -07:00
buffer = ''
2023-07-28 20:00:40 -07:00
response_content = ''
2023-08-04 00:42:08 -07:00
async with message.channel.typing():
await message.add_reaction('🤔')
async for chunk in generate_response(raw_content, **load_session(message.reference)):
if chunk['done']:
response_content += buffer
save_session(response, chunk)
break
buffer += chunk['response']
if len(buffer) >= args.buffer_size:
# buffer the edit so as to not call Discord API too often
response_content += buffer
if response:
await response.edit(content=response_content + '...')
else:
response = await message.reply(response_content)
await message.remove_reaction('🤔', client.user)
buffer = ''
2023-07-28 20:00:40 -07:00
await response.edit(content=response_content)
parser = argparse.ArgumentParser()
parser.add_argument('--ollama-host', default='127.0.0.1')
parser.add_argument('--ollama-port', default=11434, type=int)
parser.add_argument('--ollama-model', default='llama2', type=str)
default_redis = Path.home() / '.cache' / 'discollama' / 'brain.db'
parser.add_argument('--redis', default=default_redis, type=Path)
2023-08-01 19:58:36 -07:00
parser.add_argument('--buffer-size', default=32, type=int)
2023-07-28 20:00:40 -07:00
2023-07-31 10:25:09 -07:00
args = parser.parse_args()
2023-07-28 20:00:40 -07:00
args.redis.parent.mkdir(parents=True, exist_ok=True)
2023-08-03 09:35:28 -07:00
try:
redis = Redis(args.redis)
2023-08-03 23:49:00 -07:00
client.run(os.getenv('DISCORD_TOKEN'), root_logger=True)
2023-08-03 09:35:28 -07:00
except KeyboardInterrupt:
pass
redis.close()