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 )