@@ -1452,21 +1452,10 @@ async def send_completion(
14521452 litellm_ex = LiteLLMExceptions ()
14531453 retry_delay = 0.125
14541454
1455- if self .retries :
1456- retry_config = dict ()
1457- try :
1458- retry_config = json .loads (self .retries )
1459- except (json .JSONDecodeError , TypeError , ValueError ):
1460- retry_config = dict ()
1461- pass
1462-
1463- self .retry_on_unavailable = bool (
1464- nested .getter (retry_config , "retry-on-unavailable" , True )
1465- )
1466- self .retry_backoff_factor = float (
1467- nested .getter (retry_config , "retry-backoff-factor" , 1.5 )
1468- )
1469- self .retry_timeout = float (nested .getter (retry_config , "retry-timeout" , 30 ))
1455+ retry_config = parse_retry_config (self .retries )
1456+ self .retry_on_unavailable = retry_config ["retry_on_unavailable" ]
1457+ self .retry_backoff_factor = retry_config ["retry_backoff_factor" ]
1458+ self .retry_timeout = retry_config ["retry_timeout" ]
14701459
14711460 while True :
14721461 try :
@@ -1557,6 +1546,11 @@ async def simple_send_with_retries(
15571546 temperature = None
15581547 tools = None
15591548
1549+ retry_config = parse_retry_config (self .retries )
1550+ retry_backoff_factor = retry_config ["retry_backoff_factor" ]
1551+ retry_timeout = retry_config ["retry_timeout" ]
1552+ retry_on_unavailable = retry_config ["retry_on_unavailable" ]
1553+
15601554 if self .verbose :
15611555 dump (messages )
15621556
@@ -1607,14 +1601,17 @@ async def simple_send_with_retries(
16071601 if ex_info .description :
16081602 print (ex_info .description )
16091603 should_retry = ex_info .retry
1604+ if ex_info .name == "ServiceUnavailableError" :
1605+ should_retry = should_retry or retry_on_unavailable
1606+
16101607 custom_retry_delay = self ._extract_retry_delay (err )
16111608 if custom_retry_delay is not None :
16121609 retry_delay = custom_retry_delay
16131610 should_retry = True
16141611 elif should_retry :
1615- retry_delay *= 2
1612+ retry_delay *= retry_backoff_factor
16161613
1617- if retry_delay > RETRY_TIMEOUT :
1614+ if retry_delay > retry_timeout :
16181615 should_retry = False
16191616
16201617 if not should_retry :
@@ -1792,6 +1789,42 @@ def _configured_provider(self) -> str:
17921789 return provider
17931790
17941791
1792+ def parse_retry_config (retries_input ):
1793+ """
1794+ Parse and normalize retry configuration from a JSON string or dict.
1795+ Returns a unified dict with defaults:
1796+ retry_timeout: 30
1797+ retry_backoff_factor: 1.5
1798+ retry_on_unavailable: True
1799+ retry_on_empty: False
1800+ """
1801+ config = dict ()
1802+ if isinstance (retries_input , str ):
1803+ try :
1804+ config = json .loads (retries_input )
1805+ except (json .JSONDecodeError , TypeError , ValueError ):
1806+ config = dict ()
1807+ elif isinstance (retries_input , dict ):
1808+ config = retries_input .copy ()
1809+
1810+ # Helper to get either hyphenated or underscored key
1811+ def _get (key , default ):
1812+ val = config .get (key )
1813+ if val is not None :
1814+ return val
1815+ val = config .get (key .replace ("_" , "-" ))
1816+ if val is not None :
1817+ return val
1818+ return default
1819+
1820+ return {
1821+ "retry_timeout" : float (_get ("retry_timeout" , 30 )),
1822+ "retry_backoff_factor" : float (_get ("retry_backoff_factor" , 1.5 )),
1823+ "retry_on_unavailable" : bool (_get ("retry_on_unavailable" , True )),
1824+ "retry_on_empty" : bool (_get ("retry_on_empty" , False )),
1825+ }
1826+
1827+
17951828def register_models (model_settings_fnames ):
17961829 files_loaded = []
17971830 for model_settings_fname in model_settings_fnames :
0 commit comments