diff --git a/wsai/backends/claude.py b/wsai/backends/claude.py index 3064ab3..9ab71fc 100644 --- a/wsai/backends/claude.py +++ b/wsai/backends/claude.py @@ -29,6 +29,13 @@ from ..interfaces import Frame, Reply, ScreenObservation # this identity string; the real persona/instructions go in later blocks. _CLAUDE_CODE_ID = "You are Claude Code, Anthropic's official CLI for Claude." +# Claude occasionally returns 529 Overloaded; the anthropic SDK retries >=500 +# (and 429) with exponential backoff, but its default of 2 tries can be too few +# to ride out a busy window. Bump it so a transient overload doesn't drop the +# voice turn to the apology fallback. Kept modest so a *sustained* overload +# still fails fast rather than leaving the bot silent for many seconds. +_MAX_RETRIES = int(os.environ.get("WSAI_BRAIN_MAX_RETRIES", "4")) + def _load_oauth_token() -> str | None: path = os.environ.get("CLAUDE_CREDENTIALS_PATH") @@ -59,7 +66,7 @@ class _Auth: key = self._explicit_key or os.environ.get("ANTHROPIC_API_KEY") if key: if self._client is None: - self._client = self._anthropic.AsyncAnthropic(api_key=key) + self._client = self._anthropic.AsyncAnthropic(api_key=key, max_retries=_MAX_RETRIES) self._oauth = False return self._client tok = _load_oauth_token() @@ -70,7 +77,7 @@ class _Auth: ) if tok != self._token: self._token = tok - self._client = self._anthropic.AsyncAnthropic(auth_token=tok) + self._client = self._anthropic.AsyncAnthropic(auth_token=tok, max_retries=_MAX_RETRIES) self._oauth = True return self._client diff --git a/wsai/dashboard.py b/wsai/dashboard.py index b0ca831..c876492 100644 --- a/wsai/dashboard.py +++ b/wsai/dashboard.py @@ -304,6 +304,10 @@ class Dashboard: except Exception as exc: # noqa: BLE001 log.exception("brain failed") self.monitor.log("error", f"두뇌 응답 실패: {exc}") + blob = f"{getattr(exc, 'status_code', '')} {exc}".lower() + if "529" in blob or "overload" in blob: + # Transient server overload survived the SDK retries. + return "지금 서버가 잠깐 붐벼서 생각이 늦네. 잠시 뒤에 다시 말해줄래?" return "미안, 지금 잠깐 생각이 안 났어. 다시 말해줄래?" if not text: return "음, 뭐라고 해야 할지 모르겠어. 다시 말해줄래?"