OpenHands / openhands /llm /retry_mixin.py
Backup-bdg's picture
Upload 964 files
51ff9e5 verified
from typing import Any, Callable
from tenacity import (
retry,
retry_if_exception_type,
stop_after_attempt,
wait_exponential,
)
from openhands.core.exceptions import LLMNoResponseError
from openhands.core.logger import openhands_logger as logger
from openhands.utils.tenacity_stop import stop_if_should_exit
class RetryMixin:
"""Mixin class for retry logic."""
def retry_decorator(self, **kwargs: Any) -> Callable:
"""
Create a LLM retry decorator with customizable parameters. This is used for 429 errors, and a few other exceptions in LLM classes.
Args:
**kwargs: Keyword arguments to override default retry behavior.
Keys: num_retries, retry_exceptions, retry_min_wait, retry_max_wait, retry_multiplier
Returns:
A retry decorator with the parameters customizable in configuration.
"""
num_retries = kwargs.get('num_retries')
retry_exceptions: tuple = kwargs.get('retry_exceptions', ())
retry_min_wait = kwargs.get('retry_min_wait')
retry_max_wait = kwargs.get('retry_max_wait')
retry_multiplier = kwargs.get('retry_multiplier')
retry_listener = kwargs.get('retry_listener')
def before_sleep(retry_state: Any) -> None:
self.log_retry_attempt(retry_state)
if retry_listener:
retry_listener(retry_state.attempt_number, num_retries)
# Check if the exception is LLMNoResponseError
exception = retry_state.outcome.exception()
if isinstance(exception, LLMNoResponseError):
if hasattr(retry_state, 'kwargs'):
# Only change temperature if it's zero or not set
current_temp = retry_state.kwargs.get('temperature', 0)
if current_temp == 0:
retry_state.kwargs['temperature'] = 1.0
logger.warning(
'LLMNoResponseError detected with temperature=0, setting temperature to 1.0 for next attempt.'
)
else:
logger.warning(
f'LLMNoResponseError detected with temperature={current_temp}, keeping original temperature'
)
retry_decorator: Callable = retry(
before_sleep=before_sleep,
stop=stop_after_attempt(num_retries) | stop_if_should_exit(),
reraise=True,
retry=(
retry_if_exception_type(retry_exceptions)
), # retry only for these types
wait=wait_exponential(
multiplier=retry_multiplier,
min=retry_min_wait,
max=retry_max_wait,
),
)
return retry_decorator
def log_retry_attempt(self, retry_state: Any) -> None:
"""Log retry attempts."""
exception = retry_state.outcome.exception()
logger.error(
f'{exception}. Attempt #{retry_state.attempt_number} | You can customize retry values in the configuration.',
)