diff --git a/ee/lib/gitlab/llm/concerns/exponential_backoff.rb b/ee/lib/gitlab/llm/concerns/exponential_backoff.rb index efa366bc1932e27e3bee8a534d284c656a23bdcf..6e20fcb0c9e673f96268aacec394b6648753e624 100644 --- a/ee/lib/gitlab/llm/concerns/exponential_backoff.rb +++ b/ee/lib/gitlab/llm/concerns/exponential_backoff.rb @@ -46,7 +46,11 @@ def retry_with_exponential_backoff loop do response = yield - return if response.nil? + return unless response.present? + + http_response = response.response + return if http_response.nil? || http_response.body.blank? + raise InternalServerError if response.server_error? && Feature.enabled?(:circuit_breaker, type: :ops) return response unless response.too_many_requests? diff --git a/ee/spec/lib/gitlab/llm/concerns/exponential_backoff_spec.rb b/ee/spec/lib/gitlab/llm/concerns/exponential_backoff_spec.rb index ef6ae1d91f1b21cd47102768761293c1466d3faa..925f75c57fb71fc7ef1b8056b7e7d38917c755d2 100644 --- a/ee/spec/lib/gitlab/llm/concerns/exponential_backoff_spec.rb +++ b/ee/spec/lib/gitlab/llm/concerns/exponential_backoff_spec.rb @@ -3,24 +3,33 @@ require 'spec_helper' RSpec.describe Gitlab::Llm::Concerns::ExponentialBackoff, feature_category: :no_category do # rubocop: disable RSpec/InvalidFeatureCategory + let(:body) { { 'test' => 'test' } } + let(:response) { instance_double(Net::HTTPResponse, body: body.to_json) } let(:success) do instance_double(HTTParty::Response, - code: 200, success?: true, parsed_response: {}, server_error?: false, too_many_requests?: false + code: 200, success?: true, parsed_response: {}, + response: response, server_error?: false, too_many_requests?: false ) end let(:too_many_requests_error) do instance_double(HTTParty::Response, - code: 429, success?: false, parsed_response: {}, server_error?: false, too_many_requests?: true + code: 429, success?: false, parsed_response: {}, + response: response, server_error?: false, too_many_requests?: true ) end let(:auth_error) do instance_double(HTTParty::Response, - code: 401, success?: false, parsed_response: {}, server_error?: false, too_many_requests?: false + code: 401, success?: false, parsed_response: {}, + response: response, server_error?: false, too_many_requests?: false ) end + let(:empty_response) do + instance_double(HTTParty::Response, response: nil) + end + let(:response_caller) { -> { success } } let(:dummy_class) do @@ -108,5 +117,14 @@ def dummy_method(response_caller) end end end + + context 'when the function response is empty' do + it 'does not retry the function' do + allow(response_caller).to receive(:call).and_return(empty_response) + + expect(subject).to be_nil + expect(response_caller).to have_received(:call).once + end + end end end diff --git a/ee/spec/lib/gitlab/llm/open_ai/client_spec.rb b/ee/spec/lib/gitlab/llm/open_ai/client_spec.rb index 9fecc79fd1579948ffcebd9e713dcb90ca8a7b29..12a30ba8b758fa6af23f88a2fe837ea8e0dccaab 100644 --- a/ee/spec/lib/gitlab/llm/open_ai/client_spec.rb +++ b/ee/spec/lib/gitlab/llm/open_ai/client_spec.rb @@ -9,6 +9,7 @@ let(:default_options) { {} } let(:expected_options) { {} } let(:options) { {} } + let(:response) { instance_double(Net::HTTPResponse, body: example_response.to_json) } let(:example_response) do { 'model' => 'model', @@ -37,11 +38,13 @@ end let(:response_double) do - instance_double(HTTParty::Response, code: 200, success?: true, parsed_response: example_response) + instance_double(HTTParty::Response, code: 200, success?: true, + response: response, parsed_response: example_response) end let(:moderation_response_double) do - instance_double(HTTParty::Response, code: 200, success?: true, parsed_response: moderation_response) + instance_double(HTTParty::Response, code: 200, success?: true, + response: response, parsed_response: moderation_response) end around do |ex|