simplify copilot response, adjust error responses, other minor changes
This commit is contained in:
parent
72640ae35f
commit
0d52be41db
|
@ -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`.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue