Class: Mistri::RetryPolicy

Inherits:
Object
  • Object
show all
Defined in:
lib/mistri/retry_policy.rb

Overview

When a failed turn is worth retrying, and how long to wait. Transient failures (rate limits, overload, server errors, timeouts, dropped or truncated streams) retry with jittered exponential backoff, honoring the provider's retry-after when it sent one. Everything else — auth, invalid requests, our own bugs — fails fast.

attempts counts retries, not calls: attempts 3 means up to four requests.

Constant Summary collapse

RETRYABLE_STATUSES =
[408, 429, 500, 502, 503, 504, 529].freeze
RETRYABLE_TYPES =
%w[ProviderError RateLimitError OverloadedError ServerError
TruncatedStream EmptyCompletion].freeze
EMPTY_COMPLETION =

What retries see when a completion answers nothing at all.

{
  "type" => "EmptyCompletion",
  "message" => "the provider returned an empty completion"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attempts: 3, base: 1.0, max_delay: 30.0) ⇒ RetryPolicy

Returns a new instance of RetryPolicy.



18
19
20
21
22
# File 'lib/mistri/retry_policy.rb', line 18

def initialize(attempts: 3, base: 1.0, max_delay: 30.0)
  @attempts = attempts
  @base = base
  @max_delay = max_delay
end

Instance Attribute Details

#attemptsObject (readonly)

Returns the value of attribute attempts.



16
17
18
# File 'lib/mistri/retry_policy.rb', line 16

def attempts
  @attempts
end

#baseObject (readonly)

Returns the value of attribute base.



16
17
18
# File 'lib/mistri/retry_policy.rb', line 16

def base
  @base
end

#max_delayObject (readonly)

Returns the value of attribute max_delay.



16
17
18
# File 'lib/mistri/retry_policy.rb', line 16

def max_delay
  @max_delay
end

Instance Method Details

#delay(attempt, retry_after = nil) ⇒ Object



55
56
57
58
59
60
# File 'lib/mistri/retry_policy.rb', line 55

def delay(attempt, retry_after = nil)
  return retry_after.clamp(0.0, max_delay) if retry_after

  exponential = base * (2**(attempt - 1))
  (exponential * rand(0.5..1.0)).clamp(0.0, max_delay)
end

#error_for(message) ⇒ Object

The error a finished attempt carries: the provider's own error, or a synthesized one for a completion that answers nothing at all, which a retry usually clears.



37
38
39
40
41
# File 'lib/mistri/retry_policy.rb', line 37

def error_for(message)
  return message.error if message.stop_reason == StopReason::ERROR

  EMPTY_COMPLETION if empty?(message)
end

#retry?(error, attempt) ⇒ Boolean

Returns:

  • (Boolean)


30
31
32
# File 'lib/mistri/retry_policy.rb', line 30

def retry?(error, attempt)
  attempt <= attempts && retryable?(error)
end

#retryable?(error) ⇒ Boolean

error is the ErrorData hash from an errored message. A status decides when present; otherwise only known-transient types retry, so schema violations and host bugs never loop.

Returns:

  • (Boolean)


46
47
48
49
50
51
52
53
# File 'lib/mistri/retry_policy.rb', line 46

def retryable?(error)
  return false unless error

  status = error["status"]
  return RETRYABLE_STATUSES.include?(status) if status

  RETRYABLE_TYPES.include?(error["type"])
end