AI-Anthropic

 view release on metacpan or  search on metacpan

MANIFEST  view on Meta::CPAN

AI-Anthropic-0.01.tar.gz
Changes
examples/basic.pl
lib/AI/Anthropic.pm
Makefile.PL
MANIFEST			This list of files
MANIFEST.SKIP
README.md
t/01-basic.t
tree_structure.txt
META.yml                                 Module YAML meta-data (added by MakeMaker)
META.json                                Module JSON meta-data (added by MakeMaker)

lib/AI/Anthropic.pm  view on Meta::CPAN

                    push @parts, $self->_image_from_file($part->{path});
                } elsif ($part->{type} eq 'image' && $part->{url}) {
                    # Load image from URL
                    push @parts, $self->_image_from_url($part->{url});
                } elsif ($part->{type} eq 'image' && $part->{base64}) {
                    push @parts, {
                        type   => 'image',
                        source => {
                            type         => 'base64',
                            media_type   => $part->{media_type} // 'image/png',
                            data         => $part->{base64},
                        },
                    };
                } else {
                    push @parts, $part;
                }
            }
            push @normalized, { role => $msg->{role}, content => \@parts };
        } else {
            push @normalized, $msg;
        }

lib/AI/Anthropic.pm  view on Meta::CPAN

    
    return \@normalized;
}

sub _image_from_file {
    my ($self, $path) = @_;
    
    open my $fh, '<:raw', $path
        or croak "Cannot open image file '$path': $!";
    local $/;
    my $data = <$fh>;
    close $fh;
    
    # Detect media type
    my $media_type = 'image/png';
    if ($path =~ /\.jpe?g$/i) {
        $media_type = 'image/jpeg';
    } elsif ($path =~ /\.gif$/i) {
        $media_type = 'image/gif';
    } elsif ($path =~ /\.webp$/i) {
        $media_type = 'image/webp';
    }
    
    return {
        type   => 'image',
        source => {
            type       => 'base64',
            media_type => $media_type,
            data       => encode_base64($data, ''),
        },
    };
}

sub _image_from_url {
    my ($self, $url) = @_;
    
    return {
        type   => 'image',
        source => {

lib/AI/Anthropic.pm  view on Meta::CPAN

    
    return $self->_handle_response($response);
}

sub _stream_request {
    my ($self, $body, $callback) = @_;
    
    $body->{stream} = \1;  # JSON true
    
    my $full_text = '';
    my $response_data;
    
    # HTTP::Tiny doesn't support streaming well, so we use a data callback
    my $response = $self->{_http}->post(
        $self->{api_base} . '/v1/messages',
        {
            headers      => $self->_headers,
            content      => $self->{_json}->encode($body),
            data_callback => sub {
                my ($chunk, $res) = @_;
                
                # Parse SSE events
                for my $line (split /\n/, $chunk) {
                    next unless $line =~ /^data: (.+)/;
                    my $data = $1;
                    next if $data eq '[DONE]';
                    
                    eval {
                        my $event = $self->{_json}->decode($data);
                        
                        if ($event->{type} eq 'content_block_delta') {
                            my $text = $event->{delta}{text} // '';
                            $full_text .= $text;
                            $callback->($text) if $callback;
                        } elsif ($event->{type} eq 'message_stop') {
                            $response_data = $event;
                        }
                    };
                }
            },
        }
    );
    
    unless ($response->{success}) {
        return $self->_handle_response($response);
    }
    
    # Return a response object with the full text
    return AI::Anthropic::Response->new(
        text         => $full_text,
        raw_response => $response_data,
    );
}

sub _headers {
    my ($self) = @_;
    
    return {
        'Content-Type'      => 'application/json',
        'x-api-key'         => $self->{api_key},
        'anthropic-version' => API_VERSION,
    };
}

sub _handle_response {
    my ($self, $response) = @_;
    
    my $data;
    eval {
        $data = $self->{_json}->decode($response->{content});
    };
    
    unless ($response->{success}) {
        my $error_msg = $data->{error}{message} // $response->{content} // 'Unknown error';
        croak "Anthropic API error: $error_msg (status: $response->{status})";
    }
    
    return AI::Anthropic::Response->new(
        text          => $data->{content}[0]{text} // '',
        role          => $data->{role},
        model         => $data->{model},
        stop_reason   => $data->{stop_reason},
        usage         => $data->{usage},
        raw_response  => $data,
    );
}

# ============================================
# Response class
# ============================================

package AI::Anthropic::Response;

use strict;



( run in 0.203 second using v1.01-cache-2.11-cpan-5f4f29bf90f )