File Coverage

blib/lib/AI/Ollama/Client/Impl.pm
Criterion Covered Total %
statement 101 707 14.2
branch 0 70 0.0
condition 0 8 0.0
subroutine 34 92 36.9
pod 24 26 92.3
total 159 903 17.6


line stmt bran cond sub pod time code
1             package AI::Ollama::Client::Impl 0.05;
2             # DO NOT EDIT! This is an autogenerated file.
3              
4 1     1   868 use 5.020;
  1         5  
5 1     1   8 use Moo 2;
  1         15  
  1         6  
6             with 'Role::EventEmitter';
7 1     1   471 use experimental 'signatures';
  1         3  
  1         20  
8 1     1   826 use PerlX::Maybe;
  1         3617  
  1         15  
9 1     1   56 use Carp 'croak';
  1         2  
  1         81  
10              
11             # These should go into a ::Role
12 1     1   748 use YAML::PP;
  1         72528  
  1         61  
13 1     1   556 use Mojo::UserAgent;
  1         499464  
  1         12  
14 1     1   61 use Mojo::URL;
  1         2  
  1         6  
15 1     1   690 use URI::Template;
  1         12610  
  1         78  
16 1     1   9 use Mojo::JSON 'encode_json', 'decode_json';
  1         4  
  1         81  
17 1     1   968 use OpenAPI::Modern;
  1         638786  
  1         77  
18              
19 1     1   12 use File::ShareDir 'module_file';
  1         4  
  1         89  
20              
21 1     1   724 use Future::Mojo;
  1         23307  
  1         52  
22 1     1   610 use Future::Queue;
  1         1607  
  1         76  
23              
24             our $SCHEMA_VERSION = "0.1.9";
25              
26 1     1   669 use AI::Ollama::CopyModelRequest;
  1         32  
  1         53  
27 1     1   635 use AI::Ollama::CreateModelRequest;
  1         13  
  1         57  
28 1     1   633 use AI::Ollama::CreateModelResponse;
  1         15  
  1         57  
29 1     1   697 use AI::Ollama::DeleteModelRequest;
  1         14  
  1         56  
30 1     1   668 use AI::Ollama::GenerateChatCompletionRequest;
  1         14  
  1         52  
31 1     1   678 use AI::Ollama::GenerateChatCompletionResponse;
  1         14  
  1         54  
32 1     1   662 use AI::Ollama::GenerateCompletionRequest;
  1         10  
  1         36  
33 1     1   467 use AI::Ollama::GenerateCompletionResponse;
  1         9  
  1         35  
34 1     1   414 use AI::Ollama::GenerateEmbeddingRequest;
  1         9  
  1         36  
35 1     1   394 use AI::Ollama::GenerateEmbeddingResponse;
  1         9  
  1         35  
36 1     1   416 use AI::Ollama::Message;
  1         14  
  1         84  
37 1     1   616 use AI::Ollama::Model;
  1         14  
  1         71  
38 1     1   636 use AI::Ollama::ModelInfo;
  1         13  
  1         50  
39 1     1   583 use AI::Ollama::ModelInfoRequest;
  1         13  
  1         48  
40 1     1   601 use AI::Ollama::ModelsResponse;
  1         14  
  1         82  
41 1     1   638 use AI::Ollama::PullModelRequest;
  1         14  
  1         53  
42 1     1   631 use AI::Ollama::PullModelResponse;
  1         10  
  1         38  
43 1     1   517 use AI::Ollama::PushModelRequest;
  1         9  
  1         36  
44 1     1   388 use AI::Ollama::PushModelResponse;
  1         9  
  1         35  
45 1     1   490 use AI::Ollama::RequestOptions;
  1         10  
  1         6642  
