From e8de41c60f1b3eacf9734bf11fd02e7e5c40e5a6 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Thu, 27 Jul 2023 23:39:22 +0800 Subject: [PATCH 01/16] Add models API --- src/handyllm/openai_api.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 3973a6a..5894748 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -33,7 +33,7 @@ def _get_organization_from_env(): return os.environ.get('OPENAI_ORGANIZATION') @staticmethod - def _api_request(url, api_key, organization=None, timeout=None, **kwargs): + def _api_request(url, api_key, organization=None, method='post', timeout=None, **kwargs): if api_key is None: raise Exception("OpenAI API key is not set") if url is None: @@ -50,7 +50,7 @@ def _api_request(url, api_key, organization=None, timeout=None, **kwargs): log_strs.append(f"timeout: {timeout}") module_logger.info('\n'.join(log_strs)) - request_data = kwargs + request_data = kwargs if method == 'post' else None headers = { 'Authorization': 'Bearer ' + api_key, 'Content-Type': 'application/json' @@ -59,7 +59,8 @@ def _api_request(url, api_key, organization=None, timeout=None, **kwargs): headers['OpenAI-Organization'] = organization stream = kwargs.get('stream', False) - response = requests.post( + response = requests.request( + method, url, headers=headers, # data=json.dumps(request_data), @@ -131,7 +132,7 @@ def chat(timeout=None, endpoint_manager=None, logger=None, log_marks=[], **kwarg start_time = time.time() try: - response = OpenAIAPI.api_request_endpoint(request_url, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + response = OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) if logger is not None: end_time = time.time() @@ -185,7 +186,7 @@ def completions(timeout=None, endpoint_manager=None, logger=None, log_marks=[], start_time = time.time() try: - response = OpenAIAPI.api_request_endpoint(request_url, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + response = OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) if logger is not None: end_time = time.time() @@ -225,7 +226,14 @@ def wrapper(response): @staticmethod def embeddings(timeout=None, endpoint_manager=None, **kwargs): request_url = '/embeddings' - return OpenAIAPI.api_request_endpoint(request_url, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def models(model=None, timeout=None, endpoint_manager=None, **kwargs): + request_url = '/models' + if model: + request_url += f'/{model}' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) if __name__ == '__main__': From 759b91bcea6759d3f6a8733f53222b58a61c0db2 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Thu, 27 Jul 2023 23:41:50 +0800 Subject: [PATCH 02/16] Add moderations API --- src/handyllm/openai_api.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 5894748..2b474ed 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -234,6 +234,11 @@ def models(model=None, timeout=None, endpoint_manager=None, **kwargs): if model: request_url += f'/{model}' return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def moderations(timeout=None, endpoint_manager=None, **kwargs): + request_url = '/moderations' + return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) if __name__ == '__main__': From 50f421cf7add946de637c06df6d44a71a0111f7c Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 02:08:23 +0800 Subject: [PATCH 03/16] Add images API --- src/handyllm/openai_api.py | 28 ++++++++++++++++++++++++---- src/handyllm/utils.py | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/handyllm/utils.py diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 2b474ed..8c92120 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -50,13 +50,13 @@ def _api_request(url, api_key, organization=None, method='post', timeout=None, * log_strs.append(f"timeout: {timeout}") module_logger.info('\n'.join(log_strs)) + files = kwargs.pop('files', None) request_data = kwargs if method == 'post' else None - headers = { - 'Authorization': 'Bearer ' + api_key, - 'Content-Type': 'application/json' - } + headers = { 'Authorization': 'Bearer ' + api_key } if organization is not None: headers['OpenAI-Organization'] = organization + if files is None: ## if files is not None, let requests handle the content type + headers['Content-Type'] = 'application/json' stream = kwargs.get('stream', False) response = requests.request( @@ -65,6 +65,7 @@ def _api_request(url, api_key, organization=None, method='post', timeout=None, * headers=headers, # data=json.dumps(request_data), json=request_data, + files=files, stream=stream, timeout=timeout, ) @@ -240,6 +241,25 @@ def moderations(timeout=None, endpoint_manager=None, **kwargs): request_url = '/moderations' return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + @staticmethod + def images_generations(timeout=None, endpoint_manager=None, **kwargs): + request_url = '/images/generations' + return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def images_edits(image, mask=None, timeout=None, endpoint_manager=None, **kwargs): + request_url = '/images/edits' + files = { 'image': image } + if mask: + files['mask'] = mask + return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def images_variations(image, timeout=None, endpoint_manager=None, **kwargs): + request_url = '/images/variations' + files = { 'image': image } + return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + if __name__ == '__main__': # OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' diff --git a/src/handyllm/utils.py b/src/handyllm/utils.py new file mode 100644 index 0000000..846df69 --- /dev/null +++ b/src/handyllm/utils.py @@ -0,0 +1,25 @@ +import requests +from urllib.parse import urlparse +import os +import time + + +def get_filename_from_url(download_url): + # Parse the URL. + parsed_url = urlparse(download_url) + # The last part of the path is usually the filename. + filename = os.path.basename(parsed_url.path) + return filename + +def download_binary(download_url, file_path=None, dir='.'): + response = requests.get(download_url, allow_redirects=True) + if file_path == None: + filename = get_filename_from_url(download_url) + if filename == '' or filename == None: + filename = 'download_' + time.strftime("%Y%m%d_%H%M%S") + file_path = os.path.abspath(os.path.join(dir, filename)) + # Open the file in binary mode and write to it. + with open(file_path, "wb") as file: + file.write(response.content) + return file_path + From c9bd1d8228eba282191c45d5aa34b3b2507ea3a1 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 02:15:38 +0800 Subject: [PATCH 04/16] Add audio API --- src/handyllm/openai_api.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 8c92120..83b6f3d 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -260,6 +260,18 @@ def images_variations(image, timeout=None, endpoint_manager=None, **kwargs): files = { 'image': image } return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + @staticmethod + def audio_transcriptions(file, timeout=None, endpoint_manager=None, **kwargs): + request_url = '/audio/transcriptions' + files = { 'file': file } + return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def audio_translations(file, timeout=None, endpoint_manager=None, **kwargs): + request_url = '/audio/translations' + files = { 'file': file } + return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + if __name__ == '__main__': # OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' From b4a62005497604ef08fc798e534e43698a125951 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 02:24:04 +0800 Subject: [PATCH 05/16] Add files API --- src/handyllm/openai_api.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 83b6f3d..2dd81b0 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -272,6 +272,32 @@ def audio_translations(file, timeout=None, endpoint_manager=None, **kwargs): files = { 'file': file } return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + @staticmethod + def files_list(timeout=None, endpoint_manager=None, **kwargs): + request_url = '/files' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def files_upload(file, timeout=None, endpoint_manager=None, **kwargs): + request_url = '/files' + files = { 'file': file } + return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def files_delete(file_id, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/files/{file_id}' + return OpenAIAPI.api_request_endpoint(request_url, method='delete', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def files_retrieve(file_id, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/files/{file_id}' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def files_retrieve_content(file_id, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/files/{file_id}/content' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + if __name__ == '__main__': # OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' From 12978b01ea7b248954c396c1e36d8ebca3e14812 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 02:47:27 +0800 Subject: [PATCH 06/16] Add fine-tunes API --- src/handyllm/openai_api.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 2dd81b0..6bfb43c 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -59,6 +59,7 @@ def _api_request(url, api_key, organization=None, method='post', timeout=None, * headers['Content-Type'] = 'application/json' stream = kwargs.get('stream', False) + params = { "stream": "true" } if method == 'get' and stream else None response = requests.request( method, url, @@ -66,6 +67,7 @@ def _api_request(url, api_key, organization=None, method='post', timeout=None, * # data=json.dumps(request_data), json=request_data, files=files, + params=params, stream=stream, timeout=timeout, ) @@ -298,6 +300,36 @@ def files_retrieve_content(file_id, timeout=None, endpoint_manager=None, **kwarg request_url = f'/files/{file_id}/content' return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + @staticmethod + def finetunes(timeout=None, endpoint_manager=None, **kwargs): + request_url = '/fine-tunes' + return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def finetunes_list(timeout=None, endpoint_manager=None, **kwargs): + request_url = '/fine-tunes' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def finetunes_retrieve(fine_tune_id, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/fine-tunes/{fine_tune_id}' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def finetunes_cancel(fine_tune_id, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/fine-tunes/{fine_tune_id}/cancel' + return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def finetunes_events(fine_tune_id, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/fine-tunes/{fine_tune_id}/events' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def finetunes_delete_model(model, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/models/{model}' + return OpenAIAPI.api_request_endpoint(request_url, method='delete', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + if __name__ == '__main__': # OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' From 8e64ff4eaf617b99c7d5715a7eb600cba8e6f60a Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 02:51:06 +0800 Subject: [PATCH 07/16] Add edits API --- src/handyllm/openai_api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 6bfb43c..9fc4a19 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -226,6 +226,11 @@ def wrapper(response): return response + @staticmethod + def edits(timeout=None, endpoint_manager=None, **kwargs): + request_url = '/edits' + return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + @staticmethod def embeddings(timeout=None, endpoint_manager=None, **kwargs): request_url = '/embeddings' @@ -237,7 +242,7 @@ def models(model=None, timeout=None, endpoint_manager=None, **kwargs): if model: request_url += f'/{model}' return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) - + @staticmethod def moderations(timeout=None, endpoint_manager=None, **kwargs): request_url = '/moderations' From 288d90d2d7cd5ec9700150f3b2ac8c647dbb6434 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 03:03:49 +0800 Subject: [PATCH 08/16] Improve APIs --- src/handyllm/openai_api.py | 55 ++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 9fc4a19..95ddac6 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -120,7 +120,7 @@ def api_request_endpoint(request_url, endpoint_manager=None, **kwargs): return OpenAIAPI._api_request(url, api_key, organization=organization, **kwargs) @staticmethod - def chat(timeout=None, endpoint_manager=None, logger=None, log_marks=[], **kwargs): + def chat(model, messages, timeout=None, endpoint_manager=None, logger=None, log_marks=[], **kwargs): request_url = '/chat/completions' if logger is not None and 'messages' in kwargs: @@ -135,7 +135,7 @@ def chat(timeout=None, endpoint_manager=None, logger=None, log_marks=[], **kwarg start_time = time.time() try: - response = OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + response = OpenAIAPI.api_request_endpoint(request_url, model=model, messages=messages, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) if logger is not None: end_time = time.time() @@ -174,7 +174,7 @@ def wrapper(response): return response @staticmethod - def completions(timeout=None, endpoint_manager=None, logger=None, log_marks=[], **kwargs): + def completions(model, prompt, timeout=None, endpoint_manager=None, logger=None, log_marks=[], **kwargs): request_url = '/completions' if logger is not None and 'prompt' in kwargs: @@ -189,7 +189,7 @@ def completions(timeout=None, endpoint_manager=None, logger=None, log_marks=[], start_time = time.time() try: - response = OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + response = OpenAIAPI.api_request_endpoint(request_url, model=model, prompt=prompt, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) if logger is not None: end_time = time.time() @@ -227,39 +227,42 @@ def wrapper(response): return response @staticmethod - def edits(timeout=None, endpoint_manager=None, **kwargs): + def edits(model, instruction, timeout=None, endpoint_manager=None, **kwargs): request_url = '/edits' - return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, model=model, instruction=instruction, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def embeddings(timeout=None, endpoint_manager=None, **kwargs): + def embeddings(model, input, timeout=None, endpoint_manager=None, **kwargs): request_url = '/embeddings' - return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, model=model, input=input, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def models(model=None, timeout=None, endpoint_manager=None, **kwargs): + def models_list(timeout=None, endpoint_manager=None, **kwargs): request_url = '/models' - if model: - request_url += f'/{model}' return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def moderations(timeout=None, endpoint_manager=None, **kwargs): + def models_retrieve(model, timeout=None, endpoint_manager=None, **kwargs): + request_url = f'/models/{model}' + return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + + @staticmethod + def moderations(input, timeout=None, endpoint_manager=None, **kwargs): request_url = '/moderations' - return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, input=input, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def images_generations(timeout=None, endpoint_manager=None, **kwargs): + def images_generations(prompt, timeout=None, endpoint_manager=None, **kwargs): request_url = '/images/generations' - return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, prompt=prompt, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def images_edits(image, mask=None, timeout=None, endpoint_manager=None, **kwargs): + def images_edits(image, prompt, mask=None, timeout=None, endpoint_manager=None, **kwargs): request_url = '/images/edits' files = { 'image': image } if mask: files['mask'] = mask - return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, prompt=prompt, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod def images_variations(image, timeout=None, endpoint_manager=None, **kwargs): @@ -268,16 +271,16 @@ def images_variations(image, timeout=None, endpoint_manager=None, **kwargs): return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def audio_transcriptions(file, timeout=None, endpoint_manager=None, **kwargs): + def audio_transcriptions(file, model, timeout=None, endpoint_manager=None, **kwargs): request_url = '/audio/transcriptions' files = { 'file': file } - return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, model=model, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def audio_translations(file, timeout=None, endpoint_manager=None, **kwargs): + def audio_translations(file, model, timeout=None, endpoint_manager=None, **kwargs): request_url = '/audio/translations' files = { 'file': file } - return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, model=model, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod def files_list(timeout=None, endpoint_manager=None, **kwargs): @@ -285,10 +288,10 @@ def files_list(timeout=None, endpoint_manager=None, **kwargs): return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def files_upload(file, timeout=None, endpoint_manager=None, **kwargs): + def files_upload(file, purpose, timeout=None, endpoint_manager=None, **kwargs): request_url = '/files' files = { 'file': file } - return OpenAIAPI.api_request_endpoint(request_url, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, purpose=purpose, method='post', files=files, timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod def files_delete(file_id, timeout=None, endpoint_manager=None, **kwargs): @@ -306,9 +309,9 @@ def files_retrieve_content(file_id, timeout=None, endpoint_manager=None, **kwarg return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def finetunes(timeout=None, endpoint_manager=None, **kwargs): + def finetunes_create(training_file, timeout=None, endpoint_manager=None, **kwargs): request_url = '/fine-tunes' - return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) + return OpenAIAPI.api_request_endpoint(request_url, training_file=training_file, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod def finetunes_list(timeout=None, endpoint_manager=None, **kwargs): @@ -326,7 +329,7 @@ def finetunes_cancel(fine_tune_id, timeout=None, endpoint_manager=None, **kwargs return OpenAIAPI.api_request_endpoint(request_url, method='post', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) @staticmethod - def finetunes_events(fine_tune_id, timeout=None, endpoint_manager=None, **kwargs): + def finetunes_list_events(fine_tune_id, timeout=None, endpoint_manager=None, **kwargs): request_url = f'/fine-tunes/{fine_tune_id}/events' return OpenAIAPI.api_request_endpoint(request_url, method='get', timeout=timeout, endpoint_manager=endpoint_manager, **kwargs) From bd0e1f191cab26aa0e32857889948ec252495255 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 03:04:06 +0800 Subject: [PATCH 09/16] Add ignored files --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index ccd8a01..27aad61 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,11 @@ dist *.env venv + +*.png +*.jpg +*.jpeg +*.gif +*.svg +*.ico +*.pdf From 35f766a34a2bf5bdb7ad845ea48cb8559bb2d306 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 03:17:03 +0800 Subject: [PATCH 10/16] fix data payload when `files` is specified --- src/handyllm/openai_api.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 95ddac6..78652bf 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -51,12 +51,17 @@ def _api_request(url, api_key, organization=None, method='post', timeout=None, * module_logger.info('\n'.join(log_strs)) files = kwargs.pop('files', None) - request_data = kwargs if method == 'post' else None headers = { 'Authorization': 'Bearer ' + api_key } if organization is not None: headers['OpenAI-Organization'] = organization - if files is None: ## if files is not None, let requests handle the content type - headers['Content-Type'] = 'application/json' + if method == 'post': + if files is None: + headers['Content-Type'] = 'application/json' + json_data = kwargs + data = None + else: ## if files is not None, let requests handle the content type + json_data = None + data = kwargs stream = kwargs.get('stream', False) params = { "stream": "true" } if method == 'get' and stream else None @@ -64,8 +69,9 @@ def _api_request(url, api_key, organization=None, method='post', timeout=None, * method, url, headers=headers, - # data=json.dumps(request_data), - json=request_data, + # data=json.dumps(json_data), + data=data, + json=json_data, files=files, params=params, stream=stream, From 8bb6db7c3881d677419d9558363728e9b0f7a711 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 11:54:07 +0800 Subject: [PATCH 11/16] Fix data in GET --- src/handyllm/openai_api.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index 78652bf..ec476d8 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -51,25 +51,26 @@ def _api_request(url, api_key, organization=None, method='post', timeout=None, * module_logger.info('\n'.join(log_strs)) files = kwargs.pop('files', None) + stream = kwargs.get('stream', False) headers = { 'Authorization': 'Bearer ' + api_key } + json_data = None + data = None + params = None if organization is not None: headers['OpenAI-Organization'] = organization if method == 'post': if files is None: headers['Content-Type'] = 'application/json' json_data = kwargs - data = None else: ## if files is not None, let requests handle the content type - json_data = None data = kwargs - - stream = kwargs.get('stream', False) - params = { "stream": "true" } if method == 'get' and stream else None + if method == 'get' and stream: + params = { "stream": "true" } + response = requests.request( method, - url, - headers=headers, - # data=json.dumps(json_data), + url, + headers=headers, data=data, json=json_data, files=files, From c5400ece5ed6d109d68528ca06b3bed69bceaa1d Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 11:58:21 +0800 Subject: [PATCH 12/16] Add example test files --- .gitignore | 9 +++++++ tests/test_audio.py | 17 +++++++++++++ tests/test_embedding.py | 19 ++++++++++++++ tests/test_files.py | 21 +++++++++++++++ tests/test_images.py | 55 ++++++++++++++++++++++++++++++++++++++++ tests/test_models.py | 25 ++++++++++++++++++ tests/test_moderation.py | 19 ++++++++++++++ 7 files changed, 165 insertions(+) create mode 100644 tests/test_audio.py create mode 100644 tests/test_embedding.py create mode 100644 tests/test_files.py create mode 100644 tests/test_images.py create mode 100644 tests/test_models.py create mode 100644 tests/test_moderation.py diff --git a/.gitignore b/.gitignore index 27aad61..670d8dc 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist venv +# image files *.png *.jpg *.jpeg @@ -14,3 +15,11 @@ venv *.svg *.ico *.pdf +# audio files +*.m4a +*.mp3 +*.mp4 +*.wav +*.flac +*.aac + diff --git a/tests/test_audio.py b/tests/test_audio.py new file mode 100644 index 0000000..cad5a6a --- /dev/null +++ b/tests/test_audio.py @@ -0,0 +1,17 @@ +from handyllm import OpenAIAPI + +from dotenv import load_dotenv, find_dotenv +# load env parameters from file named .env +# API key is read from environment variable OPENAI_API_KEY +# organization is read from environment variable OPENAI_ORGANIZATION +load_dotenv(find_dotenv()) + +file_path = 'hello.m4a' + +with open(file_path, "rb") as file_bin: + response = OpenAIAPI.audio_transcriptions( + file=file_bin, + model='whisper-1', + # timeout=10, + ) +print(response['text']) diff --git a/tests/test_embedding.py b/tests/test_embedding.py new file mode 100644 index 0000000..666db22 --- /dev/null +++ b/tests/test_embedding.py @@ -0,0 +1,19 @@ +from handyllm import OpenAIAPI + +import json +from dotenv import load_dotenv, find_dotenv +# load env parameters from file named .env +# API key is read from environment variable OPENAI_API_KEY +# organization is read from environment variable OPENAI_ORGANIZATION +load_dotenv(find_dotenv()) + +## or you can set these parameters in code +# OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +# OpenAIAPI.organization = None + +response = OpenAIAPI.embeddings( + model="text-embedding-ada-002", + input="I enjoy walking with my cute dog", + timeout=10, +) +print(json.dumps(response, indent=2)) diff --git a/tests/test_files.py b/tests/test_files.py new file mode 100644 index 0000000..defb9d9 --- /dev/null +++ b/tests/test_files.py @@ -0,0 +1,21 @@ +from handyllm import OpenAIAPI +from handyllm import utils + +import json +from dotenv import load_dotenv, find_dotenv +# load env parameters from file named .env +# API key is read from environment variable OPENAI_API_KEY +# organization is read from environment variable OPENAI_ORGANIZATION +load_dotenv(find_dotenv()) + +## list all files +response = OpenAIAPI.files_list() +print(json.dumps(response, indent=2)) + +## upload a file +with open("mydata.jsonl", "rb") as file_bin: + response = OpenAIAPI.files_upload( + file=file_bin, + purpose='fine-tune' + ) +print(json.dumps(response, indent=2)) diff --git a/tests/test_images.py b/tests/test_images.py new file mode 100644 index 0000000..cc5e190 --- /dev/null +++ b/tests/test_images.py @@ -0,0 +1,55 @@ +from handyllm import OpenAIAPI +from handyllm import utils + +import json +from dotenv import load_dotenv, find_dotenv +# load env parameters from file named .env +# API key is read from environment variable OPENAI_API_KEY +# organization is read from environment variable OPENAI_ORGANIZATION +load_dotenv(find_dotenv()) + +## or you can set these parameters in code +# OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +# OpenAIAPI.organization = None + +response = OpenAIAPI.images_generations( + prompt="A very cool panda", + n=1, + size="256x256", + # timeout=10, +) +print(json.dumps(response, indent=2)) +download_url = response['data'][0]['url'] +file_path = utils.download_binary(download_url) +print(f"generated image: {file_path}") + + +mask_file_path = "mask.png" +with open(file_path, "rb") as file_bin: + with open(mask_file_path, "rb") as mask_file_bin: + response = OpenAIAPI.images_edits( + image=file_bin, + mask=mask_file_bin, + prompt="A cute panda wearing a beret", + n=1, + size="512x512", + # timeout=10, + ) +print(json.dumps(response, indent=2)) +download_url = response['data'][0]['url'] +file_path = utils.download_binary(download_url) +print(f"edited image: {file_path}") + + +with open(file_path, "rb") as file_bin: + response = OpenAIAPI.images_variations( + image=file_bin, + n=1, + size="512x512", + # timeout=10, + ) +print(json.dumps(response, indent=2)) +download_url = response['data'][0]['url'] +file_path = utils.download_binary(download_url) +print(f"varied image: {file_path}") + diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..f696679 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,25 @@ +from handyllm import OpenAIAPI + +import json +from dotenv import load_dotenv, find_dotenv +# load env parameters from file named .env +# API key is read from environment variable OPENAI_API_KEY +# organization is read from environment variable OPENAI_ORGANIZATION +load_dotenv(find_dotenv()) + +## or you can set these parameters in code +# OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +# OpenAIAPI.organization = None + +## get all models +response = OpenAIAPI.models_list( + timeout=10, +) +print(json.dumps(response, indent=2)) + +## retrieve a specific model +response = OpenAIAPI.models_retrieve( + timeout=10, + model="text-embedding-ada-002", +) +print(json.dumps(response, indent=2)) diff --git a/tests/test_moderation.py b/tests/test_moderation.py new file mode 100644 index 0000000..cad2663 --- /dev/null +++ b/tests/test_moderation.py @@ -0,0 +1,19 @@ +from handyllm import OpenAIAPI + +import json +from dotenv import load_dotenv, find_dotenv +# load env parameters from file named .env +# API key is read from environment variable OPENAI_API_KEY +# organization is read from environment variable OPENAI_ORGANIZATION +load_dotenv(find_dotenv()) + +## or you can set these parameters in code +# OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' +# OpenAIAPI.organization = None + +response = OpenAIAPI.moderations( + model="text-moderation-latest", + input="I want to kill them.", + timeout=10, +) +print(json.dumps(response, indent=2)) From e330e4df954df8a161396d53328eb581d861e9d6 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 12:14:32 +0800 Subject: [PATCH 13/16] Support `api_key` and `organization` arguments in API calls --- src/handyllm/openai_api.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/handyllm/openai_api.py b/src/handyllm/openai_api.py index ec476d8..9da69ec 100644 --- a/src/handyllm/openai_api.py +++ b/src/handyllm/openai_api.py @@ -116,13 +116,25 @@ def stream_completions(response): @staticmethod def api_request_endpoint(request_url, endpoint_manager=None, **kwargs): + specified_api_key = kwargs.pop('api_key', None) + specified_organization = kwargs.pop('organization', None) if endpoint_manager != None: # 每次换服务器和key要同时换,保证服务器和key是对应的 base_url, api_key, organization = endpoint_manager.get_endpoint() else: base_url = OpenAIAPI.base_url - api_key = OpenAIAPI.api_key if OpenAIAPI.api_key is not None else OpenAIAPI._get_key_from_env() - organization = OpenAIAPI.organization if OpenAIAPI.organization is not None else OpenAIAPI._get_organization_from_env() + if specified_api_key is not None: + api_key = specified_api_key + elif OpenAIAPI.api_key is not None: + api_key = OpenAIAPI.api_key + else: + api_key = OpenAIAPI._get_key_from_env() + if specified_organization is not None: + organization = specified_organization + elif OpenAIAPI.organization is not None: + organization = OpenAIAPI.organization + else: + organization = OpenAIAPI._get_organization_from_env() url = base_url + request_url return OpenAIAPI._api_request(url, api_key, organization=organization, **kwargs) From 709b9bdd0b97018e76584df0917988d638e9f66d Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 12:16:08 +0800 Subject: [PATCH 14/16] minor changes --- .gitignore | 1 + tests/{test_api.py => test_completions.py} | 0 2 files changed, 1 insertion(+) rename tests/{test_api.py => test_completions.py} (100%) diff --git a/.gitignore b/.gitignore index 670d8dc..99c6083 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ dist *.env venv +tmp* # image files *.png diff --git a/tests/test_api.py b/tests/test_completions.py similarity index 100% rename from tests/test_api.py rename to tests/test_completions.py From 3fe2feafcb852c5c3576667a1261cf8ce3b0c301 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 12:42:49 +0800 Subject: [PATCH 15/16] Update README --- README.md | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b3c847..13b7822 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ Example scripts are placed in [tests](./tests) folder. ## OpenAI API Request -This toolkit uses HTTP API request instead of OpenAI's official python package to support client-side `timeout` control: +### Timeout control + +This toolkit supports client-side `timeout` control, which OpenAI's official python package does not support yet: ```python from handyllm import OpenAIAPI @@ -44,6 +46,8 @@ response = OpenAIAPI.chat( print(response['choices'][0]['message']['content']) ``` +### Authorization + API key and organization will be loaded using the environment variable `OPENAI_API_KEY` and `OPENAI_ORGANIZATION`, or you can set manually: ```python @@ -51,7 +55,11 @@ OpenAIAPI.api_key = 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' OpenAIAPI.organization = '......' # default: None ``` -Stream response of `chat`/`completions` can be achieved using `steam` parameter: +Or, you can pass `api_key` and `organization` parameters in each API call. + +### Stream response + +Stream response of `chat`/`completions`/`finetunes_list_events` can be achieved using `steam` parameter: ```python response = OpenAIAPI.chat( @@ -71,10 +79,40 @@ for text in OpenAIAPI.stream_chat(response): # print(chunk['choices'][0]['delta']['content'], end='') ``` +### Supported APIs + +- chat +- completions +- edits +- embeddings +- models_list +- models_retrieve +- moderations +- images_generations +- images_edits +- images_variations +- audio_transcriptions +- audtio_translations +- files_list +- files_upload +- files_delete +- files_retrieve +- files_retrieve_content +- finetunes_create +- finetunes_list +- finetunes_retrieve +- finetunes_cancel +- finetunes_list_events +- finetunes_delete_model + +Please refer to [OpenAI official API reference](https://platform.openai.com/docs/api-reference) for details. + ## Prompt +### Prompt Conversion + `PromptConverter` can convert this text file `prompt.txt` into a structured prompt for chat API calls: ``` @@ -109,7 +147,7 @@ chat = converter.rawfile2chat('prompt.txt') new_chat = converter.chat_replace_variables(chat, {r'%misc%': 'Note: do not use any bad word.'}) ``` - +### Substitute `PromptConverter` can also substitute placeholder variables like `%output_format%` stored in text files to make multiple prompts modular. A substitute map `substitute.txt` looks like this: From 98acf0d61748eed4783f767cf681434972afa8a1 Mon Sep 17 00:00:00 2001 From: Atomie CHEN Date: Fri, 28 Jul 2023 12:43:13 +0800 Subject: [PATCH 16/16] Bump version to 0.3.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ccbefed..36a4c29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "HandyLLM" -version = "0.2.1" +version = "0.3.0" authors = [ { name="Atomie CHEN", email="atomic_cwh@163.com" }, ]