From 0d52be41db40a83ee8bf7ce9d0fd5b557219579c Mon Sep 17 00:00:00 2001 From: Cyberes Date: Thu, 11 Apr 2024 14:32:38 -0600 Subject: [PATCH] simplify copilot response, adjust error responses, other minor changes --- README.md | 2 +- main.py | 0 matrix_gpt/generate.py | 12 +++---- matrix_gpt/generate_clients/anthropic.py | 8 ++--- matrix_gpt/generate_clients/api_client.py | 34 +++++++++++++++----- matrix_gpt/generate_clients/copilot.py | 38 +++++------------------ matrix_gpt/generate_clients/openai.py | 2 +- matrix_gpt/handle_actions.py | 8 ++--- 8 files changed, 50 insertions(+), 54 deletions(-) mode change 100644 => 100755 main.py diff --git a/README.md b/README.md index 07fb98d..6b28c31 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ I included a sample Systemd service (`matrixgpt.service`). ## Use First, invite your bot to a room. Then you can start a chat by prefixing your message with your trigger (for -example, `!c hello!`). The bot will create a thread when it replies. You don't need to use the trigger in the thread. +example, `!c hello`). The bot will create a thread when it replies. You don't need to use the trigger in the thread. Use `!matrixgpt` to view the bot's help. The bot also responds to `!bots`. diff --git a/main.py b/main.py old mode 100644 new mode 100755 diff --git a/matrix_gpt/generate.py b/matrix_gpt/generate.py index dd65027..ead3c15 100644 --- a/matrix_gpt/generate.py +++ b/matrix_gpt/generate.py @@ -50,7 +50,7 @@ async def generate_ai_response( context = [{'role': api_client.HUMAN_NAME, 'content': context}] # Build the context and do the things that need to be done for our specific API type. - api_client.assemble_context(context, system_prompt=command_info.system_prompt, injected_system_prompt=command_info.injected_system_prompt) + api_client.prepare_context(context, system_prompt=command_info.system_prompt, injected_system_prompt=command_info.injected_system_prompt) if api_client.check_ignore_request(): logger.debug(f'Reply to {event.event_id} was ignored by the model "{command_info.model}".') @@ -82,7 +82,7 @@ async def generate_ai_response( room.room_id, event.event_id, '❌', - extra_error='Exception' if global_config['send_extra_messages'] else None + extra_error='Exception while generating AI response' if global_config['send_extra_messages'] else None ) await client.room_typing(room.room_id, typing_state=False, timeout=1000) return @@ -93,7 +93,7 @@ async def generate_ai_response( room.room_id, event.event_id, '❌', - extra_error='Response was null.' if global_config['send_extra_messages'] else None + extra_error='AI response was empty' if global_config['send_extra_messages'] else None ) await client.room_typing(room.room_id, typing_state=False, timeout=1000) return @@ -135,7 +135,7 @@ async def generate_ai_response( await client.room_typing(room.room_id, typing_state=False, timeout=1000) if not isinstance(resp, RoomSendResponse): logger.critical(f'Failed to respond to event {event.event_id} in room {room.room_id}:\n{vars(resp)}') - await client_helper.react_to_event(room.room_id, event.event_id, '❌', extra_error='Exception' if global_config['send_extra_messages'] else None) - except Exception: - await client_helper.react_to_event(room.room_id, event.event_id, '❌', extra_error='Exception' if global_config['send_extra_messages'] else None) + await client_helper.react_to_event(room.room_id, event.event_id, '❌', extra_error='Exception while responding to event' if global_config['send_extra_messages'] else None) + except Exception as e: + await client_helper.react_to_event(room.room_id, event.event_id, '❌', extra_error=f'Exception during response process: {e}' if global_config['send_extra_messages'] else None) raise diff --git a/matrix_gpt/generate_clients/anthropic.py b/matrix_gpt/generate_clients/anthropic.py index b87d959..9f59c02 100644 --- a/matrix_gpt/generate_clients/anthropic.py +++ b/matrix_gpt/generate_clients/anthropic.py @@ -16,7 +16,7 @@ class AnthropicApiClient(ApiClient): api_key=self._api_key ) - def assemble_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None): + def prepare_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None): assert not len(self._context) self._context = context self.verify_context() @@ -28,19 +28,19 @@ class AnthropicApiClient(ApiClient): i = 0 while i < len(self._context) - 1: if self._context[i]['role'] == self._context[i + 1]['role']: - dummy = self.generate_text_msg(f'<{self._BOT_NAME} did not respond>', self._BOT_NAME) if self._context[i]['role'] == self._HUMAN_NAME else self.generate_text_msg(f'<{self._HUMAN_NAME} did not respond>', self._HUMAN_NAME) + dummy = self.text_msg(f'<{self._BOT_NAME} did not respond>', self._BOT_NAME) if self._context[i]['role'] == self._HUMAN_NAME else self.text_msg(f'<{self._HUMAN_NAME} did not respond>', self._HUMAN_NAME) self._context.insert(i + 1, dummy) i += 1 # if self._context[-1]['role'] == self._HUMAN_NAME: # self._context.append(self.generate_text_msg(f'<{self._BOT_NAME} did not respond>', self._BOT_NAME)) - def generate_text_msg(self, content: str, role: str): + def text_msg(self, content: str, role: str): assert role in [self._HUMAN_NAME, self._BOT_NAME] return {"role": role, "content": [{"type": "text", "text": str(content)}]} def append_msg(self, content: str, role: str): assert role in [self._HUMAN_NAME, self._BOT_NAME] - self._context.append(self.generate_text_msg(content, role)) + self._context.append(self.text_msg(content, role)) async def append_img(self, img_event: RoomMessageImage, role: str): assert role in [self._HUMAN_NAME, self._BOT_NAME] diff --git a/matrix_gpt/generate_clients/api_client.py b/matrix_gpt/generate_clients/api_client.py index 7dd3bcd..de2c771 100644 --- a/matrix_gpt/generate_clients/api_client.py +++ b/matrix_gpt/generate_clients/api_client.py @@ -20,33 +20,51 @@ class ApiClient: def _create_client(self, base_url: str = None): raise NotImplementedError - def check_ignore_request(self): + def check_ignore_request(self) -> bool: + """ + If the bot wants to ignore and not respond to a request. + """ return False - def assemble_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None): + def prepare_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None) -> None: + """ + Prepare and process the context that has already been loaded. + """ assert not len(self._context) raise NotImplementedError - def generate_text_msg(self, content: str, role: str): + def text_msg(self, content: str, role: str) -> None: + """ + Create a message of the type text with the given content and role. + """ raise NotImplementedError - def append_msg(self, content: str, role: str): + def append_msg(self, content: str, role: str) -> None: + """ + Add a message of the type text to the context. + """ raise NotImplementedError - async def append_img(self, img_event: RoomMessageImage, role: str): + async def append_img(self, img_event: RoomMessageImage, role: str) -> None: + """ + Add a message of the type image to the context. + """ raise NotImplementedError async def generate(self, command_info: CommandInfo, matrix_gpt_data: str = None) -> Tuple[str, dict | None]: + """ + Generate a response. + """ raise NotImplementedError @property - def context(self): + def context(self) -> list: return self._context.copy() @property - def HUMAN_NAME(self): + def HUMAN_NAME(self) -> str: return self._HUMAN_NAME @property - def BOT_NAME(self): + def BOT_NAME(self) -> str: return self._BOT_NAME diff --git a/matrix_gpt/generate_clients/copilot.py b/matrix_gpt/generate_clients/copilot.py index 920be76..afb55ca 100644 --- a/matrix_gpt/generate_clients/copilot.py +++ b/matrix_gpt/generate_clients/copilot.py @@ -1,12 +1,10 @@ import json import re -import time from urllib.parse import urlparse from cryptography.fernet import Fernet from nio import RoomMessageImage from sydney import SydneyClient -from sydney.exceptions import ThrottledRequestException from matrix_gpt.config import global_config from matrix_gpt.generate_clients.api_client import ApiClient @@ -45,12 +43,7 @@ class CopilotClient(ApiClient): async def append_img(self, img_event: RoomMessageImage, role: str): raise NotImplementedError - # def check_ignore_request(self): - # if len(self._context) > 1: - # return True - # return False - - def assemble_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None): + def prepare_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None): assert not len(self._context) self._context = context for i in range(len(self._context)): @@ -60,8 +53,8 @@ class CopilotClient(ApiClient): async def generate(self, command_info: CommandInfo, matrix_gpt_data: str = None): # TODO: config option for style async with SydneyClient(bing_cookies=self._api_key, style='precise') as sydney: - # Ignore any exceptions doing this since they will be caught by the caller. if matrix_gpt_data: + # Ignore any exceptions doing this since they will be caught by the caller. decrypted_metadata = decrypt_string(matrix_gpt_data) conversation_metadata = json.loads(decrypted_metadata) sydney.conversation_signature = conversation_metadata["conversation_signature"] @@ -70,26 +63,9 @@ class CopilotClient(ApiClient): sydney.client_id = conversation_metadata["client_id"] sydney.invocation_id = conversation_metadata["invocation_id"] - response = None - for i in range(3): - try: - response = dict(await sydney.ask(self._context[-1]['content'], citations=True, raw=True)) - break - except ThrottledRequestException: - time.sleep(10) - if not response: - # If this happens you should first try to change your cookies. - # Otherwise, you've used all your credits for today. - raise ThrottledRequestException - - bot_response = response['item']['messages'][-1] - - text_card = {} - for msg in bot_response['adaptiveCards'][0]['body']: - if msg.get('type') == 'TextBlock': - text_card = msg - break - response_text = text_card.get('text', '') + response_text = await sydney.ask(self._context[-1]['content'], citations=True) + if not len(response_text): + raise Exception('Copilot response was empty') # Parse the attribution links. attributions_strs = [] @@ -135,9 +111,11 @@ class CopilotClient(ApiClient): ) if len(self._context) == 1: + # Add this disclaimer because the owner of the Microsoft account that the bot uses can go and view + # his conversation history and view everything the bot has done. response_text += _COPILOT_WARNING_STR - # Store the conversation metadata in the response. It's encrypted for privacy purposes. + # Store the conversation metadata in the response Matrix event. It's encrypted for privacy purposes. custom_data = { 'thread_root_event': self._event.event_id, 'data': encrypt_string(event_data) diff --git a/matrix_gpt/generate_clients/openai.py b/matrix_gpt/generate_clients/openai.py index 7c72b6a..55d38a7 100644 --- a/matrix_gpt/generate_clients/openai.py +++ b/matrix_gpt/generate_clients/openai.py @@ -41,7 +41,7 @@ class OpenAIClient(ApiClient): }] }) - def assemble_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None): + def prepare_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None): assert not len(self._context) self._context = context if isinstance(system_prompt, str) and len(system_prompt): diff --git a/matrix_gpt/handle_actions.py b/matrix_gpt/handle_actions.py index bdd370b..dd5a97c 100644 --- a/matrix_gpt/handle_actions.py +++ b/matrix_gpt/handle_actions.py @@ -25,9 +25,9 @@ async def do_reply_msg(client_helper: MatrixClientHelper, room: MatrixRoom, requ context=msg, command_info=command_info, ) - except Exception: + except Exception as e: logger.critical(traceback.format_exc()) - await client_helper.react_to_event(room.room_id, requestor_event.event_id, '❌') + await client_helper.react_to_event(room.room_id, requestor_event.event_id, '❌', extra_error=f'Exception during response process: {e}' if global_config['send_extra_messages'] else None) raise @@ -86,9 +86,9 @@ async def do_reply_threaded_msg(client_helper: MatrixClientHelper, room: MatrixR thread_root_id=thread_content[0].event_id, matrix_gpt_data=matrix_gpt_data ) - except: + except Exception as e: logger.error(traceback.format_exc()) - await client_helper.react_to_event(room.room_id, event.event_id, '❌') + await client_helper.react_to_event(room.room_id, requestor_event.event_id, '❌', extra_error=f'Exception during response process: {e}' if global_config['send_extra_messages'] else None) raise