From a67b1f6e1ccaeeca37cf26f2eedff6820fec3067 Mon Sep 17 00:00:00 2001 From: Randy Mackay Date: Tue, 19 Dec 2023 20:48:55 +0900 Subject: [PATCH 1/4] chat: assistant setup may re-use existing files --- .../assistant_setup/setup_assistant.py | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/MAVProxy/modules/mavproxy_chat/assistant_setup/setup_assistant.py b/MAVProxy/modules/mavproxy_chat/assistant_setup/setup_assistant.py index 6a5d5502fb..9a5f698706 100644 --- a/MAVProxy/modules/mavproxy_chat/assistant_setup/setup_assistant.py +++ b/MAVProxy/modules/mavproxy_chat/assistant_setup/setup_assistant.py @@ -140,25 +140,34 @@ def main(openai_api_key=None, assistant_name=None, model_name=None, upgrade=Fals print("setup_assistant: failed to open file: " + filename) exit() - # if OpenAI has existing files with the same name, delete them - # Note: this is slightly dangerous because files in use by another assistant could be deleted + # check if OpenAI has existing files with the same name + file_needs_uploading = True for existing_file in existing_files.data: if filename == existing_file.filename: - try: - client.files.delete(existing_file.id) - print("setup_assistant: deleted existing file: " + filename) - except: - print("setup_assistant: failed to delete file from OpenAI: " + filename) - exit() + # if not upgrading, we associate the existing file with our assistant + if not upgrade: + uploaded_file_ids.append(existing_file.id) + print("setup_assistant: using existing file: " + filename) + file_needs_uploading = False + else: + # if upgrading them we delete and re-upload the file + # Note: this is slightly dangerous because files in use by another assistant could be deleted + try: + client.files.delete(existing_file.id) + print("setup_assistant: deleted existing file: " + filename) + except: + print("setup_assistant: failed to delete file from OpenAI: " + filename) + exit() # upload file to OpenAI - try: - uploaded_file = client.files.create(file=file, purpose="assistants") - uploaded_file_ids.append(uploaded_file.id) - print("setup_assistant: uploaded: " + filename) - except: - print("setup_assistant: failed to upload file to OpenAI: " + filename) - exit() + if file_needs_uploading: + try: + uploaded_file = client.files.create(file=file, purpose="assistants") + uploaded_file_ids.append(uploaded_file.id) + print("setup_assistant: uploaded: " + filename) + except: + print("setup_assistant: failed to upload file to OpenAI: " + filename) + exit() # update assistant's accessible files try: From a6402fedb62ff0f80eb6fc4ff8c2707783d0fcbb Mon Sep 17 00:00:00 2001 From: Randy Mackay Date: Wed, 20 Dec 2023 16:54:11 +0900 Subject: [PATCH 2/4] chat: add OpenAI assistant file checker script This helps find files that are not used by any assistant so that they can be deleted --- .../assistant_setup/assistant_file_checker.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 MAVProxy/modules/mavproxy_chat/assistant_setup/assistant_file_checker.py diff --git a/MAVProxy/modules/mavproxy_chat/assistant_setup/assistant_file_checker.py b/MAVProxy/modules/mavproxy_chat/assistant_setup/assistant_file_checker.py new file mode 100644 index 0000000000..e684ddd250 --- /dev/null +++ b/MAVProxy/modules/mavproxy_chat/assistant_setup/assistant_file_checker.py @@ -0,0 +1,111 @@ +''' +AI Chat Module Assistant File Checker +Randy Mackay, December 2023 + +This script is used to check for inconsistencies in the Assistants and Files. +E.g. files which are not used by any Assistants or Assistants which use files which do not exist. + +OpenAI Assistant API: https://platform.openai.com/docs/api-reference/assistants +OpenAI Assistant Playground: https://platform.openai.com/playground +''' + +import datetime, os + +try: + from openai import OpenAI +except: + print("chat: failed to import openai. See https://ardupilot.org/mavproxy/docs/modules/chat.html") + exit() + +# main function +def main(openai_api_key=None, delete_unused=False): + + print("Starting Assistant File checker") + + # create connection object + try: + # if api key is provided, use it to create connection object + if openai_api_key is not None: + client = OpenAI(api_key=openai_api_key) + else: + # if no api key is provided, attempt to create connection object without it + # user may have set the OPENAI_API_KEY environment variable + client = OpenAI() + except: + # if connection object creation fails, exit with error message + print("assistant_file_checker: failed to connect to OpenAI. Perhaps the API key was incorrect?") + exit() + + # get a list of all Files + existing_files = client.files.list() + print("File list:") + file_found = False + for file in existing_files.data: + print(" id:" + file.id + " name:" + file.filename + " size:" + str(file.bytes) + " date:" + str(datetime.datetime.fromtimestamp(file.created_at))) + file_found = True + if not file_found: + print(" no files found") + + # get a list of all assistants + assistants_list = client.beta.assistants.list() + + # prepare a dictionary of file ids to the number of assistants using the file + file_assistant_count = {} + + # can files be used by more than one assistant? + + # for each assistant, retrieve the list of files it uses + for assistant in assistants_list.data: + # print assistant name + print("Assistant: " + assistant.name) + + # print list of files used by this assistant + # increment the number of assistants associate with this file + for assistant_file in client.beta.assistants.files.list(assistant_id=assistant.id): + file_assistant_count[assistant_file.id] = file_assistant_count.get(assistant_file.id, 0) + 1 + filename_size_date = get_filename_size_date_from_id(existing_files, assistant_file.id) + print(" id:" + assistant_file.id + " name:" + filename_size_date[0] + " size:" + str(filename_size_date[1]) + " date:" + filename_size_date[2]) + + # display the list of files which are not used by any Assistants + print_unused_files_header = True + for existing_file in existing_files: + if not existing_file.id in file_assistant_count.keys(): + # print header if this is the first unused file + if print_unused_files_header: + print("Files not used by any Assistants:") + print_unused_files_header = False + + # print file attributes + filename_size_date = get_filename_size_date_from_id(existing_files, existing_file.id) + print(" id:" + existing_file.id + " name:" + filename_size_date[0] + " size:" + str(filename_size_date[1]) + " date:" + filename_size_date[2]) + + # delete the unused file if specified by the user + if delete_unused: + client.files.delete(file_id=existing_file.id) + print(" deleted: " + existing_file.id + " name:" + filename_size_date[0]) + + # print completion message + print("Assistant File check complete") + +# searches the files_list for the specified file id and retuns the filename, size and creation date (as a string) if found +# returns None if not found +def get_filename_size_date_from_id(files_list, file_id): + for file in files_list.data: + if file.id == file_id: + return file.filename, file.bytes, str(datetime.datetime.fromtimestamp(file.created_at)) + return "not found", "unknown", "unknown" + +# call main function if this is run as standalone script +if __name__ == "__main__": + + # parse command line arguments + from argparse import ArgumentParser + parser = ArgumentParser(description="MAVProxy Chat Module Assistant File Checker") + parser.add_argument("--api-key", default=None, help="OpenAI API Key") + parser.add_argument("--delete-unused", action='store_true', help="delete unused files") + try: + args = parser.parse_args() + except AttributeError as err: + parser.print_help() + os.sys.exit(0) + main(openai_api_key=args.api_key, delete_unused=args.delete_unused) From c293d0412293f6275c2d6b0953eae54c1b1a1f06 Mon Sep 17 00:00:00 2001 From: Randy Mackay Date: Thu, 21 Dec 2023 11:51:04 +0900 Subject: [PATCH 3/4] chat: fix reply window colour display also fix background colour on windows --- MAVProxy/modules/mavproxy_chat/chat_window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MAVProxy/modules/mavproxy_chat/chat_window.py b/MAVProxy/modules/mavproxy_chat/chat_window.py index e9874ef317..2b27420702 100644 --- a/MAVProxy/modules/mavproxy_chat/chat_window.py +++ b/MAVProxy/modules/mavproxy_chat/chat_window.py @@ -26,6 +26,7 @@ def __init__(self, mpstate): # create chat window self.app = wx.App() self.frame = wx.Frame(None, title="Chat", size=(650, 200)) + self.frame.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) # add menu self.menu = wx.Menu() @@ -63,7 +64,7 @@ def __init__(self, mpstate): self.horiz_sizer.Add(self.send_button, proportion = 0, flag = wx.ALIGN_TOP | wx.ALL, border = 5) # add a reply box and read-only text box - self.text_reply = wx.TextCtrl(self.frame, id=-1, size=(600, 80), style=wx.TE_READONLY | wx.TE_MULTILINE) + self.text_reply = wx.TextCtrl(self.frame, id=-1, size=(600, 80), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH) # add a read-only status text box at the bottom self.text_status = wx.TextCtrl(self.frame, id=-1, size=(600, -1), style=wx.TE_READONLY) @@ -158,7 +159,7 @@ def send_text_to_assistant(self): # copy user input text to reply box orig_text_attr = self.text_reply.GetDefaultStyle() - wx.CallAfter(self.text_reply.SetDefaultStyle, wx.TextAttr(wx.RED, alignment=wx.TEXT_ALIGNMENT_RIGHT)) + wx.CallAfter(self.text_reply.SetDefaultStyle, wx.TextAttr(wx.RED)) wx.CallAfter(self.text_reply.AppendText, send_text + "\n") # send text to assistant and place reply in reply box From b6c1b66fead7ffcdf0f137317334302e957b27a1 Mon Sep 17 00:00:00 2001 From: Randy Mackay Date: Wed, 20 Dec 2023 21:16:48 +0900 Subject: [PATCH 4/4] chat: do not send empty text to assitant Also minor stutus text fixes --- MAVProxy/modules/mavproxy_chat/chat_window.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/MAVProxy/modules/mavproxy_chat/chat_window.py b/MAVProxy/modules/mavproxy_chat/chat_window.py index 2b27420702..ca2eda81d9 100644 --- a/MAVProxy/modules/mavproxy_chat/chat_window.py +++ b/MAVProxy/modules/mavproxy_chat/chat_window.py @@ -117,21 +117,22 @@ def record_button_click(self, event): # record button clicked def record_button_click_execute(self, event): # record audio + self.set_status_text("recording audio") rec_filename = self.chat_voice_to_text.record_audio() if rec_filename is None: - print("chat: audio recording failed") - self.set_status_text("Audio recording failed") + self.set_status_text("audio recording failed") return # convert audio to text and place in input box + self.set_status_text("converting audio to text") text = self.chat_voice_to_text.convert_audio_to_text(rec_filename) - if text is None: - print("chat: audio to text conversion failed") - self.set_status_text("Audio to text conversion failed") + if text is None or len(text) == 0: + self.set_status_text("audio to text conversion failed") return wx.CallAfter(self.text_input.SetValue, text) # send text to assistant + self.set_status_text("sending text to assistasnt") self.send_text_to_assistant() # send button clicked @@ -140,6 +141,9 @@ def send_button_click(self, event): # handle text input def text_input_change(self, event): + # protect against sending empty text + if self.text_input.GetValue() == "": + return # send text to assistant in a separate thread th = Thread(target=self.send_text_to_assistant) th.start()