From 0bf1baf3416b847cf0a1cbff533dc07222075798 Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Sun, 17 Mar 2024 15:10:35 +0100 Subject: [PATCH] Fix image handling and do not react to bots or unchanged messages. --- fjerkroa_bot/ai_responder.py | 19 +++++++++++++++---- fjerkroa_bot/discord_bot.py | 9 +++++++++ fjerkroa_bot/openai_responder.py | 9 +++++++-- openai_chat.dat | Bin 30869719 -> 31233268 bytes 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/fjerkroa_bot/ai_responder.py b/fjerkroa_bot/ai_responder.py index abf321b..ffbfcc3 100644 --- a/fjerkroa_bot/ai_responder.py +++ b/fjerkroa_bot/ai_responder.py @@ -10,7 +10,7 @@ from pathlib import Path from io import BytesIO from pprint import pformat from functools import lru_cache, wraps -from typing import Optional, List, Dict, Any, Tuple +from typing import Optional, List, Dict, Any, Tuple, Union def pp(*args, **kw): @@ -107,19 +107,21 @@ def same_channel(item1: Dict[str, Any], item2: Dict[str, Any]) -> bool: class AIMessageBase(object): def __init__(self) -> None: - pass + self.vars: List[str] = [] def __str__(self) -> str: - return json.dumps(vars(self)) + return json.dumps({k: v for k, v in vars(self).items() if k in self.vars}) class AIMessage(AIMessageBase): def __init__(self, user: str, message: str, channel: str = "chat", direct: bool = False, historise_question: bool = True) -> None: self.user = user self.message = message + self.urls: Optional[List[str]] = None self.channel = channel self.direct = direct self.historise_question = historise_question + self.vars = ['user', 'message', 'channel', 'direct'] class AIResponse(AIMessageBase): @@ -137,6 +139,7 @@ class AIResponse(AIMessageBase): self.staff = staff self.picture = picture self.hack = hack + self.vars = ['answer', 'answer_needed', 'channel', 'staff', 'picture', 'hack'] class AIResponderBase(object): @@ -182,7 +185,13 @@ class AIResponder(AIResponderBase): self.shrink_history_by_one() for msg in self.history: messages.append(msg) - messages.append({"role": "user", "content": str(message)}) + if not message.urls: + 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 async def draw(self, description: str) -> BytesIO: @@ -270,6 +279,8 @@ class AIResponder(AIResponderBase): answer: Dict[str, Any], limit: int, historise_question: bool = True) -> None: + if type(question['content']) != str: + question['content'] = question['content'][0]['text'] if historise_question: self.history.append(question) self.history.append(answer) diff --git a/fjerkroa_bot/discord_bot.py b/fjerkroa_bot/discord_bot.py index 8476987..b7cd4e0 100644 --- a/fjerkroa_bot/discord_bot.py +++ b/fjerkroa_bot/discord_bot.py @@ -110,6 +110,8 @@ class FjerkroaBot(commands.Bot): await self.handle_message_through_responder(message) async def on_reaction_operation(self, reaction, user, operation): + if user.bot: + return logging.info(f'{operation} reaction {reaction} by {user}.') airesponder = self.get_ai_responder(self.get_channel_name(reaction.message.channel)) message = str(reaction.message.content) if reaction.message.content else '' @@ -126,6 +128,8 @@ class FjerkroaBot(commands.Bot): 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 airesponder = self.get_ai_responder(self.get_channel_name(before.channel)) await airesponder.memoize(before.author.name, 'assistant', '\n> ' + before.content.replace('\n', '\n> '), @@ -202,6 +206,11 @@ class FjerkroaBot(commands.Bot): message_content = re.sub(f'[<][@][!]? *{uid} *[>]', f'@{user.name}', message_content) channel_name = self.get_channel_name(message.channel) msg = AIMessage(message.author.name, message_content, channel_name, self.user in message.mentions) + 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) async def send_message_with_typing(self, airesponder, channel, message): diff --git a/fjerkroa_bot/openai_responder.py b/fjerkroa_bot/openai_responder.py index 4496ad9..6a1287f 100644 --- a/fjerkroa_bot/openai_responder.py +++ b/fjerkroa_bot/openai_responder.py @@ -37,7 +37,12 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn): 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]: - model = self.config["model"] + if type(messages[-1]['content']) == str: + 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: result = await openai_chat(self.client, model=model, @@ -118,7 +123,7 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn): {'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'Please rewrite the memory in a way, that it contain the content mentioned in conversation. ' - f'The whole memory should not be too long, summarize if required. ' + f'Summarize the memory if required, try to keep important information. ' f'Write just new memory data without any comments.'}] logging.info(f'Rewrite memory:\n{pp(messages)}') try: diff --git a/openai_chat.dat b/openai_chat.dat index 53f5a1611b1f44f223fc5651f127f3cf94850786..586ed7106d65cec7f0aa84be14dbd2b3d5572ad8 100644 GIT binary patch delta 62346 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