AI-Anthropic
view release on metacpan or search on metacpan
Revision history for Perl extension AI::Anthropic
0.01 2025-11-25
- Initial release
- Messages API support
- Streaming support
- Vision (image) support
- Tool use (function calling) support
- Support for all Claude models
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)
MANIFEST.SKIP view on Meta::CPAN
# Git
\.git/
\.gitignore
# Build artifacts
^Makefile$
^Makefile\.old$
^MYMETA\.
^blib/
^pm_to_blib$
\.o$
\.bs$
# Editor files
~$
\.swp$
\.bak$
\.orig$
# OS files
\.DS_Store$
Thumbs\.db$
# IDE
\.vscode/
\.idea/
# Test/dev files
^\.prove$
^cover_db/
{
"abstract" : "Perl interface to Anthropic Claude API",
"author" : [
"Your Name <your@email.com>"
],
"dynamic_config" : 1,
"generated_by" : "ExtUtils::MakeMaker version 7.76, CPAN::Meta::Converter version 2.150010",
"keywords" : [
"anthropic",
"claude",
"ai",
"llm",
"api",
"chatbot",
"gpt",
"language-model"
],
"license" : [
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
"version" : 2
},
"name" : "AI-Anthropic",
"no_index" : {
"directory" : [
"t",
"inc"
]
},
"prereqs" : {
"build" : {
"requires" : {
"ExtUtils::MakeMaker" : "0"
}
},
"configure" : {
"requires" : {
"ExtUtils::MakeMaker" : "0"
}
},
"runtime" : {
"requires" : {
"Carp" : "0",
"HTTP::Tiny" : "0.070",
"JSON::PP" : "2.0",
"MIME::Base64" : "0",
"perl" : "5.010"
}
},
"test" : {
"requires" : {
"Test::More" : "0.88"
}
}
},
"release_status" : "stable",
"resources" : {
"bugtracker" : {
"web" : "https://github.com/yourusername/AI-Anthropic/issues"
},
"repository" : {
"type" : "git",
"url" : "https://github.com/yourusername/AI-Anthropic.git",
"web" : "https://github.com/yourusername/AI-Anthropic"
}
},
"version" : "0.01",
"x_serialization_backend" : "JSON::PP version 4.16"
}
---
abstract: 'Perl interface to Anthropic Claude API'
author:
- 'Your Name <your@email.com>'
build_requires:
ExtUtils::MakeMaker: '0'
Test::More: '0.88'
configure_requires:
ExtUtils::MakeMaker: '0'
dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 7.76, CPAN::Meta::Converter version 2.150010'
keywords:
- anthropic
- claude
- ai
- llm
- api
- chatbot
- gpt
- language-model
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
version: '1.4'
name: AI-Anthropic
no_index:
directory:
- t
- inc
requires:
Carp: '0'
HTTP::Tiny: '0.070'
JSON::PP: '2.0'
MIME::Base64: '0'
perl: '5.010'
resources:
bugtracker: https://github.com/yourusername/AI-Anthropic/issues
repository: https://github.com/yourusername/AI-Anthropic.git
version: '0.01'
x_serialization_backend: 'CPAN::Meta::YAML version 0.020'
Makefile.PL view on Meta::CPAN
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'AI::Anthropic',
VERSION_FROM => 'lib/AI/Anthropic.pm',
ABSTRACT => 'Perl interface to Anthropic Claude API',
AUTHOR => 'Your Name <your@email.com>',
LICENSE => 'perl_5',
MIN_PERL_VERSION => '5.010',
PREREQ_PM => {
'HTTP::Tiny' => '0.070',
'JSON::PP' => '2.0',
'MIME::Base64' => '0',
'Carp' => '0',
},
TEST_REQUIRES => {
'Test::More' => '0.88',
},
META_MERGE => {
'meta-spec' => { version => 2 },
resources => {
repository => {
type => 'git',
url => 'https://github.com/yourusername/AI-Anthropic.git',
web => 'https://github.com/yourusername/AI-Anthropic',
},
bugtracker => {
web => 'https://github.com/yourusername/AI-Anthropic/issues',
},
},
keywords => [
'anthropic', 'claude', 'ai', 'llm', 'api',
'chatbot', 'gpt', 'language-model',
],
},
);
# AI::Anthropic
Perl interface to Anthropic's Claude API.
## Synopsis
```perl
use AI::Anthropic;
my $claude = AI::Anthropic->new(
api_key => 'sk-ant-api03-your-key-here',
);
# Simple message
print $claude->message("What is the meaning of life?");
# With system prompt
my $response = $claude->chat(
system => 'You are a helpful Perl programmer.',
messages => [
{ role => 'user', content => 'How do I read a file?' },
],
);
print "Response: ", $response->text, "\n";
print "Tokens: ", $response->total_tokens, "\n";
```
## Installation
From CPAN:
```bash
cpanm AI::Anthropic
```
Or manually:
```bash
perl Makefile.PL
make
make test
make install
```
## Features
- **Messages API** - Full support for Claude chat completions
- **Streaming** - Real-time response streaming with callbacks
- **Vision** - Send images (from files, URLs, or base64)
- **Tool Use** - Function calling support
- **All Models** - Claude 4 Opus, Sonnet, Haiku and older models
## Quick Start
```perl
use AI::Anthropic;
my $claude = AI::Anthropic->new(
api_key => 'sk-ant-api03-your-key-here',
);
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
# From file
my $response = $claude->chat(
messages => [
{
role => 'user',
content => [
{ type => 'text', text => 'What is in this image?' },
{ type => 'image', path => '/path/to/image.jpg' },
],
},
],
);
# From URL
my $response = $claude->chat(
messages => [
{
role => 'user',
content => [
{ type => 'text', text => 'Describe this image' },
{ type => 'image', url => 'https://example.com/image.png' },
],
},
],
);
```
## Tool Use (Function Calling)
```perl
my $response = $claude->chat(
messages => [
{ role => 'user', content => 'What is the weather in Baku?' },
],
tools => [
{
name => 'get_weather',
description => 'Get current weather for a location',
input_schema => {
type => 'object',
properties => {
location => {
type => 'string',
description => 'City name',
},
},
required => ['location'],
},
},
],
);
```
## Response Object
```perl
my $response = $claude->message("Hello");
$response->text; # Response text
$response->model; # Model used
$response->stop_reason; # Why generation stopped
$response->input_tokens; # Tokens in prompt
$response->output_tokens; # Tokens in response
$response->total_tokens; # Total tokens
$response->raw_response; # Full API response hashref
# Stringifies to text
print "$response";
```
## Configuration
```perl
my $claude = AI::Anthropic->new(
api_key => 'sk-ant-...', # or use ANTHROPIC_API_KEY env
model => 'claude-opus-4-20250514', # default: claude-sonnet-4-20250514
max_tokens => 8192, # default: 4096
timeout => 300, # default: 120 seconds
);
```
## Available Models
```perl
my @models = $claude->models;
# claude-opus-4-20250514
# claude-sonnet-4-20250514
# claude-sonnet-4-5-20250929
# claude-haiku-4-5-20251001
# claude-3-5-sonnet-20241022
# ... and more
```
## Environment Variables
- `ANTHROPIC_API_KEY` - Your Anthropic API key
## Dependencies
- Perl 5.10+
- HTTP::Tiny
- JSON::PP
- MIME::Base64
All dependencies are core modules or widely available on CPAN.
## Why This Module?
- **Pure Perl** - No XS, works everywhere
- **Minimal dependencies** - Uses core modules where possible
- **Perlish API** - Feels natural to Perl programmers
- **Full featured** - Streaming, vision, tools - all supported
- **Well documented** - POD and examples included
## See Also
- [Anthropic API Documentation](https://docs.anthropic.com/)
- [OpenAI::API](https://metacpan.org/pod/OpenAI::API) - Similar module for OpenAI
## Contributing
Pull requests welcome! Please include tests for new features.
## 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.
## Author
Your Name <your@email.com>
examples/basic.pl view on Meta::CPAN
#!/usr/bin/perl
# Example: Basic usage of AI::Anthropic
#
# Replace 'your-api-key-here' with your actual Anthropic API key
# Get your key at: https://console.anthropic.com/settings/keys
use strict;
use warnings;
use 5.010;
use FindBin;
use lib "$FindBin::Bin/../lib";
use AI::Anthropic;
# Create client with your API key
my $claude = AI::Anthropic->new(
api_key => 'sk-ant-api03-your-key-here', # <-- PUT YOUR KEY HERE
);
say "=" x 50;
say "AI::Anthropic Example";
say "=" x 50;
# Example 1: Simple message
say "\n1. Simple message:";
say "-" x 30;
my $response = $claude->message("What is Perl? Answer in 2 sentences.");
say "Response: $response";
say "Tokens used: " . $response->total_tokens;
# Example 2: Chat with system prompt
say "\n2. Chat with system prompt:";
say "-" x 30;
$response = $claude->chat(
system => 'You are a grumpy Perl programmer who loves one-liners.',
messages => [
{ role => 'user', content => 'How do I reverse a string?' },
],
);
say "Response: $response";
# Example 3: Multi-turn conversation
say "\n3. Multi-turn conversation:";
say "-" x 30;
$response = $claude->chat(
messages => [
{ role => 'user', content => 'My name is Vugar.' },
{ role => 'assistant', content => 'Nice to meet you, Vugar!' },
{ role => 'user', content => 'What is my name?' },
],
);
say "Response: $response";
# 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
package AI::Anthropic;
use strict;
use warnings;
use 5.010;
our $VERSION = '0.01';
use Carp qw(croak);
use JSON::PP;
use HTTP::Tiny;
use MIME::Base64 qw(encode_base64);
# Constants
use constant {
API_BASE => 'https://api.anthropic.com',
API_VERSION => '2023-06-01',
DEFAULT_MODEL => 'claude-sonnet-4-20250514',
};
=head1 NAME
AI::Anthropic - Perl interface to Anthropic's Claude API
=head1 SYNOPSIS
use AI::Anthropic;
my $claude = AI::Anthropic->new(
api_key => 'sk-ant-api03-your-key-here',
);
# Simple message
my $response = $claude->message("What is the capital of France?");
print $response; # prints response text
# Chat with history
my $response = $claude->chat(
messages => [
{ role => 'user', content => 'Hello!' },
{ role => 'assistant', content => 'Hello! How can I help you today?' },
{ role => 'user', content => 'What is 2+2?' },
],
);
# With system prompt
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.
=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
if (my $stream_cb = $args{stream}) {
return $self->_stream_request($body, $stream_cb);
} else {
return $self->_request($body);
}
}
=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) {
if ($part->{type} eq 'image' && $part->{path}) {
# Load image from file
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;
}
}
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 => {
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);
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;
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',
},
},
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
=item ANTHROPIC_API_KEY
Your Anthropic API key. Can be set instead of passing api_key to new().
=back
=head1 SEE ALSO
L<https://docs.anthropic.com/> - Anthropic API documentation
L<OpenAI::API> - Similar module for OpenAI
=head1 AUTHOR
Your Name <your@email.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.
=cut
t/01-basic.t view on Meta::CPAN
#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
# Test loading
use_ok('AI::Anthropic');
# Test object creation without API key (should fail gracefully later)
{
local $ENV{ANTHROPIC_API_KEY};
eval {
my $claude = AI::Anthropic->new();
};
like($@, qr/API key required/, 'Dies without API key');
}
# Test object creation with API key
{
my $claude = AI::Anthropic->new(api_key => 'test-key-123');
isa_ok($claude, 'AI::Anthropic');
}
# Test models list
{
my $claude = AI::Anthropic->new(api_key => 'test');
my @models = $claude->models;
ok(@models > 0, 'models() returns list');
ok(grep { /claude/ } @models, 'models contain claude');
}
# Test response object
{
my $response = AI::Anthropic::Response->new(
text => 'Hello!',
model => 'claude-sonnet-4-20250514',
stop_reason => 'end_turn',
usage => { input_tokens => 10, output_tokens => 5 },
);
is($response->text, 'Hello!', 'Response text');
is($response->model, 'claude-sonnet-4-20250514', 'Response model');
is($response->input_tokens, 10, 'Input tokens');
is($response->output_tokens, 5, 'Output tokens');
is($response->total_tokens, 15, 'Total tokens');
# Test stringification
is("$response", 'Hello!', 'Response stringifies to text');
}
done_testing();
tree_structure.txt view on Meta::CPAN
AI-Anthropic/
âââ lib/
â âââ AI/
â âââ Anthropic.pm # ÐлавнÑй модÑлÑ
â âââ Anthropic/
â âââ Client.pm # HTTP-клиенÑ
â âââ Message.pm # ÐлаÑÑ ÑообÑениÑ
â âââ Response.pm # ÐлаÑÑ Ð¾ÑвеÑа
â âââ Stream.pm # Streaming поддеÑжка
â âââ Error.pm # ÐбÑабоÑка оÑибок
âââ t/ # ТеÑÑÑ
âââ examples/ # ÐÑимеÑÑ
âââ Makefile.PL
âââ README.md
âââ Changes
( run in 1.401 second using v1.01-cache-2.11-cpan-39bf76dae61 )