diff --git a/fjerkroa_bot/ai_responder.py b/fjerkroa_bot/ai_responder.py index 3683496..3fde220 100644 --- a/fjerkroa_bot/ai_responder.py +++ b/fjerkroa_bot/ai_responder.py @@ -150,13 +150,19 @@ class AIResponder(AIResponderBase): def __init__(self, config: Dict[str, Any], channel: Optional[str] = None) -> None: super().__init__(config, channel) self.history: List[Dict[str, Any]] = [] + self.memory: str = 'I am an assistant.' self.rate_limit_backoff = exponential_backoff() self.history_file: Optional[Path] = None + self.memory_file: Optional[Path] = None if 'history-directory' in self.config: self.history_file = Path(self.config['history-directory']).expanduser() / f'{self.channel}.dat' if self.history_file.exists(): with open(self.history_file, 'rb') as fd: self.history = pickle.load(fd) + self.memory_file = Path(self.config['history-directory']).expanduser() / f'{self.channel}.memory' + if self.memory_file.exists(): + with open(self.memory_file, 'rb') as fd: + self.memory = pickle.load(fd) def message(self, message: AIMessage, limit: Optional[int] = None) -> List[Dict[str, Any]]: messages = [] @@ -168,6 +174,7 @@ class AIResponder(AIResponderBase): with open(news_feed) as fd: news_feed = fd.read().strip() system = system.replace('{news}', news_feed) + system = system.replace('{memory}', self.memory) messages.append({"role": "system", "content": system}) if limit is not None: while len(self.history) > limit: @@ -240,6 +247,9 @@ class AIResponder(AIResponderBase): async def fix(self, answer: str) -> str: raise NotImplementedError() + async def memory_rewrite(self, memory: str, user: str, question: str, answer: str) -> str: + raise NotImplementedError() + async def translate(self, text: str, language: str = "english") -> str: raise NotImplementedError() @@ -268,6 +278,19 @@ class AIResponder(AIResponderBase): with open(self.history_file, 'wb') as fd: pickle.dump(self.history, fd) + def update_memory(self, memory) -> None: + if self.memory_file is not None: + with open(self.memory_file, 'wb') as fd: + pickle.dump(self.memory, fd) + + async def handle_picture(self, response: Dict) -> bool: + if not isinstance(response.get("picture"), (type(None), str)): + logging.warning(f"picture key is wrong in response: {pp(response)}") + return False + if response.get("picture") is not None: + response["picture"] = await self.translate(response["picture"]) + return True + async def send(self, message: AIMessage) -> AIResponse: # Get the history limit from the configuration limit = self.config["history-limit"] @@ -305,15 +328,10 @@ class AIResponder(AIResponderBase): retries -= 1 continue - # Check if the response has the correct picture format - if not isinstance(response.get("picture"), (type(None), str)): - logging.warning(f"picture key is wrong in response: {pp(response)}") + if not await self.handle_picture(response): retries -= 1 continue - if response.get("picture") is not None: - response["picture"] = await self.translate(response["picture"]) - # Post-process the message and update the answer's content answer_message = await self.post_process(message, response) answer['content'] = str(answer_message) @@ -322,6 +340,11 @@ class AIResponder(AIResponderBase): self.update_history(messages[-1], answer, limit, message.historise_question) logging.info(f"got this answer:\n{str(answer_message)}") + # Update memory + if answer_message.answer is not None: + self.memory = await self.memory_rewrite(self.memory, message.user, message.message, answer_message.answer) + self.update_memory(self.memory) + # Return the updated answer message return answer_message diff --git a/fjerkroa_bot/discord_bot.py b/fjerkroa_bot/discord_bot.py index 94d5f7f..eeace01 100644 --- a/fjerkroa_bot/discord_bot.py +++ b/fjerkroa_bot/discord_bot.py @@ -73,7 +73,9 @@ class FjerkroaBot(commands.Bot): boreness_interval = float(self.config.get('boreness-interval', 12.0)) elapsed_time = (time.monotonic() - self.last_activity_time) / 3600.0 probability = 1 / (1 + math.exp(-1 * (elapsed_time - (boreness_interval / 2.0)) + math.log(1 / 0.2 - 1))) - if random.random() < probability: + prev_messages = await self.chat_channel.history(limit=2).flatten() + last_author = prev_messages[1].author.id if len(prev_messages) > 1 else None + if random.random() < probability and last_author and last_author != self.user.id: logging.info(f'Borred with {probability} probability after {elapsed_time}') boreness_prompt = self.config.get('boreness-prompt', 'Pretend that you just now thought of something, be creative.') message = AIMessage('system', boreness_prompt, self.config.get('chat-channel', 'chat'), True, False) @@ -155,6 +157,9 @@ class FjerkroaBot(commands.Bot): async def handle_message_through_responder(self, message): """Handle a message through the AI responder""" message_content = str(message.content).strip() + if message.reference and message.reference.resolved and type(message.reference.resolved.content) == str: + reference_content = str(message.reference.resolved.content).replace("\n", "> \n") + message_content = f'> {reference_content}\n\n{message_content}' if len(message_content) < 1: return for ma_user in self._re_user.finditer(message_content): diff --git a/fjerkroa_bot/openai_responder.py b/fjerkroa_bot/openai_responder.py index ab167e7..23c1fd3 100644 --- a/fjerkroa_bot/openai_responder.py +++ b/fjerkroa_bot/openai_responder.py @@ -110,3 +110,26 @@ class OpenAIResponder(AIResponder, LeonardoAIDrawMixIn): except Exception as err: logging.warning(f"failed to translate the text: {repr(err)}") return text + + async def memory_rewrite(self, memory: str, user: str, question: str, answer: str) -> str: + if 'memory-model' not in self.config: + return memory + 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' + 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'The whole memory should not be too long, summarize if required. ' + f'Write just new memory data without any comments.'}] + try: + logging.info(f'send this memory request:\n{pp(messages)}') + result = await openai_chat(self.client, + model=self.config['memory-model'], + messages=messages, + temperature=0.6, + max_tokens=4096) + new_memory = result.choices[0].message.content + logging.info(f'new memory:\n{pp(new_memory)}') + return new_memory + except Exception as err: + logging.warning(f"failed to create new memory: {repr(err)}") + return memory diff --git a/openai_chat.dat b/openai_chat.dat index 42e7624..2eaab9c 100644 Binary files a/openai_chat.dat and b/openai_chat.dat differ