Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat: minor GUI and assistant setup enhanecments #1288

Merged
merged 4 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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)
39 changes: 24 additions & 15 deletions MAVProxy/modules/mavproxy_chat/assistant_setup/setup_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
19 changes: 12 additions & 7 deletions MAVProxy/modules/mavproxy_chat/chat_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -116,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
Expand All @@ -139,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()
Expand All @@ -158,7 +163,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
Expand Down