Current section

Files

Jump to
gemini_ex lib gemini rate_limiter config.ex
Raw

lib/gemini/rate_limiter/config.ex

defmodule Gemini.RateLimiter.Config do
@moduledoc """
Configuration management for the rate limiter.
Provides configuration defaults and profile-based settings for rate limiting,
concurrency gating, and retry behavior.
## Configuration Options
- `:max_concurrency_per_model` - Maximum concurrent requests per model (default: 4, nil/0 disables)
- `:max_attempts` - Maximum retry attempts (default: 3)
- `:base_backoff_ms` - Base backoff duration in milliseconds (default: 1000)
- `:jitter_factor` - Jitter factor for backoff (default: 0.25)
- `:non_blocking` - Return immediately with retry_at instead of waiting (default: false)
- `:disable_rate_limiter` - Disable all rate limiting (default: false)
- `:adaptive_concurrency` - Enable adaptive concurrency (default: false)
- `:adaptive_ceiling` - Maximum concurrency when adaptive mode is enabled (default: 8)
- `:profile` - Configuration profile (:dev | :prod | :custom)
## Profiles
- `:dev` - Lower concurrency (2), longer backoff, more verbose
- `:prod` - Higher concurrency (4), optimized for throughput
- `:custom` - Uses only explicitly provided settings
"""
@type profile :: :dev | :prod | :custom
@type t :: %__MODULE__{
max_concurrency_per_model: non_neg_integer() | nil,
max_attempts: pos_integer(),
base_backoff_ms: pos_integer(),
jitter_factor: float(),
non_blocking: boolean(),
disable_rate_limiter: boolean(),
adaptive_concurrency: boolean(),
adaptive_ceiling: pos_integer(),
profile: profile()
}
defstruct max_concurrency_per_model: 4,
max_attempts: 3,
base_backoff_ms: 1000,
jitter_factor: 0.25,
non_blocking: false,
disable_rate_limiter: false,
adaptive_concurrency: false,
adaptive_ceiling: 8,
profile: :prod
@profiles %{
dev: %{
max_concurrency_per_model: 2,
max_attempts: 5,
base_backoff_ms: 2000,
adaptive_ceiling: 4
},
prod: %{
max_concurrency_per_model: 4,
max_attempts: 3,
base_backoff_ms: 1000,
adaptive_ceiling: 8
}
}
@doc """
Build a configuration struct from application config and overrides.
## Parameters
- `overrides` - Keyword list of configuration overrides
## Examples
config = Config.build(max_concurrency_per_model: 8, profile: :prod)
"""
@spec build(keyword()) :: t()
def build(overrides \\ []) do
# Get base config from application environment
app_config = Application.get_env(:gemini_ex, :rate_limiter, [])
# Merge in order: defaults -> profile -> app_config -> overrides
profile = Keyword.get(overrides, :profile) || Keyword.get(app_config, :profile, :prod)
profile_config = Map.get(@profiles, profile, %{})
base =
%__MODULE__{}
|> struct(profile_config)
|> struct(Enum.into(app_config, %{}))
|> struct(Enum.into(overrides, %{}))
# Normalize nil/0 concurrency to disabled
%{base | max_concurrency_per_model: normalize_concurrency(base.max_concurrency_per_model)}
end
@doc """
Check if rate limiting is enabled.
"""
@spec enabled?(t()) :: boolean()
def enabled?(%__MODULE__{disable_rate_limiter: disabled}), do: not disabled
@doc """
Check if concurrency gating is enabled.
"""
@spec concurrency_enabled?(t()) :: boolean()
def concurrency_enabled?(%__MODULE__{max_concurrency_per_model: max}) do
is_integer(max) and max > 0
end
@doc """
Check if adaptive concurrency is enabled.
"""
@spec adaptive_enabled?(t()) :: boolean()
def adaptive_enabled?(%__MODULE__{adaptive_concurrency: adaptive}), do: adaptive
@doc """
Get profile-specific configuration.
"""
@spec profile_config(profile()) :: map()
def profile_config(profile), do: Map.get(@profiles, profile, %{})
# Private helpers
defp normalize_concurrency(nil), do: nil
defp normalize_concurrency(0), do: nil
defp normalize_concurrency(n) when is_integer(n) and n > 0, do: n
defp normalize_concurrency(_), do: 4
end