From d2fc5679ad520684e95e32ac43427131a348680d Mon Sep 17 00:00:00 2001 From: Graham Neubig Date: Wed, 2 Jul 2025 12:27:35 -0400 Subject: [PATCH] Improve rate limit message to indicate automatic retry (#9281) Co-authored-by: openhands --- frontend/src/i18n/declaration.ts | 1 + frontend/src/i18n/translation.json | 44 ++++++++++++++++-------- openhands/controller/agent_controller.py | 15 +++++++- openhands/llm/retry_mixin.py | 24 +++++++++++++ 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/frontend/src/i18n/declaration.ts b/frontend/src/i18n/declaration.ts index 0c78f98529..a740ae33e5 100644 --- a/frontend/src/i18n/declaration.ts +++ b/frontend/src/i18n/declaration.ts @@ -262,6 +262,7 @@ export enum I18nKey { CHAT_INTERFACE$AGENT_RUNNING_MESSAGE = "CHAT_INTERFACE$AGENT_RUNNING_MESSAGE", CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE = "CHAT_INTERFACE$AGENT_AWAITING_USER_INPUT_MESSAGE", CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE = "CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE", + CHAT_INTERFACE$AGENT_RATE_LIMITED_STOPPED_MESSAGE = "CHAT_INTERFACE$AGENT_RATE_LIMITED_STOPPED_MESSAGE", CHAT_INTERFACE$AGENT_PAUSED_MESSAGE = "CHAT_INTERFACE$AGENT_PAUSED_MESSAGE", LANDING$TITLE = "LANDING$TITLE", LANDING$SUBTITLE = "LANDING$SUBTITLE", diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json index 73f87801f3..d787dca9c7 100644 --- a/frontend/src/i18n/translation.json +++ b/frontend/src/i18n/translation.json @@ -4176,20 +4176,36 @@ "uk": "Агент очікує на введення даних від користувача..." }, "CHAT_INTERFACE$AGENT_RATE_LIMITED_MESSAGE": { - "en": "Agent is Rate Limited", - "zh-CN": "智能体已达到速率限制", - "zh-TW": "智慧代理已達到速率限制", - "de": "Agent ist ratenbegrenzt", - "ko-KR": "에이전트가 속도 제한되었습니다", - "no": "Agenten er hastighetsbegrenset", - "it": "L'agente è limitato dalla frequenza", - "pt": "O agente está com limite de taxa", - "es": "El agente está limitado por tasa", - "ar": "الوكيل مقيد بحد السرعة", - "fr": "L'agent est limité en fréquence", - "tr": "Ajan hız sınırına ulaştı", - "ja": "エージェントがレート制限中", - "uk": "Агента обмежено кількістю запитів" + "en": "Agent is Rate Limited. Retrying...", + "zh-CN": "智能体已达到速率限制。正在重试...", + "zh-TW": "智慧代理已達到速率限制。正在重試...", + "de": "Agent ist ratenbegrenzt. Wiederholungsversuch...", + "ko-KR": "에이전트가 속도 제한되었습니다. 재시도 중...", + "no": "Agenten er hastighetsbegrenset. Prøver på nytt...", + "it": "L'agente è limitato dalla frequenza. Riprovando...", + "pt": "O agente está com limite de taxa. Tentando novamente...", + "es": "El agente está limitado por tasa. Reintentando...", + "ar": "الوكيل مقيد بحد السرعة. إعادة المحاولة...", + "fr": "L'agent est limité en fréquence. Nouvelle tentative...", + "tr": "Ajan hız sınırına ulaştı. Yeniden deniyor...", + "ja": "エージェントがレート制限中。再試行しています...", + "uk": "Агента обмежено кількістю запитів. Повторюємо спробу..." + }, + "CHAT_INTERFACE$AGENT_RATE_LIMITED_STOPPED_MESSAGE": { + "en": "Agent is rate-limited. Stopped.", + "zh-CN": "智能体已达到速率限制。已停止。", + "zh-TW": "智慧代理已達到速率限制。已停止。", + "de": "Agent ist ratenbegrenzt. Angehalten.", + "ko-KR": "에이전트가 속도 제한되었습니다. 중지됨.", + "no": "Agenten er hastighetsbegrenset. Stoppet.", + "it": "L'agente è limitato dalla frequenza. Fermato.", + "pt": "O agente está com limite de taxa. Parado.", + "es": "El agente está limitado por tasa. Detenido.", + "ar": "الوكيل مقيد بحد السرعة. توقف.", + "fr": "L'agent est limité en fréquence. Arrêté.", + "tr": "Ajan hız sınırına ulaştı. Durduruldu.", + "ja": "エージェントがレート制限中。停止しました。", + "uk": "Агента обмежено кількістю запитів. Зупинено." }, "CHAT_INTERFACE$AGENT_PAUSED_MESSAGE": { "en": "Agent has paused.", diff --git a/openhands/controller/agent_controller.py b/openhands/controller/agent_controller.py index 661775febd..06587ab5cf 100644 --- a/openhands/controller/agent_controller.py +++ b/openhands/controller/agent_controller.py @@ -275,7 +275,20 @@ class AgentController: err_id = 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION' self.state.last_error = err_id elif isinstance(e, RateLimitError): - await self.set_agent_state_to(AgentState.RATE_LIMITED) + # Check if this is the final retry attempt + if ( + hasattr(e, 'retry_attempt') + and hasattr(e, 'max_retries') + and e.retry_attempt >= e.max_retries + ): + # All retries exhausted, set to ERROR state with a special message + self.state.last_error = ( + 'CHAT_INTERFACE$AGENT_RATE_LIMITED_STOPPED_MESSAGE' + ) + await self.set_agent_state_to(AgentState.ERROR) + else: + # Still retrying, set to RATE_LIMITED state + await self.set_agent_state_to(AgentState.RATE_LIMITED) return self.status_callback('error', err_id, self.state.last_error) diff --git a/openhands/llm/retry_mixin.py b/openhands/llm/retry_mixin.py index 367bcbd97d..5841a5c9ed 100644 --- a/openhands/llm/retry_mixin.py +++ b/openhands/llm/retry_mixin.py @@ -72,6 +72,30 @@ class RetryMixin: def log_retry_attempt(self, retry_state: Any) -> None: """Log retry attempts.""" exception = retry_state.outcome.exception() + + # Add retry attempt and max retries to the exception for later use + if hasattr(retry_state, 'retry_object') and hasattr( + retry_state.retry_object, 'stop' + ): + # Get the max retries from the stop_after_attempt + stop_condition = retry_state.retry_object.stop + + # Handle both single stop conditions and stop_any (combined conditions) + stop_funcs = [] + if hasattr(stop_condition, 'stops'): + # This is a stop_any object with multiple stop conditions + stop_funcs = stop_condition.stops + else: + # This is a single stop condition + stop_funcs = [stop_condition] + + for stop_func in stop_funcs: + if hasattr(stop_func, 'max_attempts'): + # Add retry information to the exception + exception.retry_attempt = retry_state.attempt_number + exception.max_retries = stop_func.max_attempts + break + logger.error( f'{exception}. Attempt #{retry_state.attempt_number} | You can customize retry values in the configuration.', )