2121import json
2222import keyword
2323import os
24+ import random
2425import re
2526import time
2627from functools import lru_cache
@@ -516,20 +517,25 @@ def __init__(
516517 self ,
517518 max_attempts : int = 1 ,
518519 wait_seconds : float = 0.0 ,
520+ exponential_base : float = 2.0 ,
521+ jitter : bool = True ,
519522 retry_predicate : Callable [[BaseException ], bool ] | None = None ,
520523 reraise : bool = False ,
521524 before_sleep_logger : tuple [Logger , int ] | None = None ,
522525 after_logger : tuple [Logger , int ] | None = None ,
523526 ):
524527 self .max_attempts = max_attempts
525528 self .wait_seconds = wait_seconds
529+ self .exponential_base = exponential_base
530+ self .jitter = jitter
526531 self .retry_predicate = retry_predicate
527532 self .reraise = reraise
528533 self .before_sleep_logger = before_sleep_logger
529534 self .after_logger = after_logger
530535
531536 def __call__ (self , fn , * args : Any , ** kwargs : Any ) -> Any :
532537 start_time = time .time ()
538+ delay = self .wait_seconds
533539
534540 for attempt_number in range (1 , self .max_attempts + 1 ):
535541 try :
@@ -542,7 +548,7 @@ def __call__(self, fn, *args: Any, **kwargs: Any) -> Any:
542548 fn_name = getattr (fn , "__name__" , repr (fn ))
543549 logger .log (
544550 log_level ,
545- f"Finished call to '{ fn_name } ' after { seconds :.3f} (s), this was attempt n°{ attempt_number } ." ,
551+ f"Finished call to '{ fn_name } ' after { seconds :.3f} (s), this was attempt n°{ attempt_number } / { self . max_attempts } ." ,
546552 )
547553
548554 return result
@@ -564,18 +570,22 @@ def __call__(self, fn, *args: Any, **kwargs: Any) -> Any:
564570 fn_name = getattr (fn , "__name__" , repr (fn ))
565571 logger .log (
566572 log_level ,
567- f"Finished call to '{ fn_name } ' after { seconds :.3f} (s), this was attempt n°{ attempt_number } ." ,
573+ f"Finished call to '{ fn_name } ' after { seconds :.3f} (s), this was attempt n°{ attempt_number } / { self . max_attempts } ." ,
568574 )
569575
576+ # Exponential backoff with jitter
577+ # https://cookbook.openai.com/examples/how_to_handle_rate_limits#example-3-manual-backoff-implementation
578+ delay *= self .exponential_base * (1 + self .jitter * random .random ())
579+
570580 # Log before sleeping
571581 if self .before_sleep_logger :
572582 logger , log_level = self .before_sleep_logger
573583 fn_name = getattr (fn , "__name__" , repr (fn ))
574584 logger .log (
575585 log_level ,
576- f"Retrying { fn_name } in { self . wait_seconds } seconds as it raised { e .__class__ .__name__ } : { e } ." ,
586+ f"Retrying { fn_name } in { delay } seconds as it raised { e .__class__ .__name__ } : { e } ." ,
577587 )
578588
579589 # Sleep before next attempt
580- if self . wait_seconds > 0 :
581- time .sleep (self . wait_seconds )
590+ if delay > 0 :
591+ time .sleep (delay )
0 commit comments