Compare commits
No commits in common. "fb39aef577d5b4e1b9b320fdcf58ca5897bb66cf" and "73d9b9184d0974160c2ea19d169a8c68fd358fc1" have entirely different histories.
fb39aef577
...
73d9b9184d
@ -10,7 +10,7 @@ from pathlib import Path
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from functools import lru_cache, wraps
|
from functools import lru_cache, wraps
|
||||||
from typing import Optional, List, Dict, Any, Tuple, Union
|
from typing import Optional, List, Dict, Any, Tuple
|
||||||
|
|
||||||
|
|
||||||
def pp(*args, **kw):
|
def pp(*args, **kw):
|
||||||
@ -107,21 +107,19 @@ def same_channel(item1: Dict[str, Any], item2: Dict[str, Any]) -> bool:
|
|||||||
|
|
||||||
class AIMessageBase(object):
|
class AIMessageBase(object):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.vars: List[str] = []
|
pass
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return json.dumps({k: v for k, v in vars(self).items() if k in self.vars})
|
return json.dumps(vars(self))
|
||||||
|
|
||||||
|
|
||||||
class AIMessage(AIMessageBase):
|
class AIMessage(AIMessageBase):
|
||||||
def __init__(self, user: str, message: str, channel: str = "chat", direct: bool = False, historise_question: bool = True) -> None:
|
def __init__(self, user: str, message: str, channel: str = "chat", direct: bool = False, historise_question: bool = True) -> None:
|
||||||
self.user = user
|
self.user = user
|
||||||
self.message = message
|
self.message = message
|
||||||
self.urls: Optional[List[str]] = None
|
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.direct = direct
|
self.direct = direct
|
||||||
self.historise_question = historise_question
|
self.historise_question = historise_question
|
||||||
self.vars = ['user', 'message', 'channel', 'direct']
|
|
||||||
|
|
||||||
|
|
||||||
class AIResponse(AIMessageBase):
|
class AIResponse(AIMessageBase):
|
||||||
@ -139,7 +137,6 @@ class AIResponse(AIMessageBase):
|
|||||||
self.staff = staff
|
self.staff = staff
|
||||||
self.picture = picture
|
self.picture = picture
|
||||||
self.hack = hack
|
self.hack = hack
|
||||||
self.vars = ['answer', 'answer_needed', 'channel', 'staff', 'picture', 'hack']
|
|
||||||
|
|
||||||
|
|
||||||
class AIResponderBase(object):
|
class AIResponderBase(object):
|
||||||
@ -185,13 +182,7 @@ class AIResponder(AIResponderBase):
|
|||||||
self.shrink_history_by_one()
|
self.shrink_history_by_one()
|
||||||
for msg in self.history:
|
for msg in self.history:
|
||||||
messages.append(msg)
|
messages.append(msg)
|
||||||
if not message.urls:
|
|
||||||
messages.append({"role": "user", "content": str(message)})
|
messages.append({"role": "user", "content": str(message)})
|
||||||
else:
|
|
||||||
content: List[Dict[str, Union[str, Dict[str, str]]]] = [{"type": "text", "text": str(message)}]
|
|
||||||
for url in message.urls:
|
|
||||||
content.append({"type": "image_url", "image_url": {"url": url}})
|
|
||||||
messages.append({"role": "user", "content": content})
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
async def draw(self, description: str) -> BytesIO:
|
async def draw(self, description: str) -> BytesIO:
|
||||||
@ -257,7 +248,7 @@ class AIResponder(AIResponderBase):
|
|||||||
async def fix(self, answer: str) -> str:
|
async def fix(self, answer: str) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def memory_rewrite(self, memory: str, message_user: str, answer_user: str, question: str, answer: str) -> str:
|
async def memory_rewrite(self, memory: str, user: str, question: str, answer: str) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
async def translate(self, text: str, language: str = "english") -> str:
|
async def translate(self, text: str, language: str = "english") -> str:
|
||||||
@ -279,8 +270,6 @@ class AIResponder(AIResponderBase):
|
|||||||
answer: Dict[str, Any],
|
answer: Dict[str, Any],
|
||||||
limit: int,
|
limit: int,
|
||||||
historise_question: bool = True) -> None:
|
historise_question: bool = True) -> None:
|
||||||
if type(question['content']) != str:
|
|
||||||
question['content'] = question['content'][0]['text']
|
|
||||||
if historise_question:
|
if historise_question:
|
||||||
self.history.append(question)
|
self.history.append(question)
|
||||||
self.history.append(answer)
|
self.history.append(answer)
|
||||||
@ -303,14 +292,13 @@ class AIResponder(AIResponderBase):
|
|||||||
response["picture"] = await self.translate(response["picture"])
|
response["picture"] = await self.translate(response["picture"])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def memoize(self, message_user: str, answer_user: str, message: str, answer: str) -> None:
|
async def memoize(self, user: str, message: str, answer: str) -> None:
|
||||||
self.memory = await self.memory_rewrite(self.memory, message_user, answer_user, message, answer)
|
self.memory = await self.memory_rewrite(self.memory, user, message, answer)
|
||||||
self.update_memory(self.memory)
|
self.update_memory(self.memory)
|
||||||
|
|
||||||
async def memoize_reaction(self, message_user: str, reaction_user: str, operation: str, reaction: str, message: str) -> None:
|
async def memoize_reaction(self, message_user: str, reaction_user: str, operation: str, reaction: str, message: str) -> None:
|
||||||
quoted_message = message.replace('\n', '\n> ')
|
quoted_message = message.replace('\n', '\n> ')
|
||||||
await self.memoize(message_user, 'assistant',
|
await self.memoize(reaction_user, f'{message_user}:\n> {quoted_message}',
|
||||||
f'\n> {quoted_message}',
|
|
||||||
f'User {reaction_user} has {operation} this raction: {reaction}')
|
f'User {reaction_user} has {operation} this raction: {reaction}')
|
||||||
|
|
||||||
async def send(self, message: AIMessage) -> AIResponse:
|
async def send(self, message: AIMessage) -> AIResponse:
|
||||||
@ -364,7 +352,7 @@ class AIResponder(AIResponderBase):
|
|||||||
|
|
||||||
# Update memory
|
# Update memory
|
||||||
if answer_message.answer is not None:
|
if answer_message.answer is not None:
|
||||||
await self.memoize(message.user, 'assistant', message.message, answer_message.answer)
|
await self.memoize(message.user, message.message, answer_message.answer)
|
||||||
|
|
||||||
# Return the updated answer message
|
# Return the updated answer message
|
||||||
return answer_message
|
return answer_message
|
||||||
|
|||||||
@ -32,7 +32,6 @@ class FjerkroaBot(commands.Bot):
|
|||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
intents.members = True
|
intents.members = True
|
||||||
intents.reactions = True
|
|
||||||
self._re_user = re.compile(r"[<][@][!]?\s*([0-9]+)[>]")
|
self._re_user = re.compile(r"[<][@][!]?\s*([0-9]+)[>]")
|
||||||
|
|
||||||
self.init_observer()
|
self.init_observer()
|
||||||
@ -109,37 +108,21 @@ class FjerkroaBot(commands.Bot):
|
|||||||
return
|
return
|
||||||
await self.handle_message_through_responder(message)
|
await self.handle_message_through_responder(message)
|
||||||
|
|
||||||
async def on_reaction_operation(self, reaction, user, operation):
|
async def on_reaction_add(self, reaction, user):
|
||||||
if user.bot:
|
if user.bot:
|
||||||
return
|
return
|
||||||
logging.info(f'{operation} reaction {reaction} by {user}.')
|
|
||||||
airesponder = self.get_ai_responder(self.get_channel_name(reaction.message.channel))
|
airesponder = self.get_ai_responder(self.get_channel_name(reaction.message.channel))
|
||||||
message = str(reaction.message.content) if reaction.message.content else ''
|
message = str(reaction.message.content) if reaction.message.content else ''
|
||||||
if len(message) > 1:
|
if len(message) > 1:
|
||||||
await airesponder.memoize_reaction(reaction.message.author.name, user.name, operation, str(reaction.emoji), message)
|
await airesponder.memoize_reaction(reaction.message.user.name, user.name, 'adding', str(reaction.emoji), message)
|
||||||
|
|
||||||
async def on_reaction_add(self, reaction, user):
|
|
||||||
await self.on_reaction_operation(reaction, user, 'adding')
|
|
||||||
|
|
||||||
async def on_reaction_remove(self, reaction, user):
|
async def on_reaction_remove(self, reaction, user):
|
||||||
await self.on_reaction_operation(reaction, user, 'removing')
|
if user.bot:
|
||||||
|
|
||||||
async def on_reaction_clear(self, reaction, user):
|
|
||||||
await self.on_reaction_operation(reaction, user, 'clearing')
|
|
||||||
|
|
||||||
async def on_message_edit(self, before, after):
|
|
||||||
if before.author.bot or before.content == after.content:
|
|
||||||
return
|
return
|
||||||
airesponder = self.get_ai_responder(self.get_channel_name(before.channel))
|
airesponder = self.get_ai_responder(self.get_channel_name(reaction.message.channel))
|
||||||
await airesponder.memoize(before.author.name, 'assistant',
|
message = str(reaction.message.content) if reaction.message.content else ''
|
||||||
'\n> ' + before.content.replace('\n', '\n> '),
|
if len(message) > 1:
|
||||||
'User changed this message to:\n> ' + after.content.replace('\n', '\n> '))
|
await airesponder.memoize_reaction(reaction.message.user.name, user.name, 'removing', str(reaction.emoji), message)
|
||||||
|
|
||||||
async def on_message_delete(self, message):
|
|
||||||
airesponder = self.get_ai_responder(self.get_channel_name(message.channel))
|
|
||||||
await airesponder.memoize(message.author.name, 'assistant',
|
|
||||||
'\n> ' + message.content.replace('\n', '\n> '),
|
|
||||||
'User deleted this message.')
|
|
||||||
|
|
||||||
def on_config_file_modified(self, event):
|
def on_config_file_modified(self, event):
|
||||||
if event.src_path == self.config_file:
|
if event.src_path == self.config_file:
|
||||||
@ -205,13 +188,7 @@ class FjerkroaBot(commands.Bot):
|
|||||||
if user is not None:
|
if user is not None:
|
||||||
message_content = re.sub(f'[<][@][!]? *{uid} *[>]', f'@{user.name}', message_content)
|
message_content = re.sub(f'[<][@][!]? *{uid} *[>]', f'@{user.name}', message_content)
|
||||||
channel_name = self.get_channel_name(message.channel)
|
channel_name = self.get_channel_name(message.channel)
|
||||||
msg = AIMessage(message.author.name, message_content, channel_name,
|
msg = AIMessage(message.author.name, message_content, channel_name, self.user in message.mentions)
|
||||||
self.user in message.mentions or isinstance(message.channel, DMChannel))
|
|
||||||
if message.attachments:
|
|
||||||
for attachment in message.attachments:
|
|
||||||
if not msg.urls:
|
|
||||||
msg.urls = []
|
|
||||||
msg.urls.append(attachment.url)
|
|
||||||
await self.respond(msg, message.channel)
|
await self.respond(msg, message.channel)
|
||||||
|
|
||||||
async def send_message_with_typing(self, airesponder, channel, message):
|
async def send_message_with_typing(self, airesponder, channel, message):
|
||||||
|
|||||||
@ -37,12 +37,7 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
|||||||
raise RuntimeError(f"Failed to generate image {repr(description)} after multiple retries")
|
raise RuntimeError(f"Failed to generate image {repr(description)} after multiple retries")
|
||||||
|
|
||||||
async def chat(self, messages: List[Dict[str, Any]], limit: int) -> Tuple[Optional[Dict[str, Any]], int]:
|
async def chat(self, messages: List[Dict[str, Any]], limit: int) -> Tuple[Optional[Dict[str, Any]], int]:
|
||||||
if type(messages[-1]['content']) == str:
|
|
||||||
model = self.config["model"]
|
model = self.config["model"]
|
||||||
elif 'model-vision' in self.config:
|
|
||||||
model = self.config["model-vision"]
|
|
||||||
else:
|
|
||||||
messages[-1]['content'] = messages[-1]['content'][0]['text']
|
|
||||||
try:
|
try:
|
||||||
result = await openai_chat(self.client,
|
result = await openai_chat(self.client,
|
||||||
model=model,
|
model=model,
|
||||||
@ -116,16 +111,15 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn):
|
|||||||
logging.warning(f"failed to translate the text: {repr(err)}")
|
logging.warning(f"failed to translate the text: {repr(err)}")
|
||||||
return text
|
return text
|
||||||
|
|
||||||
async def memory_rewrite(self, memory: str, message_user: str, answer_user: str, question: str, answer: str) -> str:
|
async def memory_rewrite(self, memory: str, user: str, question: str, answer: str) -> str:
|
||||||
if 'memory-model' not in self.config:
|
if 'memory-model' not in self.config:
|
||||||
return memory
|
return memory
|
||||||
messages = [{'role': 'system', 'content': self.config.get('memory-system', 'You are an memory assistant.')},
|
messages = [{'role': 'system', 'content': self.config.get('memory-system', 'You are an memory assistant.')},
|
||||||
{'role': 'user', 'content': f'Here is my previous memory:\n```\n{memory}\n```\n\n'
|
{'role': 'user', 'content': f'Here is my previous memory:\n```\n{memory}\n```\n\n'
|
||||||
f'Here is my conversanion:\n```\n{message_user}: {question}\n\n{answer_user}: {answer}\n```\n\n'
|
f'Here is my conversanion:\n```\n{user}: {question}\n\nassistant: {answer}\n```\n\n'
|
||||||
f'Please rewrite the memory in a way, that it contain the content mentioned in conversation. '
|
f'Please rewrite the memory in a way, that it contain the content mentioned in conversation. '
|
||||||
f'Summarize the memory if required, try to keep important information. '
|
f'The whole memory should not be too long, summarize if required. '
|
||||||
f'Write just new memory data without any comments.'}]
|
f'Write just new memory data without any comments.'}]
|
||||||
logging.info(f'Rewrite memory:\n{pp(messages)}')
|
|
||||||
try:
|
try:
|
||||||
# logging.info(f'send this memory request:\n{pp(messages)}')
|
# logging.info(f'send this memory request:\n{pp(messages)}')
|
||||||
result = await openai_chat(self.client,
|
result = await openai_chat(self.client,
|
||||||
|
|||||||
BIN
openai_chat.dat
BIN
openai_chat.dat
Binary file not shown.
Loading…
Reference in New Issue
Block a user