simplify copilot response, adjust error responses, other minor changes

This commit is contained in:
Cyberes 2024-04-11 14:32:38 -06:00
parent 72640ae35f
commit 0d52be41db
8 changed files with 50 additions and 54 deletions

View File

@ -35,7 +35,7 @@ I included a sample Systemd service (`matrixgpt.service`).
## Use ## Use
First, invite your bot to a room. Then you can start a chat by prefixing your message with your trigger (for 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`. Use `!matrixgpt` to view the bot's help. The bot also responds to `!bots`.

0
main.py Normal file → Executable file
View File

View File

@ -50,7 +50,7 @@ async def generate_ai_response(
context = [{'role': api_client.HUMAN_NAME, 'content': context}] 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. # 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(): if api_client.check_ignore_request():
logger.debug(f'Reply to {event.event_id} was ignored by the model "{command_info.model}".') 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, room.room_id,
event.event_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) await client.room_typing(room.room_id, typing_state=False, timeout=1000)
return return
@ -93,7 +93,7 @@ async def generate_ai_response(
room.room_id, room.room_id,
event.event_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) await client.room_typing(room.room_id, typing_state=False, timeout=1000)
return return
@ -135,7 +135,7 @@ async def generate_ai_response(
await client.room_typing(room.room_id, typing_state=False, timeout=1000) await client.room_typing(room.room_id, typing_state=False, timeout=1000)
if not isinstance(resp, RoomSendResponse): if not isinstance(resp, RoomSendResponse):
logger.critical(f'Failed to respond to event {event.event_id} in room {room.room_id}:\n{vars(resp)}') 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) 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: except Exception as e:
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=f'Exception during response process: {e}' if global_config['send_extra_messages'] else None)
raise raise

View File

@ -16,7 +16,7 @@ class AnthropicApiClient(ApiClient):
api_key=self._api_key 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) assert not len(self._context)
self._context = context self._context = context
self.verify_context() self.verify_context()
@ -28,19 +28,19 @@ class AnthropicApiClient(ApiClient):
i = 0 i = 0
while i < len(self._context) - 1: while i < len(self._context) - 1:
if self._context[i]['role'] == self._context[i + 1]['role']: 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) self._context.insert(i + 1, dummy)
i += 1 i += 1
# if self._context[-1]['role'] == self._HUMAN_NAME: # 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)) # 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] assert role in [self._HUMAN_NAME, self._BOT_NAME]
return {"role": role, "content": [{"type": "text", "text": str(content)}]} return {"role": role, "content": [{"type": "text", "text": str(content)}]}
def append_msg(self, content: str, role: str): def append_msg(self, content: str, role: str):
assert role in [self._HUMAN_NAME, self._BOT_NAME] 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): async def append_img(self, img_event: RoomMessageImage, role: str):
assert role in [self._HUMAN_NAME, self._BOT_NAME] assert role in [self._HUMAN_NAME, self._BOT_NAME]

View File

@ -20,33 +20,51 @@ class ApiClient:
def _create_client(self, base_url: str = None): def _create_client(self, base_url: str = None):
raise NotImplementedError 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 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) assert not len(self._context)
raise NotImplementedError 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 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 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 raise NotImplementedError
async def generate(self, command_info: CommandInfo, matrix_gpt_data: str = None) -> Tuple[str, dict | None]: async def generate(self, command_info: CommandInfo, matrix_gpt_data: str = None) -> Tuple[str, dict | None]:
"""
Generate a response.
"""
raise NotImplementedError raise NotImplementedError
@property @property
def context(self): def context(self) -> list:
return self._context.copy() return self._context.copy()
@property @property
def HUMAN_NAME(self): def HUMAN_NAME(self) -> str:
return self._HUMAN_NAME return self._HUMAN_NAME
@property @property
def BOT_NAME(self): def BOT_NAME(self) -> str:
return self._BOT_NAME return self._BOT_NAME

View File

@ -1,12 +1,10 @@
import json import json
import re import re
import time
from urllib.parse import urlparse from urllib.parse import urlparse
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from nio import RoomMessageImage from nio import RoomMessageImage
from sydney import SydneyClient from sydney import SydneyClient
from sydney.exceptions import ThrottledRequestException
from matrix_gpt.config import global_config from matrix_gpt.config import global_config
from matrix_gpt.generate_clients.api_client import ApiClient 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): async def append_img(self, img_event: RoomMessageImage, role: str):
raise NotImplementedError raise NotImplementedError
# def check_ignore_request(self): def prepare_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None):
# if len(self._context) > 1:
# return True
# return False
def assemble_context(self, context: list, system_prompt: str = None, injected_system_prompt: str = None):
assert not len(self._context) assert not len(self._context)
self._context = context self._context = context
for i in range(len(self._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): async def generate(self, command_info: CommandInfo, matrix_gpt_data: str = None):
# TODO: config option for style # TODO: config option for style
async with SydneyClient(bing_cookies=self._api_key, style='precise') as sydney: 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: if matrix_gpt_data:
# Ignore any exceptions doing this since they will be caught by the caller.
decrypted_metadata = decrypt_string(matrix_gpt_data) decrypted_metadata = decrypt_string(matrix_gpt_data)
conversation_metadata = json.loads(decrypted_metadata) conversation_metadata = json.loads(decrypted_metadata)
sydney.conversation_signature = conversation_metadata["conversation_signature"] sydney.conversation_signature = conversation_metadata["conversation_signature"]
@ -70,26 +63,9 @@ class CopilotClient(ApiClient):
sydney.client_id = conversation_metadata["client_id"] sydney.client_id = conversation_metadata["client_id"]
sydney.invocation_id = conversation_metadata["invocation_id"] sydney.invocation_id = conversation_metadata["invocation_id"]
response = None response_text = await sydney.ask(self._context[-1]['content'], citations=True)
for i in range(3): if not len(response_text):
try: raise Exception('Copilot response was empty')
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', '')
# Parse the attribution links. # Parse the attribution links.
attributions_strs = [] attributions_strs = []
@ -135,9 +111,11 @@ class CopilotClient(ApiClient):
) )
if len(self._context) == 1: 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 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 = { custom_data = {
'thread_root_event': self._event.event_id, 'thread_root_event': self._event.event_id,
'data': encrypt_string(event_data) 'data': encrypt_string(event_data)

View File

@ -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) assert not len(self._context)
self._context = context self._context = context
if isinstance(system_prompt, str) and len(system_prompt): if isinstance(system_prompt, str) and len(system_prompt):

View File

@ -25,9 +25,9 @@ async def do_reply_msg(client_helper: MatrixClientHelper, room: MatrixRoom, requ
context=msg, context=msg,
command_info=command_info, command_info=command_info,
) )
except Exception: except Exception as e:
logger.critical(traceback.format_exc()) 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 raise
@ -86,9 +86,9 @@ async def do_reply_threaded_msg(client_helper: MatrixClientHelper, room: MatrixR
thread_root_id=thread_content[0].event_id, thread_root_id=thread_content[0].event_id,
matrix_gpt_data=matrix_gpt_data matrix_gpt_data=matrix_gpt_data
) )
except: except Exception as e:
logger.error(traceback.format_exc()) 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 raise