Class: Agentic::RetryHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/agentic/retry_handler.rb

Overview

Handles retrying operations with configurable backoff strategies

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_retries: 3, retryable_errors: [Errors::LlmTimeoutError, Errors::LlmRateLimitError, Errors::LlmServerError, Errors::LlmNetworkError], backoff_strategy: :exponential, backoff_options: {}, before_retry: nil, after_retry: nil) ⇒ RetryHandler

Initializes a new RetryHandler

Parameters:

  • max_retries (Integer) (defaults to: 3)

    The maximum number of retry attempts

  • retryable_errors (Array<Class, String>) (defaults to: [Errors::LlmTimeoutError, Errors::LlmRateLimitError, Errors::LlmServerError, Errors::LlmNetworkError])

    List of retryable error types/names

  • backoff_strategy (Symbol) (defaults to: :exponential)

    The backoff strategy (:constant, :linear, :exponential)

  • backoff_options (Hash) (defaults to: {})

    Options for the backoff strategy

  • before_retry (Proc, nil) (defaults to: nil)

    Optional block to run before each retry

  • after_retry (Proc, nil) (defaults to: nil)

    Optional block to run after each retry

Options Hash (backoff_options:):

  • :base_delay (Float)

    The base delay in seconds

  • :jitter_factor (Float)

    The jitter factor (0.0-1.0)



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/agentic/retry_handler.rb', line 30

def initialize(
  max_retries: 3,
  retryable_errors: [Errors::LlmTimeoutError, Errors::LlmRateLimitError, Errors::LlmServerError, Errors::LlmNetworkError],
  backoff_strategy: :exponential,
  backoff_options: {},
  before_retry: nil,
  after_retry: nil
)
  @max_retries = max_retries
  @retryable_errors = retryable_errors
  @backoff_strategy = backoff_strategy
  @backoff_options = {
    base_delay: 1.0,
    jitter_factor: 0.25
  }.merge(backoff_options)
  @before_retry = before_retry
  @after_retry = after_retry
end

Instance Attribute Details

#after_retryProc? (readonly)

Returns Optional block to run after each retry.

Returns:

  • (Proc, nil)

    Optional block to run after each retry



19
20
21
# File 'lib/agentic/retry_handler.rb', line 19

def after_retry
  @after_retry
end

#backoff_strategySymbol (readonly)

Returns The backoff strategy to use.

Returns:

  • (Symbol)

    The backoff strategy to use



13
14
15
# File 'lib/agentic/retry_handler.rb', line 13

def backoff_strategy
  @backoff_strategy
end

#before_retryProc? (readonly)

Returns Optional block to run before each retry.

Returns:

  • (Proc, nil)

    Optional block to run before each retry



16
17
18
# File 'lib/agentic/retry_handler.rb', line 16

def before_retry
  @before_retry
end

#max_retriesInteger (readonly)

Returns The maximum number of retry attempts.

Returns:

  • (Integer)

    The maximum number of retry attempts



7
8
9
# File 'lib/agentic/retry_handler.rb', line 7

def max_retries
  @max_retries
end

#retryable_errorsArray<Class, String> (readonly)

Returns List of retryable error types/names.

Returns:

  • (Array<Class, String>)

    List of retryable error types/names



10
11
12
# File 'lib/agentic/retry_handler.rb', line 10

def retryable_errors
  @retryable_errors
end

Instance Method Details

#with_retry(&block) ⇒ Object

Executes the given block with retries

Parameters:

  • block (Proc)

    The block to execute with retries

Returns:

  • (Object)

    The return value of the block

Raises:

  • (StandardError)

    If the block failed after all retries



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/agentic/retry_handler.rb', line 53

def with_retry(&block)
  attempt = 0

  begin
    attempt += 1
    block.call
  rescue => e
    error = e.is_a?(Errors::LlmError) ? e : Errors::LlmError.new(e.message, context: {original_error: e.class.name})

    if retryable?(error) && attempt <= max_retries
      delay = calculate_backoff_delay(attempt)
      Agentic.logger.info("Retry #{attempt}/#{max_retries} for error: #{error.message}. Waiting #{delay.round(2)}s before retrying.")

      @before_retry&.call(attempt: attempt, error: error, delay: delay)
      sleep(delay)
      @after_retry&.call(attempt: attempt, error: error, delay: delay)

      retry
    else
      if attempt > max_retries
        Agentic.logger.error("Max retries (#{max_retries}) exceeded for error: #{error.message}")
      else
        Agentic.logger.error("Non-retryable error: #{error.message}")
      end

      raise error
    end
  end
end