From 69408f67bff67d706520f464cdff1c3d55157a24 Mon Sep 17 00:00:00 2001 From: hallacy Date: Thu, 11 Feb 2021 11:00:56 -0800 Subject: [PATCH 01/10] Add support for modifying files and file_set support (#4) * Add support for modifying files and file_set support * Bump version * Updated file sets to use name everywhere * Bump to 0.4.0 --- openai/api_resources/__init__.py | 1 + openai/api_resources/file.py | 4 +-- openai/api_resources/file_set.py | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 openai/api_resources/file_set.py diff --git a/openai/api_resources/__init__.py b/openai/api_resources/__init__.py index 2a77f47097..e2c4e69cc0 100644 --- a/openai/api_resources/__init__.py +++ b/openai/api_resources/__init__.py @@ -4,6 +4,7 @@ from openai.api_resources.error_object import ErrorObject from openai.api_resources.event import Event from openai.api_resources.file import File +from openai.api_resources.file_set import FileSet from openai.api_resources.higherlevel import HigherLevel from openai.api_resources.plan import Plan from openai.api_resources.run import Run diff --git a/openai/api_resources/file.py b/openai/api_resources/file.py index 3411a4a3bb..4d5eb1830f 100644 --- a/openai/api_resources/file.py +++ b/openai/api_resources/file.py @@ -7,8 +7,6 @@ import openai from openai import api_requestor, util from openai.api_resources.abstract import ( - APIResource, - CreateableAPIResource, DeletableAPIResource, ListableAPIResource, UpdateableAPIResource, @@ -16,7 +14,7 @@ from openai.util import log_info -class File(ListableAPIResource): +class File(ListableAPIResource, UpdateableAPIResource, DeletableAPIResource): OBJECT_NAME = "file" @classmethod diff --git a/openai/api_resources/file_set.py b/openai/api_resources/file_set.py new file mode 100644 index 0000000000..88438669d3 --- /dev/null +++ b/openai/api_resources/file_set.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, division, print_function + +from openai import error, six, util +from openai.six.moves.urllib.parse import quote_plus +from openai.api_resources.abstract import ( + CreateableAPIResource, + DeletableAPIResource, + ListableAPIResource, +) + + +class FileSet( + CreateableAPIResource, + ListableAPIResource, + DeletableAPIResource, +): + OBJECT_NAME = "file_set" + + def __init__(self, name, *args, **kwargs): + self.name = name + super().__init__(*args, name=self.name, **kwargs) + + @classmethod + def retrieve(cls, name, api_key=None, request_id=None, **params): + instance = cls(name, api_key, **params) + instance.refresh(request_id=request_id) + return instance + + def instance_url(self): + # file_sets are normally called by their name, not their id + name = self.get("name") + + if not isinstance(name, six.string_types): + raise error.InvalidRequestError( + "Could not determine which URL to request: %s instance " + "has invalid ID: %r, %s. ID should be of type `str` (or" + " `unicode`)" % (type(self).__name__, name, type(name)), + "name", + ) + + id = util.utf8(name) + base = self.class_url() + extn = quote_plus(id) + return "%s/%s" % (base, extn) From 67e2468cf3686a49cb0b358c792394acddd17e85 Mon Sep 17 00:00:00 2001 From: hallacy Date: Tue, 16 Feb 2021 12:28:38 -0800 Subject: [PATCH 02/10] Added cli support for file and fileset (#5) * Added cli support for file and fileset * Back to .4.0 * Typo * Add some basic tests to confirm that array stuff works * Added a test for multiple prompts. --- openai/api_requestor.py | 4 +- openai/cli.py | 120 +++++++++++++++++++++++++++++ openai/multipart_data_generator.py | 9 +++ openai/tests/test_endpoints.py | 44 +++++++++++ 4 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 openai/tests/test_endpoints.py diff --git a/openai/api_requestor.py b/openai/api_requestor.py index 3b5e0c1c92..186bdd284a 100644 --- a/openai/api_requestor.py +++ b/openai/api_requestor.py @@ -45,11 +45,11 @@ def _api_encode(data): elif isinstance(value, list) or isinstance(value, tuple): for i, sv in enumerate(value): if isinstance(sv, dict): - subdict = _encode_nested_dict("%s[]" % (key,), sv) + subdict = _encode_nested_dict("%s[%d]" % (key, i), sv) for k, v in _api_encode(subdict): yield (k, v) else: - yield ("%s[]" % (key,), util.utf8(sv)) + yield ("%s[%d]" % (key, i), util.utf8(sv)) elif isinstance(value, dict): subdict = _encode_nested_dict(key, value) for subkey, subvalue in _api_encode(subdict): diff --git a/openai/cli.py b/openai/cli.py index ba29fe6bc1..f3ebfd749f 100644 --- a/openai/cli.py +++ b/openai/cli.py @@ -239,6 +239,63 @@ def cancel(cls, args): print(resp) +class File: + @classmethod + def create(cls, args): + resp = openai.File.create( + file=open(args.file), + purpose=args.purpose, + file_set_names=args.file_set_names, + ) + print(resp) + + @classmethod + def get(cls, args): + resp = openai.File.retrieve(id=args.id) + print(resp) + + @classmethod + def update(cls, args): + resp = openai.File.modify(sid=args.id, file_set_names=args.file_set_names) + print(resp) + + @classmethod + def delete(cls, args): + file = openai.File(id=args.id).delete() + print(file) + + @classmethod + def list(cls, args): + file = openai.File.list() + print(file) + + +class FileSet: + @classmethod + def create(cls, args): + resp = openai.FileSet.create( + name=args.name, + file_ids=args.file_ids, + ) + print(resp) + + @classmethod + def get(cls, args): + # Need to add query support + resp = openai.FileSet.retrieve(name=args.name) + print(resp) + + @classmethod + def delete(cls, args): + file = openai.FileSet(name=args.name).delete() + print(file) + + @classmethod + def list(cls, args): + file = openai.FileSet.list() + print(file) + + def register(parser): # Engine management subparsers = parser.add_subparsers(help="All API subcommands") @@ -450,3 +507,66 @@ def help(args): sub = subparsers.add_parser("fine_tunes.cancel") sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") sub.set_defaults(func=FineTuneCLI.cancel) + # Files + sub = subparsers.add_parser("files.create") + + sub.add_argument( + "-f", + "--file", + required=True, + help="File to upload", + ) + sub.add_argument( + "-p", + "--purpose", + help="Why are you uploading this file? (see https://beta.openai.com/docs/api-reference/ for purposes)", + required=True, + ) + sub.add_argument( + "--file_set_names", + nargs="*", + help="What sets of files do you want to add this ?", + ) + sub.set_defaults(func=File.create) + + sub = subparsers.add_parser("files.update") + sub.add_argument("-i", "--id", required=True, help="The files ID") + sub.add_argument( + "--file_set_names", + nargs="*", + help="What sets of files do you want to add this to?", + ) + sub.set_defaults(func=File.update) + + sub = subparsers.add_parser("files.get") + sub.add_argument("-i", "--id", required=True, help="The files ID") + sub.set_defaults(func=File.get) + + sub = subparsers.add_parser("files.delete") + sub.add_argument("-i", "--id", required=True, help="The files ID") + sub.set_defaults(func=File.delete) + + sub = subparsers.add_parser("files.list") + sub.set_defaults(func=File.list) + + # FileSets + sub = subparsers.add_parser("filesets.create") + + sub.add_argument("-n", "--name", required=True, help="The file_set name") + sub.add_argument( + "--file_ids", + nargs="*", + help="What files do you want to add this to?", + ) + sub.set_defaults(func=FileSet.create) + + sub = subparsers.add_parser("filesets.get") + sub.add_argument("-n", "--name", required=True, help="The file_set name") + sub.set_defaults(func=FileSet.get) + + sub = subparsers.add_parser("filesets.delete") + sub.add_argument("-n", "--name", required=True, help="The file_set name") + sub.set_defaults(func=FileSet.delete) + + sub = subparsers.add_parser("filesets.list") + sub.set_defaults(func=FileSet.list) diff --git a/openai/multipart_data_generator.py b/openai/multipart_data_generator.py index 8b29a2b551..93a683ee7b 100644 --- a/openai/multipart_data_generator.py +++ b/openai/multipart_data_generator.py @@ -4,6 +4,7 @@ import io import openai +import re class MultipartDataGenerator(object): @@ -13,11 +14,19 @@ def __init__(self, chunk_size=1028): self.boundary = self._initialize_boundary() self.chunk_size = chunk_size + def _remove_array_element(self, input_string): + match = re.match(r"^(.*)\[.*\]$", input_string) + return match[1] if match else input_string + def add_params(self, params): # Flatten parameters first params = dict(openai.api_requestor._api_encode(params)) for key, value in openai.six.iteritems(params): + + # strip array elements if present from key + key = self._remove_array_element(key) + if value is None: continue diff --git a/openai/tests/test_endpoints.py b/openai/tests/test_endpoints.py new file mode 100644 index 0000000000..3e9cbe5d0d --- /dev/null +++ b/openai/tests/test_endpoints.py @@ -0,0 +1,44 @@ +import openai +import io +import json +import uuid + +### FILE TESTS +def _upload_default_file(): + return openai.File.create( + file=io.StringIO(json.dumps({"text": "test file data"})), + purpose="search", + file_set_names=["test1", "test2"], + ) + + +def test_file_upload_with_multiple_file_sets(): + result = _upload_default_file() + assert set(result.file_sets) == set(["test2", "test1"]) + assert "id" in result + + +### FILE SET TESTS +def test_file_sets_upload(): + file1 = _upload_default_file() + file2 = _upload_default_file() + + result = openai.FileSet.create( + name=f"ci_{uuid.uuid4().hex}", + file_ids=[file1.id, file2.id], + ) + assert set(result.files) == set([file1.id, file2.id]) + assert "id" in result + + +### COMPLETION TESTS +def test_completions(): + result = openai.Completion.create(prompt="This was a test", n=5, engine="davinci") + assert len(result.choices) == 5 + + +def test_completions_multiple_prompts(): + result = openai.Completion.create( + prompt=["This was a test", "This was another test"], n=5, engine="davinci" + ) + assert len(result.choices) == 10 \ No newline at end of file From 10e995bbddddfccca43c431b4994d6855f406744 Mon Sep 17 00:00:00 2001 From: hallacy Date: Tue, 16 Feb 2021 14:53:48 -0800 Subject: [PATCH 03/10] refactor retriever endpoint (#6) --- openai/api_resources/higherlevel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openai/api_resources/higherlevel.py b/openai/api_resources/higherlevel.py index da14918670..599e3f4ae0 100644 --- a/openai/api_resources/higherlevel.py +++ b/openai/api_resources/higherlevel.py @@ -13,5 +13,5 @@ def classification(self, **params): def answer(self, **params): return self.request("post", self.get_url("answers"), params) - def retriever_file_set_search(self, **params): - return self.request("post", self.get_url("retriever_file_set_search"), params) + def file_set_search(self, **params): + return self.request("post", self.get_url("file_set_search"), params) From 8ef54f5069c68113bda1e1536dcdaf7f64d86839 Mon Sep 17 00:00:00 2001 From: hallacy Date: Tue, 16 Feb 2021 15:54:24 -0800 Subject: [PATCH 04/10] Make higherlevel have class methods so you can call with openai.HigherLevel.answer (#7) * refactor retriever endpoint * Actually just make everything a classmethod so you can call it like openai.HigherLevel --- openai/api_resources/higherlevel.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openai/api_resources/higherlevel.py b/openai/api_resources/higherlevel.py index 599e3f4ae0..83c630ceb0 100644 --- a/openai/api_resources/higherlevel.py +++ b/openai/api_resources/higherlevel.py @@ -4,14 +4,21 @@ class HigherLevel(EngineAPIResource): api_prefix = "higherlevel" + @classmethod def get_url(self, base): return "/%s/%s" % (self.api_prefix, base) - def classification(self, **params): - return self.request("post", self.get_url("classifications"), params) + @classmethod + def classification(cls, **params): + instance = cls() + return instance.request("post", cls.get_url("classifications"), params) - def answer(self, **params): - return self.request("post", self.get_url("answers"), params) + @classmethod + def answer(cls, **params): + instance = cls() + return instance.request("post", cls.get_url("answers"), params) - def file_set_search(self, **params): - return self.request("post", self.get_url("file_set_search"), params) + @classmethod + def file_set_search(cls, **params): + instance = cls() + return instance.request("post", cls.get_url("file_set_search"), params) From 92d22200ccad6ff7b457db524b4634f7f8138c9c Mon Sep 17 00:00:00 2001 From: hallacy Date: Wed, 17 Feb 2021 12:41:27 -0800 Subject: [PATCH 05/10] Rename file_sets to collections everywhere (#8) * Rename file_sets to collections everywhere --- openai/api_resources/__init__.py | 2 +- .../{file_set.py => collection.py} | 6 +-- openai/api_resources/higherlevel.py | 4 +- openai/cli.py | 44 +++++++++---------- openai/tests/test_endpoints.py | 14 +++--- 5 files changed, 35 insertions(+), 35 deletions(-) rename openai/api_resources/{file_set.py => collection.py} (91%) diff --git a/openai/api_resources/__init__.py b/openai/api_resources/__init__.py index e2c4e69cc0..fa8e7b213d 100644 --- a/openai/api_resources/__init__.py +++ b/openai/api_resources/__init__.py @@ -4,7 +4,7 @@ from openai.api_resources.error_object import ErrorObject from openai.api_resources.event import Event from openai.api_resources.file import File -from openai.api_resources.file_set import FileSet +from openai.api_resources.collection import Collection from openai.api_resources.higherlevel import HigherLevel from openai.api_resources.plan import Plan from openai.api_resources.run import Run diff --git a/openai/api_resources/file_set.py b/openai/api_resources/collection.py similarity index 91% rename from openai/api_resources/file_set.py rename to openai/api_resources/collection.py index 88438669d3..bb18fe983b 100644 --- a/openai/api_resources/file_set.py +++ b/openai/api_resources/collection.py @@ -9,12 +9,12 @@ ) -class FileSet( +class Collection( CreateableAPIResource, ListableAPIResource, DeletableAPIResource, ): - OBJECT_NAME = "file_set" + OBJECT_NAME = "collection" def __init__(self, name, *args, **kwargs): self.name = name @@ -27,7 +27,7 @@ def retrieve(cls, name, api_key=None, request_id=None, **params): return instance def instance_url(self): - # file_sets are normally called by their name, not their id + # collections are normally called by their name, not their id name = self.get("name") if not isinstance(name, six.string_types): diff --git a/openai/api_resources/higherlevel.py b/openai/api_resources/higherlevel.py index 83c630ceb0..0ef7579924 100644 --- a/openai/api_resources/higherlevel.py +++ b/openai/api_resources/higherlevel.py @@ -19,6 +19,6 @@ def answer(cls, **params): return instance.request("post", cls.get_url("answers"), params) @classmethod - def file_set_search(cls, **params): + def collection_search(cls, **params): instance = cls() - return instance.request("post", cls.get_url("file_set_search"), params) + return instance.request("post", cls.get_url("collection_search"), params) diff --git a/openai/cli.py b/openai/cli.py index f3ebfd749f..9cf2e7b736 100644 --- a/openai/cli.py +++ b/openai/cli.py @@ -256,7 +256,7 @@ def get(cls, args): @classmethod def update(cls, args): - resp = openai.File.modify(sid=args.id, file_set_names=args.file_set_names) + resp = openai.File.modify(sid=args.id, collection_names=args.collection_names) print(resp) @classmethod @@ -270,10 +270,10 @@ def list(cls, args): print(file) -class FileSet: +class Collection: @classmethod def create(cls, args): - resp = openai.FileSet.create( + resp = openai.Collection.create( name=args.name, file_ids=args.file_ids, ) @@ -282,17 +282,17 @@ def create(cls, args): @classmethod def get(cls, args): # Need to add query support - resp = openai.FileSet.retrieve(name=args.name) + resp = openai.Collection.retrieve(name=args.name) print(resp) @classmethod def delete(cls, args): - file = openai.FileSet(name=args.name).delete() + file = openai.Collection(name=args.name).delete() print(file) @classmethod def list(cls, args): - file = openai.FileSet.list() + file = openai.Collection.list() print(file) @@ -523,18 +523,18 @@ def help(args): required=True, ) sub.add_argument( - "--file_set_names", + "--collection_names", nargs="*", - help="What sets of files do you want to add this ?", + help="What collections do you want to add this to?", ) sub.set_defaults(func=File.create) sub = subparsers.add_parser("files.update") sub.add_argument("-i", "--id", required=True, help="The files ID") sub.add_argument( - "--file_set_names", + "--collection_names", nargs="*", - help="What sets of files do you want to add this to?", + help="What collections do you want to add this to?", ) sub.set_defaults(func=File.update) @@ -549,24 +549,24 @@ def help(args): sub = subparsers.add_parser("files.list") sub.set_defaults(func=File.list) - # FileSets - sub = subparsers.add_parser("filesets.create") + # Collections + sub = subparsers.add_parser("collections.create") - sub.add_argument("-n", "--name", required=True, help="The file_set name") + sub.add_argument("-n", "--name", required=True, help="The collection name") sub.add_argument( "--file_ids", nargs="*", help="What files do you want to add this to?", ) - sub.set_defaults(func=FileSet.create) + sub.set_defaults(func=Collection.create) - sub = subparsers.add_parser("filesets.get") - sub.add_argument("-n", "--name", required=True, help="The file_set name") - sub.set_defaults(func=FileSet.get) + sub = subparsers.add_parser("collections.get") + sub.add_argument("-n", "--name", required=True, help="The collection name") + sub.set_defaults(func=Collection.get) - sub = subparsers.add_parser("filesets.delete") - sub.add_argument("-n", "--name", required=True, help="The file_set name") - sub.set_defaults(func=FileSet.delete) + sub = subparsers.add_parser("collections.delete") + sub.add_argument("-n", "--name", required=True, help="The collection name") + sub.set_defaults(func=Collection.delete) - sub = subparsers.add_parser("filesets.list") - sub.set_defaults(func=FileSet.list) + sub = subparsers.add_parser("collections.list") + sub.set_defaults(func=Collection.list) diff --git a/openai/tests/test_endpoints.py b/openai/tests/test_endpoints.py index 3e9cbe5d0d..45a83b5620 100644 --- a/openai/tests/test_endpoints.py +++ b/openai/tests/test_endpoints.py @@ -8,22 +8,22 @@ def _upload_default_file(): return openai.File.create( file=io.StringIO(json.dumps({"text": "test file data"})), purpose="search", - file_set_names=["test1", "test2"], + collection_names=["test1", "test2"], ) -def test_file_upload_with_multiple_file_sets(): +def test_file_upload_with_multiple_collections(): result = _upload_default_file() - assert set(result.file_sets) == set(["test2", "test1"]) + assert set(result.collections) == set(["test2", "test1"]) assert "id" in result -### FILE SET TESTS -def test_file_sets_upload(): +### COLLECTION TESTS +def test_collections_upload(): file1 = _upload_default_file() file2 = _upload_default_file() - result = openai.FileSet.create( + result = openai.Collection.create( name=f"ci_{uuid.uuid4().hex}", file_ids=[file1.id, file2.id], ) @@ -41,4 +41,4 @@ def test_completions_multiple_prompts(): result = openai.Completion.create( prompt=["This was a test", "This was another test"], n=5, engine="davinci" ) - assert len(result.choices) == 10 \ No newline at end of file + assert len(result.choices) == 10 From 13a4c4172cc3cc571eb5c2dbbee7162c9fcccb5b Mon Sep 17 00:00:00 2001 From: hallacy Date: Wed, 3 Mar 2021 19:19:18 -0800 Subject: [PATCH 06/10] Remove collections (#10) --- openai/api_resources/__init__.py | 1 - openai/api_resources/collection.py | 44 ------------- openai/api_resources/file.py | 2 +- openai/cli.py | 99 +++++++++++++++--------------- openai/tests/test_endpoints.py | 24 +------- 5 files changed, 55 insertions(+), 115 deletions(-) delete mode 100644 openai/api_resources/collection.py diff --git a/openai/api_resources/__init__.py b/openai/api_resources/__init__.py index fa8e7b213d..2a77f47097 100644 --- a/openai/api_resources/__init__.py +++ b/openai/api_resources/__init__.py @@ -4,7 +4,6 @@ from openai.api_resources.error_object import ErrorObject from openai.api_resources.event import Event from openai.api_resources.file import File -from openai.api_resources.collection import Collection from openai.api_resources.higherlevel import HigherLevel from openai.api_resources.plan import Plan from openai.api_resources.run import Run diff --git a/openai/api_resources/collection.py b/openai/api_resources/collection.py deleted file mode 100644 index bb18fe983b..0000000000 --- a/openai/api_resources/collection.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import absolute_import, division, print_function - -from openai import error, six, util -from openai.six.moves.urllib.parse import quote_plus -from openai.api_resources.abstract import ( - CreateableAPIResource, - DeletableAPIResource, - ListableAPIResource, -) - - -class Collection( - CreateableAPIResource, - ListableAPIResource, - DeletableAPIResource, -): - OBJECT_NAME = "collection" - - def __init__(self, name, *args, **kwargs): - self.name = name - super().__init__(*args, name=self.name, **kwargs) - - @classmethod - def retrieve(cls, name, api_key=None, request_id=None, **params): - instance = cls(name, api_key, **params) - instance.refresh(request_id=request_id) - return instance - - def instance_url(self): - # collections are normally called by their name, not their id - name = self.get("name") - - if not isinstance(name, six.string_types): - raise error.InvalidRequestError( - "Could not determine which URL to request: %s instance " - "has invalid ID: %r, %s. ID should be of type `str` (or" - " `unicode`)" % (type(self).__name__, name, type(name)), - "name", - ) - - id = util.utf8(name) - base = self.class_url() - extn = quote_plus(id) - return "%s/%s" % (base, extn) diff --git a/openai/api_resources/file.py b/openai/api_resources/file.py index 4d5eb1830f..dcdb9b94df 100644 --- a/openai/api_resources/file.py +++ b/openai/api_resources/file.py @@ -14,7 +14,7 @@ from openai.util import log_info -class File(ListableAPIResource, UpdateableAPIResource, DeletableAPIResource): +class File(ListableAPIResource, DeletableAPIResource): OBJECT_NAME = "file" @classmethod diff --git a/openai/cli.py b/openai/cli.py index 9cf2e7b736..d2443892b8 100644 --- a/openai/cli.py +++ b/openai/cli.py @@ -245,7 +245,6 @@ def create(cls, args): resp = openai.File.create( file=open(args.file), purpose=args.purpose, - file_set_names=args.file_set_names, ) print(resp) @@ -254,11 +253,6 @@ def get(cls, args): resp = openai.File.retrieve(id=args.id) print(resp) - @classmethod - def update(cls, args): - resp = openai.File.modify(sid=args.id, collection_names=args.collection_names) - print(resp) - @classmethod def delete(cls, args): file = openai.File(id=args.id).delete() @@ -270,30 +264,48 @@ def list(cls, args): print(file) -class Collection: +class FineTuneCLI: + @classmethod + def list(cls, args): + resp = openai.FineTune.list() + print(resp) + @classmethod def create(cls, args): - resp = openai.Collection.create( - name=args.name, - file_ids=args.file_ids, - ) + create_args = { + "train_file": args.train_file, + } + if args.test_file: + create_args["test_file"] = args.test_file + if args.base_model: + create_args["base_model"] = args.base_model + if args.hparams: + try: + hparams = json.loads(args.hparams) + except json.decoder.JSONDecodeError: + sys.stderr.write( + "--hparams must be JSON decodable and match the hyperparameter arguments of the API" + ) + sys.exit(1) + create_args.update(hparams) + + resp = openai.FineTune.create(**create_args) print(resp) @classmethod def get(cls, args): - # Need to add query support - resp = openai.Collection.retrieve(name=args.name) + resp = openai.FineTune.retrieve(id=args.id) print(resp) @classmethod - def delete(cls, args): - file = openai.Collection(name=args.name).delete() - print(file) + def events(cls, args): + resp = openai.FineTune.list_events(id=args.id) + print(resp) @classmethod - def list(cls, args): - file = openai.Collection.list() - print(file) + def cancel(cls, args): + resp = openai.FineTune.cancel(id=args.id) + print(resp) def register(parser): @@ -522,22 +534,8 @@ def help(args): help="Why are you uploading this file? (see https://beta.openai.com/docs/api-reference/ for purposes)", required=True, ) - sub.add_argument( - "--collection_names", - nargs="*", - help="What collections do you want to add this to?", - ) sub.set_defaults(func=File.create) - sub = subparsers.add_parser("files.update") - sub.add_argument("-i", "--id", required=True, help="The files ID") - sub.add_argument( - "--collection_names", - nargs="*", - help="What collections do you want to add this to?", - ) - sub.set_defaults(func=File.update) - sub = subparsers.add_parser("files.get") sub.add_argument("-i", "--id", required=True, help="The files ID") sub.set_defaults(func=File.get) @@ -549,24 +547,29 @@ def help(args): sub = subparsers.add_parser("files.list") sub.set_defaults(func=File.list) - # Collections - sub = subparsers.add_parser("collections.create") + # Finetune + sub = subparsers.add_parser("fine_tunes.list") + sub.set_defaults(func=FineTuneCLI.list) - sub.add_argument("-n", "--name", required=True, help="The collection name") + sub = subparsers.add_parser("fine_tunes.create") + sub.add_argument("-t", "--train_file", required=True, help="File to train") + sub.add_argument("--test_file", help="File to test") sub.add_argument( - "--file_ids", - nargs="*", - help="What files do you want to add this to?", + "-b", + "--base_model", + help="The model name to start the run from", ) - sub.set_defaults(func=Collection.create) + sub.add_argument("-p", "--hparams", help="Hyperparameter JSON") + sub.set_defaults(func=FineTuneCLI.create) - sub = subparsers.add_parser("collections.get") - sub.add_argument("-n", "--name", required=True, help="The collection name") - sub.set_defaults(func=Collection.get) + sub = subparsers.add_parser("fine_tunes.get") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTuneCLI.get) - sub = subparsers.add_parser("collections.delete") - sub.add_argument("-n", "--name", required=True, help="The collection name") - sub.set_defaults(func=Collection.delete) + sub = subparsers.add_parser("fine_tunes.events") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTuneCLI.events) - sub = subparsers.add_parser("collections.list") - sub.set_defaults(func=Collection.list) + sub = subparsers.add_parser("fine_tunes.cancel") + sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") + sub.set_defaults(func=FineTuneCLI.cancel) diff --git a/openai/tests/test_endpoints.py b/openai/tests/test_endpoints.py index 45a83b5620..25cc1cf764 100644 --- a/openai/tests/test_endpoints.py +++ b/openai/tests/test_endpoints.py @@ -4,30 +4,12 @@ import uuid ### FILE TESTS -def _upload_default_file(): - return openai.File.create( +def test_file_upload(): + result = openai.File.create( file=io.StringIO(json.dumps({"text": "test file data"})), purpose="search", - collection_names=["test1", "test2"], ) - - -def test_file_upload_with_multiple_collections(): - result = _upload_default_file() - assert set(result.collections) == set(["test2", "test1"]) - assert "id" in result - - -### COLLECTION TESTS -def test_collections_upload(): - file1 = _upload_default_file() - file2 = _upload_default_file() - - result = openai.Collection.create( - name=f"ci_{uuid.uuid4().hex}", - file_ids=[file1.id, file2.id], - ) - assert set(result.files) == set([file1.id, file2.id]) + assert result.purpose == "search" assert "id" in result From 81f141b7d331820dfbfceeb5073c4d88b590ff08 Mon Sep 17 00:00:00 2001 From: hallacy Date: Thu, 11 Mar 2021 16:54:52 -0800 Subject: [PATCH 07/10] Higherlevel endpoints now point to /v1 (#11) * Higherlevel endpoints now point to v1 * new line --- openai/api_resources/higherlevel.py | 7 +------ openai/version.py | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/openai/api_resources/higherlevel.py b/openai/api_resources/higherlevel.py index 0ef7579924..6b71cb46f0 100644 --- a/openai/api_resources/higherlevel.py +++ b/openai/api_resources/higherlevel.py @@ -2,7 +2,7 @@ class HigherLevel(EngineAPIResource): - api_prefix = "higherlevel" + api_prefix = "v1" @classmethod def get_url(self, base): @@ -17,8 +17,3 @@ def classification(cls, **params): def answer(cls, **params): instance = cls() return instance.request("post", cls.get_url("answers"), params) - - @classmethod - def collection_search(cls, **params): - instance = cls() - return instance.request("post", cls.get_url("collection_search"), params) diff --git a/openai/version.py b/openai/version.py index 6cc293ea98..5a2867da19 100644 --- a/openai/version.py +++ b/openai/version.py @@ -1 +1 @@ -VERSION = "0.4.0" +VERSION = "0.6.0" From 4f47f0affa644b82968378619e7b440719666648 Mon Sep 17 00:00:00 2001 From: hallacy Date: Tue, 16 Mar 2021 11:23:26 -0700 Subject: [PATCH 08/10] Move answer and classification to top level attributes, rename higherlevel (#12) * Move answer and classification to top level attributes * New namespaces for answers and classifications * Meant to make the method create * Go up the class stack since we don't need all the things that engineapiresource gives us --- openai/api_resources/__init__.py | 3 ++- openai/api_resources/answer.py | 14 ++++++++++++++ openai/api_resources/classification.py | 14 ++++++++++++++ openai/api_resources/higherlevel.py | 19 ------------------- 4 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 openai/api_resources/answer.py create mode 100644 openai/api_resources/classification.py delete mode 100644 openai/api_resources/higherlevel.py diff --git a/openai/api_resources/__init__.py b/openai/api_resources/__init__.py index 2a77f47097..2cce27ae9b 100644 --- a/openai/api_resources/__init__.py +++ b/openai/api_resources/__init__.py @@ -4,7 +4,8 @@ from openai.api_resources.error_object import ErrorObject from openai.api_resources.event import Event from openai.api_resources.file import File -from openai.api_resources.higherlevel import HigherLevel +from openai.api_resources.answer import Answer +from openai.api_resources.classification import Classification from openai.api_resources.plan import Plan from openai.api_resources.run import Run from openai.api_resources.snapshot import Snapshot diff --git a/openai/api_resources/answer.py b/openai/api_resources/answer.py new file mode 100644 index 0000000000..8dd3e84d23 --- /dev/null +++ b/openai/api_resources/answer.py @@ -0,0 +1,14 @@ +from openai.openai_object import OpenAIObject + + +class Answer(OpenAIObject): + api_prefix = "v1" + + @classmethod + def get_url(self, base): + return "/%s/%s" % (self.api_prefix, base) + + @classmethod + def create(cls, **params): + instance = cls() + return instance.request("post", cls.get_url("answers"), params) diff --git a/openai/api_resources/classification.py b/openai/api_resources/classification.py new file mode 100644 index 0000000000..b659164e5a --- /dev/null +++ b/openai/api_resources/classification.py @@ -0,0 +1,14 @@ +from openai.openai_object import OpenAIObject + + +class Classification(OpenAIObject): + api_prefix = "v1" + + @classmethod + def get_url(self, base): + return "/%s/%s" % (self.api_prefix, base) + + @classmethod + def create(cls, **params): + instance = cls() + return instance.request("post", cls.get_url("classifications"), params) diff --git a/openai/api_resources/higherlevel.py b/openai/api_resources/higherlevel.py deleted file mode 100644 index 6b71cb46f0..0000000000 --- a/openai/api_resources/higherlevel.py +++ /dev/null @@ -1,19 +0,0 @@ -from openai.api_resources.abstract.engine_api_resource import EngineAPIResource - - -class HigherLevel(EngineAPIResource): - api_prefix = "v1" - - @classmethod - def get_url(self, base): - return "/%s/%s" % (self.api_prefix, base) - - @classmethod - def classification(cls, **params): - instance = cls() - return instance.request("post", cls.get_url("classifications"), params) - - @classmethod - def answer(cls, **params): - instance = cls() - return instance.request("post", cls.get_url("answers"), params) From 36b6427d153b892faee0cf65f02f45b466417a2e Mon Sep 17 00:00:00 2001 From: hallacy Date: Thu, 18 Mar 2021 11:35:17 -0700 Subject: [PATCH 09/10] Add file support to search (#13) * Add file support to search * Add support for max_rerank * Added return_metadata support --- openai/cli.py | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/openai/cli.py b/openai/cli.py index d2443892b8..d28395a4b2 100644 --- a/openai/cli.py +++ b/openai/cli.py @@ -76,7 +76,7 @@ def generate(cls, args): top_p=args.top_p, logprobs=args.logprobs, stop=args.stop, - **kwargs + **kwargs, ) if not args.stream: resp = [resp] @@ -94,17 +94,34 @@ def generate(cls, args): @classmethod def search(cls, args): # Will soon be deprecated and replaced by a Search.create - resp = openai.Engine(id=args.id).search( - documents=args.documents, query=args.query - ) + params = { + "query": args.query, + "max_rerank": args.max_rerank, + "return_metadata": args.return_metadata, + } + if args.documents: + params["documents"] = args.documents + if args.file: + params["file"] = args.file + + resp = openai.Engine(id=args.id).search(**params) scores = [ (search_result["score"], search_result["document"]) for search_result in resp["data"] ] scores.sort(reverse=True) + dataset = ( + args.documents if args.documents else [x["text"] for x in resp["data"]] + ) for score, document_idx in scores: print("=== score {:.3f} ===".format(score)) - print(args.documents[document_idx]) + print(dataset[document_idx]) + if ( + args.return_metadata + and args.file + and "metadata" in resp["data"][document_idx] + ): + print(f"METADATA: {resp['data'][document_idx]['metadata']}") @classmethod def list(cls, args): @@ -380,8 +397,26 @@ def help(args): "-d", "--documents", action="append", - help="List of documents to search over", - required=True, + help="List of documents to search over. Only one of `documents` or `file` may be supplied.", + required=False, + ) + sub.add_argument( + "-f", + "--file", + help="A file id to search over. Only one of `documents` or `file` may be supplied.", + required=False, + ) + sub.add_argument( + "--max_rerank", + help="The maximum number of documents to be re-ranked and returned by search. This flag only takes effect when `file` is set.", + type=int, + default=200, + ) + sub.add_argument( + "--return_metadata", + help="A special boolean flag for showing metadata. If set `true`, each document entry in the returned json will contain a 'metadata' field. Default to be `false`. This flag only takes effect when `file` is set.", + type=bool, + default=False, ) sub.add_argument("-q", "--query", required=True, help="Search query") sub.set_defaults(func=Engine.search) From 3eb72e85e72919288f793e949debaa759e26e076 Mon Sep 17 00:00:00 2001 From: Chris Hallacy Date: Thu, 18 Mar 2021 11:51:26 -0700 Subject: [PATCH 10/10] Fixed some cherry pick issues --- openai/cli.py | 70 --------------------------------------------------- 1 file changed, 70 deletions(-) diff --git a/openai/cli.py b/openai/cli.py index d28395a4b2..2c2429dc62 100644 --- a/openai/cli.py +++ b/openai/cli.py @@ -212,50 +212,6 @@ def list(cls, args): print(tags) -class FineTuneCLI: - @classmethod - def list(cls, args): - resp = openai.FineTune.list() - print(resp) - - @classmethod - def create(cls, args): - create_args = { - "train_file": args.train_file, - } - if args.test_file: - create_args["test_file"] = args.test_file - if args.base_model: - create_args["base_model"] = args.base_model - if args.hparams: - try: - hparams = json.loads(args.hparams) - except json.decoder.JSONDecodeError: - sys.stderr.write( - "--hparams must be JSON decodable and match the hyperparameter arguments of the API" - ) - sys.exit(1) - create_args.update(hparams) - - resp = openai.FineTune.create(**create_args) - print(resp) - - @classmethod - def get(cls, args): - resp = openai.FineTune.retrieve(id=args.id) - print(resp) - - @classmethod - def events(cls, args): - resp = openai.FineTune.list_events(id=args.id) - print(resp) - - @classmethod - def cancel(cls, args): - resp = openai.FineTune.cancel(id=args.id) - print(resp) - - class File: @classmethod def create(cls, args): @@ -528,32 +484,6 @@ def help(args): sub = subparsers.add_parser("tags.list") sub.set_defaults(func=Tag.list) - # /fine-tunes API - sub = subparsers.add_parser("fine_tunes.list") - sub.set_defaults(func=FineTuneCLI.list) - - sub = subparsers.add_parser("fine_tunes.create") - sub.add_argument("-t", "--train_file", required=True, help="File to train") - sub.add_argument("--test_file", help="File to test") - sub.add_argument( - "-b", - "--base_model", - help="The model name to start the run from", - ) - sub.add_argument("-p", "--hparams", help="Hyperparameter JSON") - sub.set_defaults(func=FineTuneCLI.create) - - sub = subparsers.add_parser("fine_tunes.get") - sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") - sub.set_defaults(func=FineTuneCLI.get) - - sub = subparsers.add_parser("fine_tunes.events") - sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") - sub.set_defaults(func=FineTuneCLI.events) - - sub = subparsers.add_parser("fine_tunes.cancel") - sub.add_argument("-i", "--id", required=True, help="The id of the fine-tune job") - sub.set_defaults(func=FineTuneCLI.cancel) # Files sub = subparsers.add_parser("files.create")