46              
47             =encoding utf8
48              
49             =head1 SYNOPSIS
50              
51             my $client = AI::Ollama::Client::Impl->new(
52             schema_file => '...',
53             );
54              
55             =head1 PROPERTIES
56              
57             =head2 B<< schema_file >>
58              
59             The OpenAPI schema file we use for validation
60              
61             =head2 B<< schema >>
62              
63             The OpenAPI schema data structure we use for validation. If not given,
64             we will create one using the C parameter.
65              
66             =head2 B<< openapi >>
67              
68             The L object we use for validation. If not given,
69             we will create one using the C parameter.
70              
71             =head2 B<< ua >>
72              
73             The L to use
74              
75             =head2 B<< server >>
76              
77             The server to access
78              
79             =cut
80              
81             has 'schema_file' => (
82             is => 'lazy',
83             default => sub { require AI::Ollama::Client::Impl; module_file('AI::Ollama::Client::Impl', 'ollama-curated.yaml') },
84             );
85              
86             has 'schema' => (
87             is => 'lazy',
88             default => sub {
89             if( my $fn = $_[0]->schema_file ) {
90             YAML::PP->new( boolean => 'JSON::PP' )->load_file($fn);
91             }
92             },
93             );
94              
95             has 'validate_requests' => (
96             is => 'rw',
97             default => 1,
98             );
99              
100             has 'validate_responses' => (
101             is => 'rw',
102             default => 1,
103             );
104              
105             has 'openapi' => (
106             is => 'lazy',
107             default => sub {
108             if( my $schema = $_[0]->schema ) {
109             OpenAPI::Modern->new( openapi_schema => $schema, openapi_uri => '' )
110             }
111             },
112             );
113              
114             # The HTTP stuff should go into a ::Role I guess
115             has 'ua' => (
116             is => 'lazy',
117             default => sub { Mojo::UserAgent->new },
118             );
119              
120             has 'server' => (
121             is => 'ro',
122             );
123              
124             =head1 METHODS
125              
126             =head2 C<< build_checkBlob_request >>
127              
128             Build an HTTP request as L object. For the parameters see below.
129              
130             =head2 C<< checkBlob >>
131              
132             my $res = $client->checkBlob(
133             'digest' => '...',
134             )->get;
135              
136             Check to see if a blob exists on the Ollama server which is useful when creating models.
137              
138             =head3 Parameters
139              
140             =over 4
141              
142             =item B<< digest >>
143              
144             the SHA256 digest of the blob
145              
146             =back
147              
148              
149              
150             =cut
151              
152 0     0 1   sub build_checkBlob_request( $self, %options ) {
  0            
  0            
  0            
153             croak "Missing required parameter 'digest'"
154 0 0         unless exists $options{ 'digest' };
155              
156 0           my $method = 'HEAD';
157 0           my $template = URI::Template->new( '/blobs/{digest}' );
158             my $path = $template->process(
159 0           'digest' => delete $options{'digest'},
160             );
161 0           my $url = Mojo::URL->new( $self->server . $path );
162              
163 0           my $tx = $self->ua->build_tx(
164             $method => $url,
165             {
166             }
167             );
168              
169 0           $self->validate_request( $tx );
170              
171 0           return $tx
172             }
173              
174              
175 0     0 1   sub checkBlob( $self, %options ) {
  0            
  0            
  0            
176 0           my $tx = $self->build_checkBlob_request(%options);
177              
178              
179 0           my $res = Future::Mojo->new();
180              
181 0           my $r1 = Future::Mojo->new();
182 0     0     $r1->then( sub( $tx ) {
  0            
  0            
183 0           my $resp = $tx->res;
184 0           $self->emit(response => $resp);
185             # Should we validate using OpenAPI::Modern here?!
186 0 0         if( $resp->code == 200 ) {
    0          
187             # Blob exists on the server
188 0           $res->done($resp);
189             } elsif( $resp->code == 404 ) {
190             # Blob was not found
191 0           $res->done($resp);
192             } else {
193             # An unknown/unhandled response, likely an error
194 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
195             }
196 0           })->retain;
197              
198             # Start our transaction
199 0           $self->emit(request => $tx);
200 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
201 0           $r1->resolve( $tx );
202 0           undef $r1;
203 0     0     })->catch(sub($err) {
  0            
  0            
204 0           $self->emit(response => $tx, $err);
205 0           $r1->fail( $err => $tx );
206 0           undef $r1;
207 0           });
208              
209 0           return $res
210             }
211              
212             =head2 C<< build_createBlob_request >>
213              
214             Build an HTTP request as L object. For the parameters see below.
215              
216             =head2 C<< createBlob >>
217              
218             my $res = $client->createBlob(
219             'digest' => '...',
220             )->get;
221              
222             Create a blob from a file. Returns the server file path.
223              
224             =head3 Parameters
225              
226             =over 4
227              
228             =item B<< digest >>
229              
230             the SHA256 digest of the blob
231              
232             =back
233              
234              
235              
236             =cut
237              
238 0     0 1   sub build_createBlob_request( $self, %options ) {
  0            
  0            
  0            
239             croak "Missing required parameter 'digest'"
240 0 0         unless exists $options{ 'digest' };
241              
242 0           my $method = 'POST';
243 0           my $template = URI::Template->new( '/blobs/{digest}' );
244             my $path = $template->process(
245 0           'digest' => delete $options{'digest'},
246             );
247 0           my $url = Mojo::URL->new( $self->server . $path );
248              
249 0   0       my $body = delete $options{ body } // '';
250 0           my $tx = $self->ua->build_tx(
251             $method => $url,
252             {
253             "Content-Type" => 'application/octet-stream',
254             }
255             => $body,
256             );
257              
258 0           $self->validate_request( $tx );
259              
260 0           return $tx
261             }
262              
263              
264 0     0 1   sub createBlob( $self, %options ) {
  0            
  0            
  0            
265 0           my $tx = $self->build_createBlob_request(%options);
266              
267              
268 0           my $res = Future::Mojo->new();
269              
270 0           my $r1 = Future::Mojo->new();
271 0     0     $r1->then( sub( $tx ) {
  0            
  0            
272 0           my $resp = $tx->res;
273 0           $self->emit(response => $resp);
274             # Should we validate using OpenAPI::Modern here?!
275 0 0         if( $resp->code == 201 ) {
276             # Blob was successfully created
277 0           $res->done($resp);
278             } else {
279             # An unknown/unhandled response, likely an error
280 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
281             }
282 0           })->retain;
283              
284             # Start our transaction
285 0           $self->emit(request => $tx);
286 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
287 0           $r1->resolve( $tx );
288 0           undef $r1;
289 0     0     })->catch(sub($err) {
  0            
  0            
290 0           $self->emit(response => $tx, $err);
291 0           $r1->fail( $err => $tx );
292 0           undef $r1;
293 0           });
294              
295 0           return $res
296             }
297              
298             =head2 C<< build_generateChatCompletion_request >>
299              
300             Build an HTTP request as L object. For the parameters see below.
301              
302             =head2 C<< generateChatCompletion >>
303              
304             use Future::Utils 'repeat';
305             my $response = $client->generateChatCompletion();
306             my $streamed = $response->get();
307             repeat {
308             my ($res) = $streamed->shift;
309             if( $res ) {
310             my $str = $res->get;
311             say $str;
312             }
313              
314             Future::Mojo->done( defined $res );
315             } until => sub($done) { $done->get };
316              
317             Generate the next message in a chat with a provided model.
318              
319             This is a streaming endpoint, so there will be a series of responses. The final response object will include statistics and additional data from the request.
320              
321              
322             =head3 Options
323              
324             =over 4
325              
326             =item C<< format >>
327              
328             The format to return a response in. Currently the only accepted value is json.
329              
330             Enable JSON mode by setting the format parameter to json. This will structure the response as valid JSON.
331              
332             Note: it's important to instruct the model to use JSON in the prompt. Otherwise, the model may generate large amounts whitespace.
333              
334             =item C<< keep_alive >>
335              
336             How long (in minutes) to keep the model loaded in memory.
337              
338             =over
339              
340             =item -
341              
342             If set to a positive duration (e.g. 20), the model will stay loaded for the provided duration.
343              
344              
345             =item -
346              
347             If set to a negative duration (e.g. -1), the model will stay loaded indefinitely.
348              
349              
350             =item -
351              
352             If set to 0, the model will be unloaded immediately once finished.
353              
354              
355             =item -
356              
357             If not set, the model will stay loaded for 5 minutes by default
358              
359              
360             =back
361              
362             =item C<< messages >>
363              
364             The messages of the chat, this can be used to keep a chat memory
365              
366             =item C<< model >>
367              
368             The model name.
369              
370             Model names follow a C format. Some examples are C and C. The tag is optional and, if not provided, will default to C. The tag is used to identify a specific version.
371              
372             =item C<< options >>
373              
374             Additional model parameters listed in the documentation for the Modelfile such as C.
375              
376             =item C<< stream >>
377              
378             If C the response will be returned as a single response object, otherwise the response will be streamed as a series of objects.
379              
380             =back
381              
382             Returns a L<< AI::Ollama::GenerateChatCompletionResponse >> on success.
383              
384             =cut
385              
386 0     0 1   sub build_generateChatCompletion_request( $self, %options ) {
  0            
  0            
  0            
387 0           my $method = 'POST';
388 0           my $path = '/chat';
389 0           my $url = Mojo::URL->new( $self->server . $path );
390              
391 0           my $request = AI::Ollama::GenerateChatCompletionRequest->new( \%options )->as_hash;
392 0           my $tx = $self->ua->build_tx(
393             $method => $url,
394             {
395             'Accept' => 'application/x-ndjson',
396             "Content-Type" => 'application/json',
397             }
398             => json => $request,
399             );
400              
401 0           $self->validate_request( $tx );
402              
403 0           return $tx
404             }
405              
406              
407 0     0 1   sub generateChatCompletion( $self, %options ) {
  0            
  0            
  0            
408 0           my $tx = $self->build_generateChatCompletion_request(%options);
409              
410              
411 0           my $res = Future::Mojo->new();
412              
413 0           my $r1 = Future::Mojo->new();
414 0           our @store; # we should use ->retain() instead
415 0     0     push @store, $r1->then( sub( $tx ) {
  0            
  0            
416 0           my $resp = $tx->res;
417 0           $self->emit(response => $resp);
418             # Should we validate using OpenAPI::Modern here?!
419 0 0         if( $resp->code == 200 ) {
420             # Successful operation.
421 0           my $queue = Future::Queue->new( prototype => 'Future::Mojo' );
422 0           $res->done( $queue );
423 0           my $ct = $resp->headers->content_type;
424 0 0         return unless $ct;
425 0           $ct =~ s/;\s+.*//;
426 0 0         if( $ct eq 'application/x-ndjson' ) {
427             # we only handle ndjson currently
428 0           my $handled_offset = 0;
429             $resp->on(progress => sub($msg,@) {
430 0           my $fresh = substr( $msg->body, $handled_offset );
431 0           my $body = $msg->body;
432 0           $body =~ s/[^\r\n]+\z//; # Strip any unfinished line
433 0           $handled_offset = length $body;
434 0           my @lines = split /\n/, $fresh;
435 0           for (@lines) {
436 0           my $payload = decode_json( $_ );
437 0           $self->validate_response( $payload, $tx );
438 0           $queue->push(
439             AI::Ollama::GenerateChatCompletionResponse->new($payload),
440              
441             );
442             };
443 0 0         if( $msg->{state} eq 'finished' ) {
444 0           $queue->finish();
445             }
446 0           });
447             } else {
448             # Unknown/unhandled content type
449 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
450             }
451             } else {
452             # An unknown/unhandled response, likely an error
453 0           $res->fail( sprintf( "unknown_unhandled code %d", $resp->code ), $resp);
454             }
455 0           });
456              
457 0           my $_tx;
458 0     0     $tx->res->once( progress => sub($msg, @) {
  0            
  0            
459 0           $r1->resolve( $tx );
460 0           undef $_tx;
461 0           undef $r1;
462 0           });
463 0           $self->emit(request => $tx);
464 0           $_tx = $self->ua->start_p($tx);
465              
466 0           return $res
467             }
468              
469             =head2 C<< build_copyModel_request >>
470              
471             Build an HTTP request as L object. For the parameters see below.
472              
473             =head2 C<< copyModel >>
474              
475             my $res = $client->copyModel()->get;
476              
477             Creates a model with another name from an existing model.
478              
479              
480             =head3 Options
481              
482             =over 4
483              
484             =item C<< destination >>
485              
486             Name of the new model.
487              
488             =item C<< source >>
489              
490             Name of the model to copy.
491              
492             =back
493              
494              
495             =cut
496              
497 0     0 1   sub build_copyModel_request( $self, %options ) {
  0            
  0            
  0            
498 0           my $method = 'POST';
499 0           my $path = '/copy';
500 0           my $url = Mojo::URL->new( $self->server . $path );
501              
502 0           my $request = AI::Ollama::CopyModelRequest->new( \%options )->as_hash;
503 0           my $tx = $self->ua->build_tx(
504             $method => $url,
505             {
506             "Content-Type" => 'application/json',
507             }
508             => json => $request,
509             );
510              
511 0           $self->validate_request( $tx );
512              
513 0           return $tx
514             }
515              
516              
517 0     0 1   sub copyModel( $self, %options ) {
  0            
  0            
  0            
518 0           my $tx = $self->build_copyModel_request(%options);
519              
520              
521 0           my $res = Future::Mojo->new();
522              
523 0           my $r1 = Future::Mojo->new();
524 0     0     $r1->then( sub( $tx ) {
  0            
  0            
525 0           my $resp = $tx->res;
526 0           $self->emit(response => $resp);
527             # Should we validate using OpenAPI::Modern here?!
528 0 0         if( $resp->code == 200 ) {
529             # Successful operation.
530 0           $res->done($resp);
531             } else {
532             # An unknown/unhandled response, likely an error
533 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
534             }
535 0           })->retain;
536              
537             # Start our transaction
538 0           $self->emit(request => $tx);
539 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
540 0           $r1->resolve( $tx );
541 0           undef $r1;
542 0     0     })->catch(sub($err) {
  0            
  0            
543 0           $self->emit(response => $tx, $err);
544 0           $r1->fail( $err => $tx );
545 0           undef $r1;
546 0           });
547              
548 0           return $res
549             }
550              
551             =head2 C<< build_createModel_request >>
552              
553             Build an HTTP request as L object. For the parameters see below.
554              
555             =head2 C<< createModel >>
556              
557             use Future::Utils 'repeat';
558             my $response = $client->createModel();
559             my $streamed = $response->get();
560             repeat {
561             my ($res) = $streamed->shift;
562             if( $res ) {
563             my $str = $res->get;
564             say $str;
565             }
566              
567             Future::Mojo->done( defined $res );
568             } until => sub($done) { $done->get };
569              
570             Create a model from a Modelfile.
571              
572             It is recommended to set C to the content of the Modelfile rather than just set C. This is a requirement for remote create. Remote model creation should also create any file blobs, fields such as C and C, explicitly with the server using Create a Blob and the value to the path indicated in the response.
573              
574              
575             =head3 Options
576              
577             =over 4
578              
579             =item C<< modelfile >>
580              
581             The contents of the Modelfile.
582              
583             =item C<< name >>
584              
585             The model name.
586              
587             Model names follow a C format. Some examples are C and C. The tag is optional and, if not provided, will default to C. The tag is used to identify a specific version.
588              
589             =item C<< stream >>
590              
591             If C the response will be returned as a single response object, otherwise the response will be streamed as a series of objects.
592              
593             =back
594              
595             Returns a L<< AI::Ollama::CreateModelResponse >> on success.
596              
597             =cut
598              
599 0     0 1   sub build_createModel_request( $self, %options ) {
  0            
  0            
  0            
600 0           my $method = 'POST';
601 0           my $path = '/create';
602 0           my $url = Mojo::URL->new( $self->server . $path );
603              
604 0           my $request = AI::Ollama::CreateModelRequest->new( \%options )->as_hash;
605 0           my $tx = $self->ua->build_tx(
606             $method => $url,
607             {
608             'Accept' => 'application/x-ndjson',
609             "Content-Type" => 'application/json',
610             }
611             => json => $request,
612             );
613              
614 0           $self->validate_request( $tx );
615              
616 0           return $tx
617             }
618              
619              
620 0     0 1   sub createModel( $self, %options ) {
  0            
  0            
  0            
621 0           my $tx = $self->build_createModel_request(%options);
622              
623              
624 0           my $res = Future::Mojo->new();
625              
626 0           my $r1 = Future::Mojo->new();
627 0           our @store; # we should use ->retain() instead
628 0     0     push @store, $r1->then( sub( $tx ) {
  0            
  0            
629 0           my $resp = $tx->res;
630 0           $self->emit(response => $resp);
631             # Should we validate using OpenAPI::Modern here?!
632 0 0         if( $resp->code == 200 ) {
633             # Successful operation.
634 0           my $queue = Future::Queue->new( prototype => 'Future::Mojo' );
635 0           $res->done( $queue );
636 0           my $ct = $resp->headers->content_type;
637 0 0         return unless $ct;
638 0           $ct =~ s/;\s+.*//;
639 0 0         if( $ct eq 'application/x-ndjson' ) {
640             # we only handle ndjson currently
641 0           my $handled_offset = 0;
642             $resp->on(progress => sub($msg,@) {
643 0           my $fresh = substr( $msg->body, $handled_offset );
644 0           my $body = $msg->body;
645 0           $body =~ s/[^\r\n]+\z//; # Strip any unfinished line
646 0           $handled_offset = length $body;
647 0           my @lines = split /\n/, $fresh;
648 0           for (@lines) {
649 0           my $payload = decode_json( $_ );
650 0           $self->validate_response( $payload, $tx );
651 0           $queue->push(
652             AI::Ollama::CreateModelResponse->new($payload),
653              
654             );
655             };
656 0 0         if( $msg->{state} eq 'finished' ) {
657 0           $queue->finish();
658             }
659 0           });
660             } else {
661             # Unknown/unhandled content type
662 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
663             }
664             } else {
665             # An unknown/unhandled response, likely an error
666 0           $res->fail( sprintf( "unknown_unhandled code %d", $resp->code ), $resp);
667             }
668 0           });
669              
670 0           my $_tx;
671 0     0     $tx->res->once( progress => sub($msg, @) {
  0            
  0            
672 0           $r1->resolve( $tx );
673 0           undef $_tx;
674 0           undef $r1;
675 0           });
676 0           $self->emit(request => $tx);
677 0           $_tx = $self->ua->start_p($tx);
678              
679 0           return $res
680             }
681              
682             =head2 C<< build_deleteModel_request >>
683              
684             Build an HTTP request as L object. For the parameters see below.
685              
686             =head2 C<< deleteModel >>
687              
688             my $res = $client->deleteModel()->get;
689              
690             Delete a model and its data.
691              
692              
693             =head3 Options
694              
695             =over 4
696              
697             =item C<< name >>
698              
699             The model name.
700              
701             Model names follow a C format. Some examples are C and C. The tag is optional and, if not provided, will default to C. The tag is used to identify a specific version.
702              
703             =back
704              
705              
706             =cut
707              
708 0     0 1   sub build_deleteModel_request( $self, %options ) {
  0            
  0            
  0            
709 0           my $method = 'DELETE';
710 0           my $path = '/delete';
711 0           my $url = Mojo::URL->new( $self->server . $path );
712              
713 0           my $request = AI::Ollama::DeleteModelRequest->new( \%options )->as_hash;
714 0           my $tx = $self->ua->build_tx(
715             $method => $url,
716             {
717             "Content-Type" => 'application/json',
718             }
719             => json => $request,
720             );
721              
722 0           $self->validate_request( $tx );
723              
724 0           return $tx
725             }
726              
727              
728 0     0 1   sub deleteModel( $self, %options ) {
  0            
  0            
  0            
729 0           my $tx = $self->build_deleteModel_request(%options);
730              
731              
732 0           my $res = Future::Mojo->new();
733              
734 0           my $r1 = Future::Mojo->new();
735 0     0     $r1->then( sub( $tx ) {
  0            
  0            
736 0           my $resp = $tx->res;
737 0           $self->emit(response => $resp);
738             # Should we validate using OpenAPI::Modern here?!
739 0 0         if( $resp->code == 200 ) {
740             # Successful operation.
741 0           $res->done($resp);
742             } else {
743             # An unknown/unhandled response, likely an error
744 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
745             }
746 0           })->retain;
747              
748             # Start our transaction
749 0           $self->emit(request => $tx);
750 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
751 0           $r1->resolve( $tx );
752 0           undef $r1;
753 0     0     })->catch(sub($err) {
  0            
  0            
754 0           $self->emit(response => $tx, $err);
755 0           $r1->fail( $err => $tx );
756 0           undef $r1;
757 0           });
758              
759 0           return $res
760             }
761              
762             =head2 C<< build_generateEmbedding_request >>
763              
764             Build an HTTP request as L object. For the parameters see below.
765              
766             =head2 C<< generateEmbedding >>
767              
768             my $res = $client->generateEmbedding()->get;
769              
770             Generate embeddings from a model.
771              
772              
773             =head3 Options
774              
775             =over 4
776              
777             =item C<< model >>
778              
779             The model name.
780              
781             Model names follow a C format. Some examples are C and C. The tag is optional and, if not provided, will default to C. The tag is used to identify a specific version.
782              
783             =item C<< options >>
784              
785             Additional model parameters listed in the documentation for the Modelfile such as C.
786              
787             =item C<< prompt >>
788              
789             Text to generate embeddings for.
790              
791             =back
792              
793             Returns a L<< AI::Ollama::GenerateEmbeddingResponse >> on success.
794              
795             =cut
796              
797 0     0 1   sub build_generateEmbedding_request( $self, %options ) {
  0            
  0            
  0            
798 0           my $method = 'POST';
799 0           my $path = '/embeddings';
800 0           my $url = Mojo::URL->new( $self->server . $path );
801              
802 0           my $request = AI::Ollama::GenerateEmbeddingRequest->new( \%options )->as_hash;
803 0           my $tx = $self->ua->build_tx(
804             $method => $url,
805             {
806             'Accept' => 'application/json',
807             "Content-Type" => 'application/json',
808             }
809             => json => $request,
810             );
811              
812 0           $self->validate_request( $tx );
813              
814 0           return $tx
815             }
816              
817              
818 0     0 1   sub generateEmbedding( $self, %options ) {
  0            
  0            
  0            
819 0           my $tx = $self->build_generateEmbedding_request(%options);
820              
821              
822 0           my $res = Future::Mojo->new();
823              
824 0           my $r1 = Future::Mojo->new();
825 0     0     $r1->then( sub( $tx ) {
  0            
  0            
826 0           my $resp = $tx->res;
827 0           $self->emit(response => $resp);
828             # Should we validate using OpenAPI::Modern here?!
829 0 0         if( $resp->code == 200 ) {
830             # Successful operation.
831 0           my $ct = $resp->headers->content_type;
832 0           $ct =~ s/;\s+.*//;
833 0 0         if( $ct eq 'application/json' ) {
834 0           my $payload = $resp->json();
835 0           $self->validate_response( $payload, $tx );
836 0           $res->done(
837             AI::Ollama::GenerateEmbeddingResponse->new($payload),
838              
839             );
840             } else {
841             # Unknown/unhandled content type
842 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
843             }
844             } else {
845             # An unknown/unhandled response, likely an error
846 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
847             }
848 0           })->retain;
849              
850             # Start our transaction
851 0           $self->emit(request => $tx);
852 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
853 0           $r1->resolve( $tx );
854 0           undef $r1;
855 0     0     })->catch(sub($err) {
  0            
  0            
856 0           $self->emit(response => $tx, $err);
857 0           $r1->fail( $err => $tx );
858 0           undef $r1;
859 0           });
860              
861 0           return $res
862             }
863              
864             =head2 C<< build_generateCompletion_request >>
865              
866             Build an HTTP request as L object. For the parameters see below.
867              
868             =head2 C<< generateCompletion >>
869              
870             use Future::Utils 'repeat';
871             my $response = $client->generateCompletion();
872             my $streamed = $response->get();
873             repeat {
874             my ($res) = $streamed->shift;
875             if( $res ) {
876             my $str = $res->get;
877             say $str;
878             }
879              
880             Future::Mojo->done( defined $res );
881             } until => sub($done) { $done->get };
882              
883             Generate a response for a given prompt with a provided model.
884              
885             The final response object will include statistics and additional data from the request.
886              
887              
888             =head3 Options
889              
890             =over 4
891              
892             =item C<< context >>
893              
894             The context parameter returned from a previous request to [generateCompletion], this can be used to keep a short conversational memory.
895              
896             =item C<< format >>
897              
898             The format to return a response in. Currently the only accepted value is json.
899              
900             Enable JSON mode by setting the format parameter to json. This will structure the response as valid JSON.
901              
902             Note: it's important to instruct the model to use JSON in the prompt. Otherwise, the model may generate large amounts whitespace.
903              
904             =item C<< images >>
905              
906             (optional) a list of Base64-encoded images to include in the message (for multimodal models such as llava)
907              
908             =item C<< keep_alive >>
909              
910             How long (in minutes) to keep the model loaded in memory.
911              
912             =over
913              
914             =item -
915              
916             If set to a positive duration (e.g. 20), the model will stay loaded for the provided duration.
917              
918              
919             =item -
920              
921             If set to a negative duration (e.g. -1), the model will stay loaded indefinitely.
922              
923              
924             =item -
925              
926             If set to 0, the model will be unloaded immediately once finished.
927              
928              
929             =item -
930              
931             If not set, the model will stay loaded for 5 minutes by default
932              
933              
934             =back
935              
936             =item C<< model >>
937              
938             The model name.
939              
940             Model names follow a C format. Some examples are C and C. The tag is optional and, if not provided, will default to C. The tag is used to identify a specific version.
941              
942             =item C<< options >>
943              
944             Additional model parameters listed in the documentation for the Modelfile such as C.
945              
946             =item C<< prompt >>
947              
948             The prompt to generate a response.
949              
950             =item C<< raw >>
951              
952             If C no formatting will be applied to the prompt and no context will be returned.
953              
954             You may choose to use the C parameter if you are specifying a full templated prompt in your request to the API, and are managing history yourself.
955              
956             =item C<< stream >>
957              
958             If C the response will be returned as a single response object, otherwise the response will be streamed as a series of objects.
959              
960             =item C<< system >>
961              
962             The system prompt to (overrides what is defined in the Modelfile).
963              
964             =item C<< template >>
965              
966             The full prompt or prompt template (overrides what is defined in the Modelfile).
967              
968             =back
969              
970             Returns a L<< AI::Ollama::GenerateCompletionResponse >> on success.
971              
972             =cut
973              
974 0     0 1   sub build_generateCompletion_request( $self, %options ) {
  0            
  0            
  0            
975 0           my $method = 'POST';
976 0           my $path = '/generate';
977 0           my $url = Mojo::URL->new( $self->server . $path );
978              
979 0           my $request = AI::Ollama::GenerateCompletionRequest->new( \%options )->as_hash;
980 0           my $tx = $self->ua->build_tx(
981             $method => $url,
982             {
983             'Accept' => 'application/x-ndjson',
984             "Content-Type" => 'application/json',
985             }
986             => json => $request,
987             );
988              
989 0           $self->validate_request( $tx );
990              
991 0           return $tx
992             }
993              
994              
995 0     0 1   sub generateCompletion( $self, %options ) {
  0            
  0            
  0            
996 0           my $tx = $self->build_generateCompletion_request(%options);
997              
998              
999 0           my $res = Future::Mojo->new();
1000              
1001 0           my $r1 = Future::Mojo->new();
1002 0           our @store; # we should use ->retain() instead
1003 0     0     push @store, $r1->then( sub( $tx ) {
  0            
  0            
1004 0           my $resp = $tx->res;
1005 0           $self->emit(response => $resp);
1006             # Should we validate using OpenAPI::Modern here?!
1007 0 0         if( $resp->code == 200 ) {
1008             # Successful operation.
1009 0           my $queue = Future::Queue->new( prototype => 'Future::Mojo' );
1010 0           $res->done( $queue );
1011 0           my $ct = $resp->headers->content_type;
1012 0 0         return unless $ct;
1013 0           $ct =~ s/;\s+.*//;
1014 0 0         if( $ct eq 'application/x-ndjson' ) {
1015             # we only handle ndjson currently
1016 0           my $handled_offset = 0;
1017             $resp->on(progress => sub($msg,@) {
1018 0           my $fresh = substr( $msg->body, $handled_offset );
1019 0           my $body = $msg->body;
1020 0           $body =~ s/[^\r\n]+\z//; # Strip any unfinished line
1021 0           $handled_offset = length $body;
1022 0           my @lines = split /\n/, $fresh;
1023 0           for (@lines) {
1024 0           my $payload = decode_json( $_ );
1025 0           $self->validate_response( $payload, $tx );
1026 0           $queue->push(
1027             AI::Ollama::GenerateCompletionResponse->new($payload),
1028              
1029             );
1030             };
1031 0 0         if( $msg->{state} eq 'finished' ) {
1032 0           $queue->finish();
1033             }
1034 0           });
1035             } else {
1036             # Unknown/unhandled content type
1037 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
1038             }
1039             } else {
1040             # An unknown/unhandled response, likely an error
1041 0           $res->fail( sprintf( "unknown_unhandled code %d", $resp->code ), $resp);
1042             }
1043 0           });
1044              
1045 0           my $_tx;
1046 0     0     $tx->res->once( progress => sub($msg, @) {
  0            
  0            
1047 0           $r1->resolve( $tx );
1048 0           undef $_tx;
1049 0           undef $r1;
1050 0           });
1051 0           $self->emit(request => $tx);
1052 0           $_tx = $self->ua->start_p($tx);
1053              
1054 0           return $res
1055             }
1056              
1057             =head2 C<< build_pullModel_request >>
1058              
1059             Build an HTTP request as L object. For the parameters see below.
1060              
1061             =head2 C<< pullModel >>
1062              
1063             use Future::Utils 'repeat';
1064             my $response = $client->pullModel();
1065             my $streamed = $response->get();
1066             repeat {
1067             my ($res) = $streamed->shift;
1068             if( $res ) {
1069             my $str = $res->get;
1070             say $str;
1071             }
1072              
1073             Future::Mojo->done( defined $res );
1074             } until => sub($done) { $done->get };
1075              
1076             Download a model from the ollama library.
1077              
1078             Cancelled pulls are resumed from where they left off, and multiple calls will share the same download progress.
1079              
1080              
1081             =head3 Options
1082              
1083             =over 4
1084              
1085             =item C<< insecure >>
1086              
1087             Allow insecure connections to the library.
1088              
1089             Only use this if you are pulling from your own library during development.
1090              
1091             =item C<< name >>
1092              
1093             The model name.
1094              
1095             Model names follow a C format. Some examples are C and C. The tag is optional and, if not provided, will default to C. The tag is used to identify a specific version.
1096              
1097             =item C<< stream >>
1098              
1099             If C the response will be returned as a single response object, otherwise the response will be streamed as a series of objects.
1100              
1101             =back
1102              
1103             Returns a L<< AI::Ollama::PullModelResponse >> on success.
1104              
1105             =cut
1106              
1107 0     0 1   sub build_pullModel_request( $self, %options ) {
  0            
  0            
  0            
1108 0           my $method = 'POST';
1109 0           my $path = '/pull';
1110 0           my $url = Mojo::URL->new( $self->server . $path );
1111              
1112 0           my $request = AI::Ollama::PullModelRequest->new( \%options )->as_hash;
1113 0           my $tx = $self->ua->build_tx(
1114             $method => $url,
1115             {
1116             'Accept' => 'application/x-ndjson',
1117             "Content-Type" => 'application/json',
1118             }
1119             => json => $request,
1120             );
1121              
1122 0           $self->validate_request( $tx );
1123              
1124 0           return $tx
1125             }
1126              
1127              
1128 0     0 1   sub pullModel( $self, %options ) {
  0            
  0            
  0            
1129 0           my $tx = $self->build_pullModel_request(%options);
1130              
1131              
1132 0           my $res = Future::Mojo->new();
1133              
1134 0           my $r1 = Future::Mojo->new();
1135 0           our @store; # we should use ->retain() instead
1136 0     0     push @store, $r1->then( sub( $tx ) {
  0            
  0            
1137 0           my $resp = $tx->res;
1138 0           $self->emit(response => $resp);
1139             # Should we validate using OpenAPI::Modern here?!
1140 0 0         if( $resp->code == 200 ) {
1141             # Successful operation.
1142 0           my $queue = Future::Queue->new( prototype => 'Future::Mojo' );
1143 0           $res->done( $queue );
1144 0           my $ct = $resp->headers->content_type;
1145 0 0         return unless $ct;
1146 0           $ct =~ s/;\s+.*//;
1147 0 0         if( $ct eq 'application/x-ndjson' ) {
1148             # we only handle ndjson currently
1149 0           my $handled_offset = 0;
1150             $resp->on(progress => sub($msg,@) {
1151 0           my $fresh = substr( $msg->body, $handled_offset );
1152 0           my $body = $msg->body;
1153 0           $body =~ s/[^\r\n]+\z//; # Strip any unfinished line
1154 0           $handled_offset = length $body;
1155 0           my @lines = split /\n/, $fresh;
1156 0           for (@lines) {
1157 0           my $payload = decode_json( $_ );
1158 0           $self->validate_response( $payload, $tx );
1159 0           $queue->push(
1160             AI::Ollama::PullModelResponse->new($payload),
1161              
1162             );
1163             };
1164 0 0         if( $msg->{state} eq 'finished' ) {
1165 0           $queue->finish();
1166             }
1167 0           });
1168             } else {
1169             # Unknown/unhandled content type
1170 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
1171             }
1172             } else {
1173             # An unknown/unhandled response, likely an error
1174 0           $res->fail( sprintf( "unknown_unhandled code %d", $resp->code ), $resp);
1175             }
1176 0           });
1177              
1178 0           my $_tx;
1179 0     0     $tx->res->once( progress => sub($msg, @) {
  0            
  0            
1180 0           $r1->resolve( $tx );
1181 0           undef $_tx;
1182 0           undef $r1;
1183 0           });
1184 0           $self->emit(request => $tx);
1185 0           $_tx = $self->ua->start_p($tx);
1186              
1187 0           return $res
1188             }
1189              
1190             =head2 C<< build_pushModel_request >>
1191              
1192             Build an HTTP request as L object. For the parameters see below.
1193              
1194             =head2 C<< pushModel >>
1195              
1196             my $res = $client->pushModel()->get;
1197              
1198             Upload a model to a model library.
1199              
1200             Requires registering for ollama.ai and adding a public key first.
1201              
1202              
1203             =head3 Options
1204              
1205             =over 4
1206              
1207             =item C<< insecure >>
1208              
1209             Allow insecure connections to the library.
1210              
1211             Only use this if you are pushing to your library during development.
1212              
1213             =item C<< name >>
1214              
1215             The name of the model to push in the form of /:.
1216              
1217             =item C<< stream >>
1218              
1219             If C the response will be returned as a single response object, otherwise the response will be streamed as a series of objects.
1220              
1221             =back
1222              
1223             Returns a L<< AI::Ollama::PushModelResponse >> on success.
1224              
1225             =cut
1226              
1227 0     0 1   sub build_pushModel_request( $self, %options ) {
  0            
  0            
  0            
1228 0           my $method = 'POST';
1229 0           my $path = '/push';
1230 0           my $url = Mojo::URL->new( $self->server . $path );
1231              
1232 0           my $request = AI::Ollama::PushModelRequest->new( \%options )->as_hash;
1233 0           my $tx = $self->ua->build_tx(
1234             $method => $url,
1235             {
1236             'Accept' => 'application/json',
1237             "Content-Type" => 'application/json',
1238             }
1239             => json => $request,
1240             );
1241              
1242 0           $self->validate_request( $tx );
1243              
1244 0           return $tx
1245             }
1246              
1247              
1248 0     0 1   sub pushModel( $self, %options ) {
  0            
  0            
  0            
1249 0           my $tx = $self->build_pushModel_request(%options);
1250              
1251              
1252 0           my $res = Future::Mojo->new();
1253              
1254 0           my $r1 = Future::Mojo->new();
1255 0     0     $r1->then( sub( $tx ) {
  0            
  0            
1256 0           my $resp = $tx->res;
1257 0           $self->emit(response => $resp);
1258             # Should we validate using OpenAPI::Modern here?!
1259 0 0         if( $resp->code == 200 ) {
1260             # Successful operation.
1261 0           my $ct = $resp->headers->content_type;
1262 0           $ct =~ s/;\s+.*//;
1263 0 0         if( $ct eq 'application/json' ) {
1264 0           my $payload = $resp->json();
1265 0           $self->validate_response( $payload, $tx );
1266 0           $res->done(
1267             AI::Ollama::PushModelResponse->new($payload),
1268              
1269             );
1270             } else {
1271             # Unknown/unhandled content type
1272 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
1273             }
1274             } else {
1275             # An unknown/unhandled response, likely an error
1276 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
1277             }
1278 0           })->retain;
1279              
1280             # Start our transaction
1281 0           $self->emit(request => $tx);
1282 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
1283 0           $r1->resolve( $tx );
1284 0           undef $r1;
1285 0     0     })->catch(sub($err) {
  0            
  0            
1286 0           $self->emit(response => $tx, $err);
1287 0           $r1->fail( $err => $tx );
1288 0           undef $r1;
1289 0           });
1290              
1291 0           return $res
1292             }
1293              
1294             =head2 C<< build_showModelInfo_request >>
1295              
1296             Build an HTTP request as L object. For the parameters see below.
1297              
1298             =head2 C<< showModelInfo >>
1299              
1300             my $res = $client->showModelInfo()->get;
1301              
1302             Show details about a model including modelfile, template, parameters, license, and system prompt.
1303              
1304              
1305             =head3 Options
1306              
1307             =over 4
1308              
1309             =item C<< name >>
1310              
1311             The model name.
1312              
1313             Model names follow a C format. Some examples are C and C. The tag is optional and, if not provided, will default to C. The tag is used to identify a specific version.
1314              
1315             =back
1316              
1317             Returns a L<< AI::Ollama::ModelInfo >> on success.
1318              
1319             =cut
1320              
1321 0     0 1   sub build_showModelInfo_request( $self, %options ) {
  0            
  0            
  0            
1322 0           my $method = 'POST';
1323 0           my $path = '/show';
1324 0           my $url = Mojo::URL->new( $self->server . $path );
1325              
1326 0           my $request = AI::Ollama::ModelInfoRequest->new( \%options )->as_hash;
1327 0           my $tx = $self->ua->build_tx(
1328             $method => $url,
1329             {
1330             'Accept' => 'application/json',
1331             "Content-Type" => 'application/json',
1332             }
1333             => json => $request,
1334             );
1335              
1336 0           $self->validate_request( $tx );
1337              
1338 0           return $tx
1339             }
1340              
1341              
1342 0     0 1   sub showModelInfo( $self, %options ) {
  0            
  0            
  0            
1343 0           my $tx = $self->build_showModelInfo_request(%options);
1344              
1345              
1346 0           my $res = Future::Mojo->new();
1347              
1348 0           my $r1 = Future::Mojo->new();
1349 0     0     $r1->then( sub( $tx ) {
  0            
  0            
1350 0           my $resp = $tx->res;
1351 0           $self->emit(response => $resp);
1352             # Should we validate using OpenAPI::Modern here?!
1353 0 0         if( $resp->code == 200 ) {
1354             # Successful operation.
1355 0           my $ct = $resp->headers->content_type;
1356 0           $ct =~ s/;\s+.*//;
1357 0 0         if( $ct eq 'application/json' ) {
1358 0           my $payload = $resp->json();
1359 0           $self->validate_response( $payload, $tx );
1360 0           $res->done(
1361             AI::Ollama::ModelInfo->new($payload),
1362              
1363             );
1364             } else {
1365             # Unknown/unhandled content type
1366 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
1367             }
1368             } else {
1369             # An unknown/unhandled response, likely an error
1370 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
1371             }
1372 0           })->retain;
1373              
1374             # Start our transaction
1375 0           $self->emit(request => $tx);
1376 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
1377 0           $r1->resolve( $tx );
1378 0           undef $r1;
1379 0     0     })->catch(sub($err) {
  0            
  0            
1380 0           $self->emit(response => $tx, $err);
1381 0           $r1->fail( $err => $tx );
1382 0           undef $r1;
1383 0           });
1384              
1385 0           return $res
1386             }
1387              
1388             =head2 C<< build_listModels_request >>
1389              
1390             Build an HTTP request as L object. For the parameters see below.
1391              
1392             =head2 C<< listModels >>
1393              
1394             my $res = $client->listModels()->get;
1395              
1396             List models that are available locally.
1397              
1398              
1399             Returns a L<< AI::Ollama::ModelsResponse >> on success.
1400              
1401             =cut
1402              
1403 0     0 1   sub build_listModels_request( $self, %options ) {
  0            
  0            
  0            
1404 0           my $method = 'GET';
1405 0           my $path = '/tags';
1406 0           my $url = Mojo::URL->new( $self->server . $path );
1407              
1408 0           my $tx = $self->ua->build_tx(
1409             $method => $url,
1410             {
1411             'Accept' => 'application/json',
1412             }
1413             );
1414              
1415 0           $self->validate_request( $tx );
1416              
1417 0           return $tx
1418             }
1419              
1420              
1421 0     0 1   sub listModels( $self, %options ) {
  0            
  0            
  0            
1422 0           my $tx = $self->build_listModels_request(%options);
1423              
1424              
1425 0           my $res = Future::Mojo->new();
1426              
1427 0           my $r1 = Future::Mojo->new();
1428 0     0     $r1->then( sub( $tx ) {
  0            
  0            
1429 0           my $resp = $tx->res;
1430 0           $self->emit(response => $resp);
1431             # Should we validate using OpenAPI::Modern here?!
1432 0 0         if( $resp->code == 200 ) {
1433             # Successful operation.
1434 0           my $ct = $resp->headers->content_type;
1435 0           $ct =~ s/;\s+.*//;
1436 0 0         if( $ct eq 'application/json' ) {
1437 0           my $payload = $resp->json();
1438 0           $self->validate_response( $payload, $tx );
1439 0           $res->done(
1440             AI::Ollama::ModelsResponse->new($payload),
1441              
1442             );
1443             } else {
1444             # Unknown/unhandled content type
1445 0           $res->fail( sprintf("unknown_unhandled content type '%s'", $resp->content_type), $resp );
1446             }
1447             } else {
1448             # An unknown/unhandled response, likely an error
1449 0           $res->fail( sprintf( "unknown_unhandled code %d: %s", $resp->code, $resp->body ), $resp);
1450             }
1451 0           })->retain;
1452              
1453             # Start our transaction
1454 0           $self->emit(request => $tx);
1455 0     0     $tx = $self->ua->start_p($tx)->then(sub($tx) {
  0            
  0            
1456 0           $r1->resolve( $tx );
1457 0           undef $r1;
1458 0     0     })->catch(sub($err) {
  0            
  0            
1459 0           $self->emit(response => $tx, $err);
1460 0           $r1->fail( $err => $tx );
1461 0           undef $r1;
1462 0           });
1463              
1464 0           return $res
1465             }
1466              
1467              
1468 0     0 0   sub validate_response( $self, $payload, $tx ) {
  0            
  0            
  0            
  0            
1469 0 0 0       if( $self->validate_responses
1470             and my $openapi = $self->openapi ) {
1471 0           my $results = $openapi->validate_response($payload, { request => $tx->req });
1472 0 0         if( $results->{error}) {
1473 0           say $results;
1474 0           say $tx->res->to_string;
1475             };
1476             };
1477             }
1478              
1479 0     0 0   sub validate_request( $self, $tx ) {
  0            
  0            
  0            
1480 0 0 0       if( $self->validate_requests
1481             and my $openapi = $self->openapi ) {
1482 0           my $results = $openapi->validate_request($tx->req);
1483 0 0         if( $results->{error}) {
1484 0           say $results;
1485 0           say $tx->req->to_string;
1486             };
1487             };
1488             }
1489              
1490             1;