AI-Anthropic

 view release on metacpan or  search on metacpan

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

            { role => 'user', content => 'How do I read a file?' },
        ],
    );

    # Streaming
    $claude->chat(
        messages => [ { role => 'user', content => 'Tell me a story' } ],
        stream   => sub {
            my ($chunk) = @_;
            print $chunk;
        },
    );

=head1 DESCRIPTION

AI::Anthropic provides a Perl interface to Anthropic's Claude API.
It supports all Claude models including Claude 4 Opus, Claude 4 Sonnet,
and Claude Haiku.

=head1 METHODS


=head1 AUTHOR

Vugar Bakhshaliyev <d7951500@gmail.com>

=head1 LICENSE

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head2 new

    my $claude = AI::Anthropic->new(
        api_key     => 'your-api-key',      # required (or use ANTHROPIC_API_KEY env)
        model       => 'claude-sonnet-4-20250514',  # optional
        max_tokens  => 4096,                 # optional
        timeout     => 120,                  # optional, seconds
    );

=cut

sub new {
    my ($class, %args) = @_;
    
    my $api_key = $args{api_key} // $ENV{ANTHROPIC_API_KEY}
        or croak "API key required. Set api_key parameter or ANTHROPIC_API_KEY environment variable";
    
    my $self = {
        api_key     => $api_key,
        model       => $args{model}      // DEFAULT_MODEL,
        max_tokens  => $args{max_tokens} // 4096,
        timeout     => $args{timeout}    // 120,
        api_base    => $args{api_base}   // API_BASE,
        _http       => HTTP::Tiny->new(
            timeout => $args{timeout} // 120,
        ),
        _json       => JSON::PP->new->utf8->allow_nonref,
    };
    
    return bless $self, $class;
}

=head2 message

Simple interface for single message:

    my $response = $claude->message("Your question here");
    my $response = $claude->message("Your question", system => "You are helpful");
    
    print $response->text;

=cut

sub message {
    my ($self, $content, %opts) = @_;
    
    croak "Message content required" unless defined $content;
    
    return $self->chat(
        messages => [ { role => 'user', content => $content } ],
        %opts,
    );
}

=head2 chat

Full chat interface:

    my $response = $claude->chat(
        messages    => \@messages,       # required
        system      => $system_prompt,   # optional
        model       => $model,           # optional, overrides default
        max_tokens  => $max_tokens,      # optional
        temperature => 0.7,              # optional, 0.0-1.0
        stream      => \&callback,       # optional, for streaming
        tools       => \@tools,          # optional, for function calling
    );

=cut

sub chat {
    my ($self, %args) = @_;
    
    my $messages = $args{messages}
        or croak "messages parameter required";
    
    # Build request body
    my $body = {
        model      => $args{model}      // $self->{model},
        max_tokens => $args{max_tokens} // $self->{max_tokens},
        messages   => $self->_normalize_messages($messages),
    };
    
    # Optional parameters
    $body->{system}      = $args{system}      if defined $args{system};
    $body->{temperature} = $args{temperature} if defined $args{temperature};
    $body->{tools}       = $args{tools}       if defined $args{tools};
    $body->{tool_choice} = $args{tool_choice} if defined $args{tool_choice};
    
    # Streaming or regular request

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

            },
        }
    );
    
    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;
use warnings;
use overload '""' => \&text, fallback => 1;

sub new {
    my ($class, %args) = @_;
    return bless \%args, $class;
}

sub text         { shift->{text}         }
sub role         { shift->{role}         }
sub model        { shift->{model}        }
sub stop_reason  { shift->{stop_reason}  }
sub usage        { shift->{usage}        }
sub raw_response { shift->{raw_response} }

sub input_tokens  { shift->{usage}{input_tokens}  // 0 }
sub output_tokens { shift->{usage}{output_tokens} // 0 }
sub total_tokens  { 
    my $self = shift;
    return $self->input_tokens + $self->output_tokens;
}

1;

__END__

=head1 EXAMPLES

=head2 Basic usage

    use AI::Anthropic;
    
    my $claude = AI::Anthropic->new;
    print $claude->message("Hello, Claude!");

=head2 With image (vision)

    my $response = $claude->chat(
        messages => [
            {
                role    => 'user',
                content => [
                    { type => 'text', text => 'What is in this image?' },
                    { type => 'image', path => '/path/to/image.jpg' },
                ],
            },
        ],
    );

=head2 Tool use (function calling)

    my $response = $claude->chat(
        messages => [
            { role => 'user', content => 'What is the weather in London?' },
        ],
        tools => [
            {
                name        => 'get_weather',
                description => 'Get current weather for a location',
                input_schema => {
                    type       => 'object',
                    properties => {
                        location => {
                            type        => 'string',
                            description => 'City name',
                        },



( run in 0.700 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )