Codex.Retry (Codex SDK v0.7.2)

Copy Markdown View Source

Retry logic with configurable backoff strategies.

Strategies

  • :exponential - Exponential backoff (default)
  • :linear - Linear backoff
  • :constant - Fixed delay
  • fun/1 - Custom backoff function

Options

  • :max_attempts - Maximum retry attempts (default: 4)
  • :base_delay_ms - Base delay for backoff (default: 200)
  • :max_delay_ms - Maximum delay cap (default: 10_000)
  • :jitter - Add random jitter (default: true)
  • :retry_if - Predicate to determine if error is retryable
  • :on_retry - Callback invoked on each retry

Example

Codex.Retry.with_retry(fn ->
  make_api_call()
end, max_attempts: 3, strategy: :exponential)

Summary

Functions

Calculates delay for given attempt using configured strategy.

Returns the default options for retry operations.

Default predicate for retryable errors.

Executes function with retry logic.

Wraps an async stream with retry logic.

Types

opts()

@type opts() :: [
  max_attempts: pos_integer(),
  base_delay_ms: non_neg_integer(),
  max_delay_ms: non_neg_integer(),
  jitter: boolean(),
  strategy: strategy(),
  retry_if: (term() -> boolean()),
  on_retry: (attempt :: pos_integer(), error :: term() -> :ok)
]

strategy()

@type strategy() ::
  :exponential
  | :linear
  | :constant
  | (attempt :: pos_integer() -> non_neg_integer())

Functions

calculate_delay(attempt, opts)

@spec calculate_delay(pos_integer(), opts()) :: non_neg_integer()

Calculates delay for given attempt using configured strategy.

Examples

iex> opts = [base_delay_ms: 100, max_delay_ms: 10_000, strategy: :exponential, jitter: false]
iex> Codex.Retry.calculate_delay(1, opts)
100
iex> Codex.Retry.calculate_delay(2, opts)
200
iex> Codex.Retry.calculate_delay(3, opts)
400

default_opts()

@spec default_opts() :: opts()

Returns the default options for retry operations.

Useful for inspecting or modifying default configuration.

Examples

iex> Codex.Retry.default_opts()[:max_attempts]
4

retryable?(arg1)

@spec retryable?(term()) :: boolean()

Default predicate for retryable errors.

Retries on:

  • Timeout errors
  • Connection errors
  • 5xx HTTP errors
  • Rate limit errors (429)
  • Codex.Error with :rate_limit kind
  • Stream errors
  • Codex.TransportError with retryable?: true

Does NOT retry on:

  • Authentication errors
  • Invalid request errors
  • Context window exceeded
  • Unknown error types

Examples

iex> Codex.Retry.retryable?(:timeout)
true
iex> Codex.Retry.retryable?({:http_error, 503})
true
iex> Codex.Retry.retryable?({:http_error, 429})
true
iex> Codex.Retry.retryable?({:http_error, 401})
false
iex> Codex.Retry.retryable?(:auth_failed)
false

with_retry(fun, opts \\ [])

@spec with_retry((-> {:ok, term()} | {:error, term()}), opts()) ::
  {:ok, term()} | {:error, term()}

Executes function with retry logic.

Returns {:ok, result} on success or {:error, reason} after all attempts exhausted.

Options

  • :max_attempts - Maximum number of attempts (default: 4)
  • :base_delay_ms - Base delay in milliseconds (default: 200)
  • :max_delay_ms - Maximum delay cap in milliseconds (default: 10_000)
  • :jitter - Add random jitter to delays (default: true)
  • :strategy - Backoff strategy (default: :exponential)
  • :retry_if - Predicate function to determine if error is retryable
  • :on_retry - Callback invoked before each retry with attempt number and error

Examples

# Basic usage with defaults
Codex.Retry.with_retry(fn -> make_api_call() end)

# Custom configuration
Codex.Retry.with_retry(
  fn -> risky_operation() end,
  max_attempts: 5,
  base_delay_ms: 100,
  strategy: :linear,
  on_retry: fn attempt, error ->
    Logger.warning("Retry #{attempt}: #{inspect(error)}")
  end
)

with_stream_retry(stream_fun, opts \\ [])

@spec with_stream_retry((-> Enumerable.t()), opts()) :: Enumerable.t()

Wraps an async stream with retry logic.

For streaming operations, retries the entire stream from the beginning when a retryable error occurs.

Options

Same as with_retry/2.

Examples

stream = Codex.Retry.with_stream_retry(fn ->
  make_streaming_request()
end, max_attempts: 3)

Enum.each(stream, &process_item/1)