Langertha
view release on metacpan or search on metacpan
lib/Langertha/Role/AnthropicCompatible.pm view on Meta::CPAN
package Langertha::Role::AnthropicCompatible;
# ABSTRACT: Role for Anthropic-compatible API format
our $VERSION = '0.505';
use Moose::Role;
use Carp qw( croak carp );
use JSON::MaybeXS;
use Langertha::ToolChoice;
use Langertha::Tool;
use Langertha::Response;
use Langertha::ToolCall;
sub _build_reasoning_wire_format { 'anthropic' }
sub _build_cache_wire_format { 'anthropic' }
sub default_response_size { 1024 }
sub content_format { 'anthropic' }
has api_key => (
is => 'ro',
lazy_build => 1,
);
sub _build_api_key {
my ( $self ) = @_;
return croak "".(ref $self)." requires api_key to be set";
}
has api_version => (
is => 'ro',
lazy_build => 1,
);
sub _build_api_version { '2023-06-01' }
has effort => (
is => 'ro',
isa => 'Str',
predicate => 'has_effort',
);
has inference_geo => (
is => 'ro',
isa => 'Str',
predicate => 'has_inference_geo',
);
sub update_request {
my ( $self, $request ) = @_;
$request->header('x-api-key', $self->api_key);
$request->header('content-type', 'application/json');
$request->header('anthropic-version', $self->api_version);
}
sub chat_request {
my ( $self, $messages, %extra ) = @_;
# Canonical per-request controls (chat_f, karr #46) beat the engine
# attributes on a per-key basis; the rest of %extra passes straight through.
my $controls = delete $extra{controls} // {};
# Structured output. Engines whose wire has native structured output
# (Engine::Anthropic, via _native_structured_output) emit it as
# output_config.format and leave the content JSON on the wire (chat_response
# returns it verbatim). Engines without it (the legacy /anthropic shims)
# keep the ADR 0005 synthesized-tool + forced tool_choice rewrite, whose
# tool_use input chat_response lifts back into content.
my $rf_routed = 0;
my $output_config_format;
if ( $self->_native_structured_output_for_model ) {
my $rf = $self->_take_response_format(\%extra, $controls);
# A schema-bearing response_format goes native (output_config.format); the
# first-party validator rejects an open schema, so it is normalized to a
# closed one first (k182/k149). A bare json_object has no schema and the
# Messages API has no native free-form JSON form, so it routes through the
# synthesized-tool path (open, non-strict input_schema) exactly like the
# legacy /anthropic shims -- forced where the model allows it, degraded to
# tool_choice `auto` where forced tool use 400s (fable-5-1 / mythos-5-1).
if ( $self->_rf_is_native_schema($rf) ) {
$output_config_format = $self->_response_format_to_output_config($rf);
}
else {
# // 0 keeps $rf_routed defined (a non-honorable rf yields undef) so
# chat_response does not fall back to the has_response_format heuristic.
$rf_routed = $self->_response_format_via_tool($rf, \%extra) // 0;
}
}
else {
$rf_routed = $self->_translate_response_format(\%extra, $controls);
}
$self->_normalize_tool_params(\%extra, $controls);
my @msgs;
my $system = "";
for my $message (@{$messages}) {
if ($message->{role} eq 'system') {
$system .= "\n\n" if length $system;
$system .= $message->{content};
} else {
push @msgs, $message;
}
}
if ($system and scalar @msgs == 0) {
push @msgs, {
role => 'user',
content => $system,
};
$system = undef;
}
lib/Langertha/Role/AnthropicCompatible.pm view on Meta::CPAN
C<parallel_tool_use>, C<has_response_format> / C<response_format>) â normally
provided by extending L<Langertha::Engine::Remote> plus the universal roles
composed in L<Langertha::Engine::AnthropicBase>.
The wire envelope mirrors L<Langertha::Role::OpenAICompatible>: this role owns
the Anthropic request/response/stream/auth/rate-limit envelope, and the base
class stays a thin composition shell.
=head2 api_key
Anthropic-compatible API key sent as C<x-api-key>. Subclasses typically
override C<_build_api_key> to read a provider-specific environment variable.
=head2 api_version
The Anthropic API version header sent with every request. Defaults to
C<2023-06-01>.
=head2 effort
Back-compat alias of L<Langertha::Role::ReasoningEffort/reasoning_effort>.
Controls the depth of thinking for reasoning models. When set (and
C<reasoning_effort> is not), it seeds C<reasoning_effort>, which is serialized
via L<Langertha::Reasoning> to C<output_config.effort> plus
C<thinking: { type =E<gt> 'adaptive' }> (the current Messages-API shape) rather
than the legacy top-level C<effort> key.
my $claude = Langertha::Engine::Anthropic->new(
api_key => $ENV{ANTHROPIC_API_KEY},
model => 'claude-opus-4-8',
effort => 'high', # same as reasoning_effort => 'high'
);
=head2 inference_geo
Controls data residency for inference on the first-party Claude API. The API
accepts exactly two values: C<global> (the default) and C<us>. There is B<no>
EU inference-geo on the first-party API; a value such as C<eu> is not part of
the enum and is rejected. It is only honoured on Claude 4.6+ models; older
models return a 400 regardless of the value.
my $claude = Langertha::Engine::Anthropic->new(
api_key => $ENV{ANTHROPIC_API_KEY},
inference_geo => 'us',
);
The response reports where the request actually ran via
C<usage.inference_geo>, and C<us> residency is billed at 1.1x the base rate.
EU data residency is not available this way. For EU-hosted inference use one of
the EU engines Langertha already ships â L<Langertha::Engine::AKI>,
L<Langertha::Engine::Mistral>, L<Langertha::Engine::Scaleway>,
L<Langertha::Engine::TSystems> or L<Langertha::Engine::Hetzner> â or reach
Claude through the regional endpoints of Amazon Bedrock or Google Vertex AI,
where C<inference_geo> does not apply.
=head2 update_request
$self->update_request($http_request);
Adds the C<x-api-key>, C<content-type: application/json>, and
C<anthropic-version> headers to outgoing requests.
=head2 chat_request
my $request = $engine->chat_request($messages, %extra);
Generates an Anthropic-format message request (C<POST /v1/messages>).
Includes model, messages, max_tokens, temperature, reasoning-effort and
prompt-cache controls, and optional C<system>. Returns an HTTP request
object.
=head2 _native_structured_output
Internal predicate. True when the engine's wire supports native structured
output via C<output_config.format> (the first-party Claude Messages API); false
(the default) for the legacy C</anthropic> shim engines, which fall back to the
ADR 0005 synthesized-tool rewrite. L<Langertha::Engine::Anthropic> overrides it
to a true value. It describes the B<endpoint> and so decides the manifest
dialect (C<anthropic> vs C<anthropic-compat>, L<Langertha::Manifest::Builder>);
the request builders ask L</_native_structured_output_for_model> instead.
=head2 _native_structured_output_for_model
Internal predicate the request builders use to pick the structured-output path
for the current C<chat_model>: native C<output_config.format> when true, the
ADR 0005 synthesized-tool rewrite when false. Defaults to
L</_native_structured_output>; L<Langertha::Engine::MoonshotAnthropic>
overrides it to be true on C<kimi-k3> only.
=head2 _translate_response_format
Internal: turns a C<response_format> hash into a synthesized tool plus a
C<tool_choice>, returning the synthetic tool name. Returns C<undef> when no
usable structure is present. Used by the legacy C</anthropic> shim engines and,
for a bare C<json_object>, by first-party L<Langertha::Engine::Anthropic> (which
routes a C<json_schema> natively via C<output_config.format> instead). The
synthesized tool is forced via a named C<tool_choice> where the model supports
forced tool use, and degraded to C<tool_choice> C<auto> where it does not
(C<claude-fable-5-1> / C<claude-mythos-5-1>).
=head2 _normalize_tool_params
Internal: normalizes C<tool_choice> to Anthropic's native format and folds
C<parallel_tool_use> into the C<tool_choice> block as C<disable_parallel_tool_use>.
=head2 chat_response
my $response = $role->chat_response($http_response, $rf_routed);
Parses an Anthropic-format message response into a L<Langertha::Response>
object. When C<$rf_routed> (a synthetic tool name, or truthy for the
attribute path) and tool calls are present, lifts the first tool_use
arguments back into C<content> as JSON.
A 200 body that is an error envelope (C<{"type":"error","error":{...}}>), or
carries an C<error> object and no content, croaks with
C<< <engine class> response carried an error: MESSAGE >>, as the
OpenAI-compatible parser does.
=head2 _usage_input_includes_cache
( run in 1.232 second using v1.01-cache-2.11-cpan-99fa8243a42 )