AI-Anthropic
view release on metacpan or search on metacpan
);
print $claude->message("Hello!");
```
## Streaming
```perl
$claude->chat(
messages => [ { role => 'user', content => 'Tell me a story' } ],
stream => sub {
my ($chunk) = @_;
print $chunk;
STDOUT->flush;
},
);
```
## Vision (Images)
```perl
examples/basic.pl view on Meta::CPAN
# Example 4: Streaming (if you want to see output in real-time)
say "\n4. Streaming:";
say "-" x 30;
say "Streamed response: ";
$claude->chat(
messages => [
{ role => 'user', content => 'Count from 1 to 5, one number per line.' },
],
stream => sub {
my ($chunk) = @_;
print $chunk;
},
);
say "\n";
say "=" x 50;
say "Done!";
lib/AI/Anthropic.pm view on Meta::CPAN
my $response = $claude->chat(
system => 'You are a helpful Perl programmer.',
messages => [
{ 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.
lib/AI/Anthropic.pm view on Meta::CPAN
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,
lib/AI/Anthropic.pm view on Meta::CPAN
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,
);
}
lib/AI/Anthropic.pm view on Meta::CPAN
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),
lib/AI/Anthropic.pm view on Meta::CPAN
}
=head2 models
Returns list of available models:
my @models = $claude->models;
=cut
sub models {
return (
'claude-opus-4-20250514',
'claude-sonnet-4-20250514',
'claude-sonnet-4-5-20250929',
'claude-haiku-4-5-20251001',
'claude-3-5-sonnet-20241022',
'claude-3-5-haiku-20241022',
'claude-3-opus-20240229',
'claude-3-sonnet-20240229',
'claude-3-haiku-20240307',
);
}
# ============================================
# Private methods
# ============================================
sub _normalize_messages {
my ($self, $messages) = @_;
my @normalized;
for my $msg (@$messages) {
my $content = $msg->{content};
# Handle image content
if (ref $content eq 'ARRAY') {
my @parts;
for my $part (@$content) {
lib/AI/Anthropic.pm view on Meta::CPAN
}
push @normalized, { role => $msg->{role}, content => \@parts };
} else {
push @normalized, $msg;
}
}
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';
lib/AI/Anthropic.pm view on Meta::CPAN
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 => {
type => 'url',
url => $url,
},
};
}
sub _request {
my ($self, $body) = @_;
my $response = $self->{_http}->post(
$self->{api_base} . '/v1/messages',
{
headers => $self->_headers,
content => $self->{_json}->encode($body),
}
);
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);
lib/AI/Anthropic.pm view on Meta::CPAN
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})";
lib/AI/Anthropic.pm view on Meta::CPAN
# ============================================
# 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
lib/AI/Anthropic.pm view on Meta::CPAN
required => ['location'],
},
},
],
);
=head2 Streaming
$claude->chat(
messages => [ { role => 'user', content => 'Tell me a story' } ],
stream => sub {
my ($chunk) = @_;
print $chunk;
STDOUT->flush;
},
);
=head1 ENVIRONMENT
=over 4
( run in 0.522 second using v1.01-cache-2.11-cpan-5f4f29bf90f )