diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 99928547e..95c408f39 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -30,7 +30,7 @@ from aider.llm import litellm from aider.mdstream import MarkdownStream from aider.repo import GitRepo from aider.repomap import RepoMap -from aider.sendchat import send_with_retries +from aider.sendchat import retry_exceptions, send_completion from aider.utils import format_content, format_messages, is_image_file from ..dump import dump # noqa: F401 @@ -70,6 +70,7 @@ class Coder: lint_outcome = None test_outcome = None multi_response_content = "" + partial_response_content = "" partial_response_content = None @@ -895,6 +896,8 @@ class Coder: else: self.mdstream = None + retry_delay = 0.125 + self.usage_report = None exhausted = False interrupted = False @@ -903,6 +906,14 @@ class Coder: try: yield from self.send(messages, functions=self.functions) break + except retry_exceptions() as err: + self.io.tool_error(str(err)) + retry_delay *= 2 + if retry_delay > 60: + break + self.io.tool_output(f"Retrying in {retry_delay:.1f} seconds...") + time.sleep(retry_delay) + continue except KeyboardInterrupt: interrupted = True break @@ -1165,7 +1176,7 @@ class Coder: interrupted = False try: - hash_object, completion = send_with_retries( + hash_object, completion = send_completion( model.name, messages, functions, diff --git a/aider/sendchat.py b/aider/sendchat.py index c1ccbbe0e..e767e29ce 100644 --- a/aider/sendchat.py +++ b/aider/sendchat.py @@ -14,53 +14,28 @@ CACHE = None # CACHE = Cache(CACHE_PATH) +def retry_exceptions(): + import httpx + + return ( + httpx.ConnectError, + httpx.RemoteProtocolError, + httpx.ReadTimeout, + litellm.exceptions.APIConnectionError, + litellm.exceptions.APIError, + litellm.exceptions.RateLimitError, + litellm.exceptions.ServiceUnavailableError, + litellm.exceptions.Timeout, + litellm.exceptions.InternalServerError, + litellm.llms.anthropic.AnthropicError, + ) + + def lazy_litellm_retry_decorator(func): def wrapper(*args, **kwargs): - import httpx - - def should_giveup(e): - if not hasattr(e, "status_code"): - return False - - if type(e) in ( - httpx.ConnectError, - httpx.RemoteProtocolError, - httpx.ReadTimeout, - litellm.exceptions.APIConnectionError, - litellm.exceptions.APIError, - litellm.exceptions.RateLimitError, - litellm.exceptions.ServiceUnavailableError, - litellm.exceptions.Timeout, - litellm.exceptions.InternalServerError, - litellm.llms.anthropic.AnthropicError, - ): - return False - - # These seem to return .status_code = "" - # litellm._should_retry() expects an int and throws a TypeError - # - # litellm.llms.anthropic.AnthropicError - # litellm.exceptions.APIError - if not e.status_code: - return False - - return not litellm._should_retry(e.status_code) - decorated_func = backoff.on_exception( backoff.expo, - ( - httpx.ConnectError, - httpx.RemoteProtocolError, - httpx.ReadTimeout, - litellm.exceptions.APIConnectionError, - litellm.exceptions.APIError, - litellm.exceptions.RateLimitError, - litellm.exceptions.ServiceUnavailableError, - litellm.exceptions.Timeout, - litellm.exceptions.InternalServerError, - litellm.llms.anthropic.AnthropicError, - ), - giveup=should_giveup, + retry_exceptions(), max_time=60, on_backoff=lambda details: print( f"{details.get('exception', 'Exception')}\nRetry in {details['wait']:.1f} seconds." @@ -71,8 +46,7 @@ def lazy_litellm_retry_decorator(func): return wrapper -@lazy_litellm_retry_decorator -def send_with_retries( +def send_completion( model_name, messages, functions, stream, temperature=0, extra_headers=None, max_tokens=None ): from aider.llm import litellm @@ -108,9 +82,10 @@ def send_with_retries( return hash_object, res +@lazy_litellm_retry_decorator def simple_send_with_retries(model_name, messages): try: - _hash, response = send_with_retries( + _hash, response = send_completion( model_name=model_name, messages=messages, functions=None, diff --git a/tests/basic/test_sendchat.py b/tests/basic/test_sendchat.py index 6ac02a47b..f77e687ef 100644 --- a/tests/basic/test_sendchat.py +++ b/tests/basic/test_sendchat.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch import httpx from aider.llm import litellm -from aider.sendchat import send_with_retries +from aider.sendchat import simple_send_with_retries class PrintCalled(Exception): @@ -14,7 +14,7 @@ class PrintCalled(Exception): class TestSendChat(unittest.TestCase): @patch("litellm.completion") @patch("builtins.print") - def test_send_with_retries_rate_limit_error(self, mock_print, mock_completion): + def test_simple_send_with_retries_rate_limit_error(self, mock_print, mock_completion): mock = MagicMock() mock.status_code = 500 @@ -29,19 +29,19 @@ class TestSendChat(unittest.TestCase): None, ] - # Call the send_with_retries method - send_with_retries("model", ["message"], None, False) + # Call the simple_send_with_retries method + simple_send_with_retries("model", ["message"]) mock_print.assert_called_once() @patch("litellm.completion") @patch("builtins.print") - def test_send_with_retries_connection_error(self, mock_print, mock_completion): + def test_simple_send_with_retries_connection_error(self, mock_print, mock_completion): # Set up the mock to raise mock_completion.side_effect = [ httpx.ConnectError("Connection error"), None, ] - # Call the send_with_retries method - send_with_retries("model", ["message"], None, False) + # Call the simple_send_with_retries method + simple_send_with_retries("model", ["message"]) mock_print.assert_called_once()