File Coverage

blib/lib/WebService/Ollama/UA/Async.pm
Criterion Covered Total %
statement 32 73 43.8
branch 0 14 0.0
condition 0 4 0.0
subroutine 11 17 64.7
pod 3 5 60.0
total 46 113 40.7


line stmt bran cond sub pod time code
1             package WebService::Ollama::UA::Async;
2              
3 3     3   52 use 5.006;
  3         10  
4 3     3   17 use strict;
  3         5  
  3         88  
5 3     3   13 use warnings;
  3         5  
  3         194  
6              
7             our $VERSION = '0.08';
8              
9 3     3   18 use Moo;
  3         6  
  3         21  
10 3     3   1293 use Future;
  3         6  
  3         79  
11 3     3   2375 use IO::Async::Loop;
  3         140038  
  3         157  
12 3     3   2548 use Net::Async::HTTP;
  3         345924  
  3         173  
13 3     3   31 use JSON::Lines;
  3         15  
  3         55  
14 3     3   732 use MIME::Base64 qw(encode_base64);
  3         893  
  3         238  
15 3     3   24 use URI;
  3         8  
  3         82  
16              
17 3     3   588 use WebService::Ollama::Response;
  3         8  
  3         3113  
18              
19             has json => (
20             is => 'ro',
21             default => sub {
22             JSON::Lines->new( utf8 => 1 );
23             }
24             );
25              
26             has base_url => (
27             is => 'ro',
28             required => 1,
29             );
30              
31             has loop => (
32             is => 'ro',
33             lazy => 1,
34             default => sub { IO::Async::Loop->new },
35             );
36              
37             has http => (
38             is => 'ro',
39             lazy => 1,
40             default => sub {
41             my $self = shift;
42             my $http = Net::Async::HTTP->new(
43             max_connections_per_host => 4,
44             timeout => 300, # 5 min timeout for LLM responses
45             );
46             $self->loop->add($http);
47             return $http;
48             },
49             );
50              
51             sub get {
52 0     0 1   my ($self, %params) = @_;
53 0           return $self->request(type => 'GET', %params);
54             }
55              
56             sub post {
57 0     0 1   my ($self, %params) = @_;
58 0           return $self->request(type => 'POST', %params);
59             }
60              
61             sub delete {
62 0     0 1   my ($self, %params) = @_;
63 0           return $self->request(type => 'DELETE', %params);
64             }
65              
66             sub request {
67 0     0 0   my ($self, %params) = @_;
68              
69 0           my $url = URI->new($self->base_url . $params{url});
70 0   0       my $method = $params{type} // 'GET';
71 0   0       my $data = $params{data} // {};
72              
73             # Remove stream_cb - async doesn't use callbacks the same way
74 0           delete $data->{stream_cb};
75              
76 0           my %request_params = (
77             method => $method,
78             uri => $url,
79             );
80              
81 0 0         if ($method eq 'GET') {
82 0 0         $url->query_form(%$data) if keys %$data;
83 0           $request_params{uri} = $url;
84             } else {
85 0           $request_params{content_type} = 'application/json';
86 0           $request_params{content} = $self->json->encode([$data]);
87             }
88              
89             return $self->http->do_request(%request_params)->then(sub {
90 0     0     my ($response) = @_;
91              
92 0 0         if ($response->is_success) {
93 0           my $content = $response->decoded_content;
94 0           my $decoded = eval { $self->json->decode($content) };
  0            
95 0 0         if ($@) {
96 0           return Future->fail("JSON decode error: $@");
97             }
98              
99             # Handle array vs single response
100 0 0         if (ref($decoded) eq 'ARRAY') {
101             my @responses = map {
102 0           WebService::Ollama::Response->new(%$_)
  0            
103             } @$decoded;
104 0 0         return Future->done(
105             scalar @responses == 1 ? $responses[0] : \@responses
106             );
107             } else {
108 0           return Future->done(
109             WebService::Ollama::Response->new(%$decoded)
110             );
111             }
112             } else {
113 0           return Future->fail(
114             "HTTP error: " . $response->status_line . " - " . $response->decoded_content
115             );
116             }
117 0           });
118             }
119              
120             sub base64_images {
121 0     0 0   my ($self, $images) = @_;
122              
123 0           my @out;
124 0           for my $image (@$images) {
125 0 0         open my $fh, '<:raw', $image or die "Cannot open $image: $!";
126 0           my $content = do { local $/; <$fh> };
  0            
  0            
127 0           close $fh;
128 0           push @out, encode_base64($content, '');
129             }
130 0           return \@out;
131             }
132              
133             1;
134              
135             __END__