From c0272d0f44dbbc8b8b77edc4f60d8b3b4753ca59 Mon Sep 17 00:00:00 2001 From: Ryan Voots Date: Sat, 30 Dec 2023 14:38:22 -0500 Subject: [PATCH] Working towards a new design here, makes things easier and saner later --- .vstags | 14 +++--- lib/OpenAIAsync/Server.pm | 66 -------------------------- lib/OpenAIAsync/Server/API/v1/File.pm | 67 +++++++++++++++++++++++++++ lib/OpenAIAsync/Types/Requests.pm | 15 +----- lib/OpenAIAsync/Types/Shared.pm | 23 +++++++++ 5 files changed, 100 insertions(+), 85 deletions(-) create mode 100644 lib/OpenAIAsync/Server/API/v1/File.pm create mode 100644 lib/OpenAIAsync/Types/Shared.pm diff --git a/.vstags b/.vstags index 2a7da0c..9227a0a 100644 --- a/.vstags +++ b/.vstags @@ -9896,6 +9896,7 @@ OpenAIAsync::Client .build/t7Cb8f47yj/lib/OpenAIAsync/Client.pm 1;" p OpenAIAsync::Client OpenAIAsync-0.01/lib/OpenAIAsync/Client.pm 1;" p OpenAIAsync::Client lib/OpenAIAsync/Client.pm 1;" p OpenAIAsync::Server lib/OpenAIAsync/Server.pm 1;" p +OpenAIAsync::Server::API::v1::File lib/OpenAIAsync/Server/API/v1/File.pm 1;" p OpenAIAsync::Types .build/0T4wbFlmwf/blib/lib/OpenAIAsync/Types.pm 1;" p OpenAIAsync::Types .build/0T4wbFlmwf/lib/OpenAIAsync/Types.pm 1;" p OpenAIAsync::Types .build/2oNz8Mp68u/blib/lib/OpenAIAsync/Types.pm 1;" p @@ -9997,7 +9998,7 @@ OpenAIAsync::Types::Requests::ChatCompletion::Messages::Union .build/trQp7H7Uyl/ OpenAIAsync::Types::Requests::ChatCompletion::Messages::Union .build/wX6DkQhw6E/blib/lib/OpenAIAsync/Types/Requests.pm 170;" p OpenAIAsync::Types::Requests::ChatCompletion::Messages::Union .build/wX6DkQhw6E/lib/OpenAIAsync/Types/Requests.pm 170;" p OpenAIAsync::Types::Requests::ChatCompletion::Messages::Union OpenAIAsync-0.01/lib/OpenAIAsync/Types/Requests.pm 187;" p -OpenAIAsync::Types::Requests::ChatCompletion::Messages::Union lib/OpenAIAsync/Types/Requests.pm 187;" p +OpenAIAsync::Types::Requests::ChatCompletion::Messages::Union lib/OpenAIAsync/Types/Requests.pm 188;" p OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion .build/0T4wbFlmwf/blib/lib/OpenAIAsync/Types/Requests.pm 94;" p OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion .build/0T4wbFlmwf/lib/OpenAIAsync/Types/Requests.pm 94;" p OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion .build/2oNz8Mp68u/blib/lib/OpenAIAsync/Types/Requests.pm 94;" p @@ -10034,7 +10035,7 @@ OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion .buil OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion .build/wX6DkQhw6E/blib/lib/OpenAIAsync/Types/Requests.pm 94;" p OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion .build/wX6DkQhw6E/lib/OpenAIAsync/Types/Requests.pm 94;" p OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion OpenAIAsync-0.01/lib/OpenAIAsync/Types/Requests.pm 110;" p -OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion lib/OpenAIAsync/Types/Requests.pm 110;" p +OpenAIAsync::Types::Requests::ChatCompletion::Messages::User::ContentUnion lib/OpenAIAsync/Types/Requests.pm 111;" p OpenAIAsync::Types::Results .build/0T4wbFlmwf/blib/lib/OpenAIAsync/Types/Results.pm 1;" p OpenAIAsync::Types::Results .build/0T4wbFlmwf/lib/OpenAIAsync/Types/Results.pm 1;" p OpenAIAsync::Types::Results .build/2oNz8Mp68u/blib/lib/OpenAIAsync/Types/Results.pm 1;" p @@ -10072,6 +10073,7 @@ OpenAIAsync::Types::Results .build/wX6DkQhw6E/blib/lib/OpenAIAsync/Types/Results OpenAIAsync::Types::Results .build/wX6DkQhw6E/lib/OpenAIAsync/Types/Results.pm 1;" p OpenAIAsync::Types::Results OpenAIAsync-0.01/lib/OpenAIAsync/Types/Results.pm 1;" p OpenAIAsync::Types::Results lib/OpenAIAsync/Types/Results.pm 1;" p +OpenAIAsync::Types::Shared lib/OpenAIAsync/Types/Shared.pm 1;" p PAREN local/lib/perl5/Perl/Tidy/Tokenizer.pm 1699;" c PART_PRES local/lib/perl5/Lingua/EN/Inflect.pm 1452;" s PATCH local/lib/perl5/HTTP/Request/Common.pm 25;" s @@ -28184,8 +28186,8 @@ new .build/wX6DkQhw6E/lib/OpenAIAsync/Types/Requests.pm 173;" s new .build/wX6DkQhw6E/lib/OpenAIAsync/Types/Requests.pm 97;" s new OpenAIAsync-0.01/lib/OpenAIAsync/Types/Requests.pm 113;" s new OpenAIAsync-0.01/lib/OpenAIAsync/Types/Requests.pm 190;" s -new lib/OpenAIAsync/Types/Requests.pm 113;" s -new lib/OpenAIAsync/Types/Requests.pm 190;" s +new lib/OpenAIAsync/Types/Requests.pm 114;" s +new lib/OpenAIAsync/Types/Requests.pm 191;" s new local/bin/lwp-request 231;" s new local/lib/perl5/Algorithm/Diff.pm 580;" s new local/lib/perl5/App/Cmd.pm 163;" s @@ -29010,7 +29012,7 @@ ontent::new .build/trQp7H7Uyl/lib/OpenAIAsync/Types/Requests.pm 173;" s ontent::new .build/wX6DkQhw6E/blib/lib/OpenAIAsync/Types/Requests.pm 173;" s ontent::new .build/wX6DkQhw6E/lib/OpenAIAsync/Types/Requests.pm 173;" s ontent::new OpenAIAsync-0.01/lib/OpenAIAsync/Types/Requests.pm 190;" s -ontent::new lib/OpenAIAsync/Types/Requests.pm 190;" s +ontent::new lib/OpenAIAsync/Types/Requests.pm 191;" s oo local/lib/perl5/oo.pm 1;" p oo::import local/lib/perl5/oo.pm 22;" s oo::moo local/lib/perl5/oo.pm 7;" s @@ -31684,7 +31686,7 @@ y::new .build/trQp7H7Uyl/lib/OpenAIAsync/Types/Requests.pm 97;" s y::new .build/wX6DkQhw6E/blib/lib/OpenAIAsync/Types/Requests.pm 97;" s y::new .build/wX6DkQhw6E/lib/OpenAIAsync/Types/Requests.pm 97;" s y::new OpenAIAsync-0.01/lib/OpenAIAsync/Types/Requests.pm 113;" s -y::new lib/OpenAIAsync/Types/Requests.pm 113;" s +y::new lib/OpenAIAsync/Types/Requests.pm 114;" s y_n local/lib/perl5/Module/Build/Base.pm 602;" s year local/lib/perl5/Software/License.pm 57;" s year local/lib/perl5/x86_64-linux/DateTime.pm 767;" s diff --git a/lib/OpenAIAsync/Server.pm b/lib/OpenAIAsync/Server.pm index a98ddcb..754de9f 100644 --- a/lib/OpenAIAsync/Server.pm +++ b/lib/OpenAIAsync/Server.pm @@ -393,70 +393,4 @@ class OpenAIAsync::Server :repr(HASH) :isa(IO::Async::Notifier) :strict(params) $self->_custom_resp($req, 200, $json_resp, 1); return $f; } - - # This is the legacy completion api - async method completion($input) { - - if (ref($input) eq 'HASH') { - $input = OpenAIAsync::Types::Requests::Completion->new($input->%*); - } elsif (ref($input) eq 'OpenAIAsync::Types::Requests::Completion') { - # dummy, nothing to do - } else { - die "Unsupported input type [".ref($input)."]"; - } - - my $data = await $self->_make_request($input->_endpoint(), $input); - - my $type_result = OpenAIAsync::Types::Results::Completion->new($data->%*); - - return $type_result; - } - - async method chat($input) { - if (ref($input) eq 'HASH') { - $input = OpenAIAsync::Types::Requests::ChatCompletion->new($input->%*); - } elsif (ref($input) eq 'OpenAIAsync::Types::Requests::ChatCompletion') { - # dummy, nothing to do - } else { - die "Unsupported input type [".ref($input)."]"; - } - - my $data = await $self->_make_request($input->_endpoint(), $input); - - my $type_result = OpenAIAsync::Types::Results::ChatCompletion->new($data->%*); - - return $type_result; - } - - async method embedding($input) { - if (ref($input) eq 'HASH') { - $input = OpenAIAsync::Types::Requests::Embedding->new($input->%*); - } elsif (ref($input) eq 'OpenAIAsync::Types::Requests::Embedding') { - # dummy, nothing to do - } else { - die "Unsupported input type [".ref($input)."]"; - } - - my $data = await $self->_make_request($input->_endpoint(), $input); - - my $type_result = OpenAIAsync::Types::Results::Embedding->new($data->%*); - - return $type_result; - } - - async method image_generate($input) { - ... - } - - async method text_to_speech($text) { - ... - } - - async method speech_to_text($sound_data) { - ... - } - - async method vision($image, $prompt) { - ... - } } \ No newline at end of file diff --git a/lib/OpenAIAsync/Server/API/v1/File.pm b/lib/OpenAIAsync/Server/API/v1/File.pm new file mode 100644 index 0000000..3787bff --- /dev/null +++ b/lib/OpenAIAsync/Server/API/v1/File.pm @@ -0,0 +1,67 @@ +package OpenAIAsync::Server::API::v1::File; + +use v5.36.0; +use Object::Pad; +use IO::Async::SSL; # We're not directly using it but I want to enforce that we pull it in when detecting dependencies, since openai itself is always https +use Future::AsyncAwait; +use IO::Async; + +use OpenAIAsync::Types::Results; +use OpenAIAsync::Types::Requests; + +our $VERSION = '0.02'; + +# ABSTRACT: Async server for OpenAI style REST API for various AI systems (LLMs, Images, Video, etc.) + +=pod + +=head1 NAME + +OpenAIAsync::Server::API::File - Basic file api role, consumed to implement the OpenAI file server. Does not provide an implementation, you are expected to override them in your class + +=head1 SYNOPSIS + +... + +=cut + +role OpenAIAsync::Server::API::v1::File :strict(params) { + ADJUST { + $self->register_url( + method => 'POST', + url => qr{^/v1/files$}, + handle => async sub($req, $ctx, $params) {await $self->file_upload($req, $ctx)}, + result_class => "OpenAIAsync::Type::Shared::File", + ); + $self->register_url( + method => 'GET', + url => qr{^/v1/files/(?[^/]+)/content$}, + handle => async sub($req, $ctx, $params) {await $self->file_download($req, $ctx, $params)}, + result_class => "", # TODO this should be special, it's raw content, make it undef? leave it off? + ); + $self->register_url( + method => 'GET', + url => qr{^/v1/files/(?[^/]+)$}, + handle => async sub($req, $ctx, $params) {await $self->file_info($req, $ctx, $params)}, + result_class => "OpenAIAsync::Type::Shared::File", + ); + $self->register_url( + method => 'DELETE', + url => qr{^/v1/files/(?[^/]+)$}, + handle => async sub($req, $ctx, $params) {await $self->file_delete($req, $ctx, $params)}, + result_class => "OpenAIAsync::Type::Results::FileDeletion", + ); + $self->register_url( + method => 'GET', + url => qr{^/v1/files$}, + handle => async sub($req, $ctx, $params) {await $self->file_list($req, $ctx)}, + result_class => "OpenAIAsync::Type::Results::FileList", + ); + } + + async method file_list($http_req, $ctx) {...} + async method file_info($http_req, $ctx, $params) {...} + async method file_delete($http_req, $ctx, $params) {...} + async method file_upload($http_req, $ctx, $params) {...} + async method file_download($http_req, $ctx, $params) {...} +} \ No newline at end of file diff --git a/lib/OpenAIAsync/Types/Requests.pm b/lib/OpenAIAsync/Types/Requests.pm index 6f23d1d..9504114 100644 --- a/lib/OpenAIAsync/Types/Requests.pm +++ b/lib/OpenAIAsync/Types/Requests.pm @@ -6,6 +6,7 @@ use Object::PadX::Role::AutoMarshal; use Object::PadX::Role::AutoJSON; use Object::Pad::ClassAttr::Struct; use OpenAIAsync::Types; +use OpenAIAsync::Types::Shared; role OpenAIAsync::Types::Requests::Base :does(OpenAIAsync::Types::Base) :Struct { method _endpoint(); # How the client finds where to send the request @@ -210,22 +211,10 @@ package class OpenAIAsync::Types::Requests::FileUpload :does(OpenAIAsync::Types::Requests::Base) :Struct { method _endpoint() {"/files"} - field $file :MarshalTo(OpenAIAsync::Types::Requests::FileObject); + field $file :MarshalTo(OpenAIAsync::Types::Shared::FileObject); field $purpose :JSONStr; # fine-tune and assistants for the types, TODO check format/type of file } -# TODO this is shared request and result? -# TODO Add a method here that given a file name will create a new object with things filled out -class OpenAIAsync::Types::Requests::FileObject :does(OpenAIAsync::Types::Requests::Base) :Struct { - field $id :JSONStr = undef; # Only optional for uploads, but always comes back from the service. TODO make a check - field $bytes :JSONNum; - field $created_at :JSONNum; - field $filename :JSONStr; - field $object :JSONStr = "file"; # Always a file, maybe enforce this in the future - field $purpose :JSONStr; # fine-tune, fine-tune-results, assistants, or assistants_output - field $status :JSONStr = undef; # DEPRECATED, current status of the file: uploaded, processed, or error - field $status_detailts :JSONStr = undef; # DEPRECATED originally used for details of fine-tuning -} class OpenAIAsync::Types::Requests::FileList :does(OpenAIAsync::Types::Requests::Base) :Struct { method _endpoint() {"/files"} diff --git a/lib/OpenAIAsync/Types/Shared.pm b/lib/OpenAIAsync/Types/Shared.pm new file mode 100644 index 0000000..2cf7c3a --- /dev/null +++ b/lib/OpenAIAsync/Types/Shared.pm @@ -0,0 +1,23 @@ +package OpenAIAsync::Types::Shared; +use v5.36.0; +use Object::Pad; + +use Object::PadX::Role::AutoMarshal; +use Object::PadX::Role::AutoJSON; +use Object::Pad::ClassAttr::Struct; +use OpenAIAsync::Types; + +# TODO this is shared request and result? +# TODO Add a method here that given a file name will create a new object with things filled out +class OpenAIAsync::Types::Shared::FileObject :does(OpenAIAsync::Types::Requests::Base) :Struct { + field $id :JSONStr = undef; # Only optional for uploads, but always comes back from the service. TODO make a check + field $bytes :JSONNum; + field $created_at :JSONNum; + field $filename :JSONStr; + field $object :JSONStr = "file"; # Always a file, maybe enforce this in the future + field $purpose :JSONStr; # fine-tune, fine-tune-results, assistants, or assistants_output + field $status :JSONStr = undef; # DEPRECATED, current status of the file: uploaded, processed, or error + field $status_detailts :JSONStr = undef; # DEPRECATED originally used for details of fine-tuning +} + +1; \ No newline at end of file