Skip to content

Commit 743733b

Browse files
committed
Merge branch 'v1.6.2-clean-695' into v1.6.2
2 parents bef3d19 + 4fa5b5f commit 743733b

2 files changed

Lines changed: 61 additions & 34 deletions

File tree

‎cecli/coders/base_coder.py‎

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
from cecli.linter import Linter
5555
from cecli.llm import litellm
5656
from cecli.mcp import LocalServer
57-
from cecli.models import RETRY_TIMEOUT
5857
from cecli.reasoning_tags import (
5958
REASONING_TAG,
6059
format_reasoning_content,
@@ -2829,24 +2828,14 @@ async def format_in_executor():
28292828
except EmptyResponseError:
28302829
self.io.tool_warning(self.empty_llm_tool_warning())
28312830

2832-
retry_on_empty = False
2833-
retries_config = self.get_active_model().retries
2834-
if isinstance(retries_config, str):
2835-
try:
2836-
retries_config = json.loads(retries_config)
2837-
except json.JSONDecodeError:
2838-
self.io.tool_warning(
2839-
f"Could not parse retries config: {retries_config}"
2840-
)
2841-
retries_config = {}
2842-
if isinstance(retries_config, dict):
2843-
retry_on_empty = retries_config.get("retry_on_empty", False)
2831+
retry_config = models.parse_retry_config(self.get_active_model().retries)
2832+
retry_on_empty = retry_config["retry_on_empty"]
28442833

28452834
if not retry_on_empty:
28462835
break
28472836

2848-
retry_delay *= 2
2849-
if retry_delay > RETRY_TIMEOUT:
2837+
retry_delay *= retry_config["retry_backoff_factor"]
2838+
if retry_delay > retry_config["retry_timeout"]:
28502839
self.io.tool_error("Retry timeout exceeded on empty response.")
28512840
break
28522841

@@ -2866,10 +2855,15 @@ async def format_in_executor():
28662855
exhausted = True
28672856
break
28682857

2858+
retry_config = models.parse_retry_config(self.get_active_model().retries)
2859+
28692860
should_retry = ex_info.retry
2861+
if ex_info.name == "ServiceUnavailableError":
2862+
should_retry = should_retry or retry_config["retry_on_unavailable"]
2863+
28702864
if should_retry:
2871-
retry_delay *= 2
2872-
if retry_delay > RETRY_TIMEOUT:
2865+
retry_delay *= retry_config["retry_backoff_factor"]
2866+
if retry_delay > retry_config["retry_timeout"]:
28732867
should_retry = False
28742868

28752869
if not should_retry:

‎cecli/models.py‎

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
17951828
def register_models(model_settings_fnames):
17961829
files_loaded = []
17971830
for model_settings_fname in model_settings_fnames:

0 commit comments

Comments
 (0)