Langertha
view release on metacpan or search on metacpan
lib/Langertha/Engine/Gemini.pm view on Meta::CPAN
# Convert role: 'assistant' -> 'model' for Gemini
my $role = $message->{role} eq 'assistant' ? 'model' : $message->{role};
push @gemini_contents, {
role => $role,
parts => [{ text => $message->{content} }],
};
}
}
# Build the URL with model and API key
my $model_name = $self->chat_model;
my $url = $self->url . "/v1beta/models/${model_name}:generateContent?key=" . $self->api_key;
my %request_body = (
contents => \@gemini_contents,
);
# Add system instruction if present
if ($system_instruction) {
$request_body{systemInstruction} = {
parts => [{ text => $system_instruction }],
};
}
# Add generation config
my %generation_config;
if ($self->get_response_size) {
$generation_config{maxOutputTokens} = $self->get_response_size;
}
if ($self->has_temperature) {
$generation_config{temperature} = $self->temperature;
}
# Translate response_format -> Gemini's generationConfig.responseSchema /
# responseMimeType. Accepts the OpenAI-shape response_format hash so that
# callers can hand the same payload to any engine.
if ( $self->has_response_format ) {
my $rf = $self->response_format;
my $type = ref($rf) eq 'HASH' ? ( $rf->{type} // '' ) : '';
if ( $type eq 'json_object' ) {
$generation_config{responseMimeType} = 'application/json';
}
elsif ( $type eq 'json_schema'
&& ref( $rf->{json_schema} ) eq 'HASH'
&& ref( $rf->{json_schema}{schema} ) eq 'HASH' ) {
$generation_config{responseMimeType} = 'application/json';
$generation_config{responseSchema} = $rf->{json_schema}{schema};
}
}
$request_body{generationConfig} = \%generation_config if %generation_config;
return $self->generate_http_request(
POST => $url,
sub { $self->chat_response(shift) },
%request_body,
%extra,
);
}
sub update_request {
my ( $self, $request ) = @_;
$request->header('content-type', 'application/json');
}
sub chat_response {
my ( $self, $response ) = @_;
my $data = $self->parse_response($response);
# Gemini response format: candidates[0].content.parts[].text
my $candidates = $data->{candidates} || [];
my $text = '';
my $finish_reason;
my $thinking;
if (@$candidates) {
my $candidate = $candidates->[0];
my $content = $candidate->{content} || {};
my $parts = $content->{parts} || [];
my @text_parts;
my @thought_parts;
for my $part (@$parts) {
next unless exists $part->{text};
if ($part->{thought}) {
push @thought_parts, $part->{text};
} else {
push @text_parts, $part->{text};
}
}
$text = join('', @text_parts);
$thinking = join("\n", @thought_parts) if @thought_parts;
$finish_reason = $candidate->{finishReason};
}
# Normalize Gemini usage metadata
my $usage;
if (my $um = $data->{usageMetadata}) {
$usage = {
prompt_tokens => $um->{promptTokenCount},
completion_tokens => $um->{candidatesTokenCount},
total_tokens => $um->{totalTokenCount},
};
}
my @tcs = Langertha::ToolCall->extract($data);
return Langertha::Response->new(
content => $text,
raw => $data,
$data->{modelVersion} ? ( model => $data->{modelVersion} ) : (),
defined $finish_reason ? ( finish_reason => $finish_reason ) : (),
$usage ? ( usage => $usage ) : (),
defined $thinking ? ( thinking => $thinking ) : (),
@tcs ? ( tool_calls => [ @tcs ] ) : (),
);
}
sub stream_format { 'sse' }
sub chat_stream_request {
my ( $self, $messages, %extra ) = @_;
# Same tool_choice translation as chat_request.
( run in 2.388 seconds using v1.01-cache-2.11-cpan-f56aa216473 )