diff --git a/src/multi_swarm/agents/hypothesis.py b/src/multi_swarm/agents/hypothesis.py index 49ee4bc..7eec368 100644 --- a/src/multi_swarm/agents/hypothesis.py +++ b/src/multi_swarm/agents/hypothesis.py @@ -3,6 +3,8 @@ from __future__ import annotations import re from dataclasses import dataclass, field +import openai + from ..genome.hypothesis import HypothesisAgentGenome from ..llm.client import CompletionResult, EmptyCompletionError, LLMClient from ..protocol.parser import ParseError, Strategy, parse_strategy @@ -282,6 +284,12 @@ class HypothesisAgent: errors.append(f"empty_completion: {e}") last_raw = "" continue + except openai.RateLimitError as e: + # Provider upstream rate limited oltre i retry tenacity. + # Marca genome come fallito senza propagare l'eccezione al run. + errors.append(f"rate_limit: {e}") + last_raw = "" + continue completions.append(completion) last_raw = completion.text diff --git a/src/multi_swarm/llm/client.py b/src/multi_swarm/llm/client.py index 1ea0183..f7c58cb 100644 --- a/src/multi_swarm/llm/client.py +++ b/src/multi_swarm/llm/client.py @@ -26,11 +26,14 @@ class EmptyCompletionError(RuntimeError): pass -# Errori transient: retry. RateLimit/Auth/InvalidRequest: NO retry. +# Errori transient: retry. Auth/InvalidRequest: NO retry. +# RateLimitError (HTTP 429) ora retryable: provider OpenRouter come DeepInfra +# applicano rate limit upstream temporaneo, recuperabile con backoff. _RETRYABLE_EXCEPTIONS: tuple[type[BaseException], ...] = ( openai.APIConnectionError, openai.APITimeoutError, openai.InternalServerError, + openai.RateLimitError, EmptyCompletionError, ) @@ -79,8 +82,8 @@ class LLMClient: self._client = OpenAI(api_key=openrouter_api_key, base_url=openrouter_base_url) @retry( - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1.0, min=2.0, max=10.0), + stop=stop_after_attempt(5), + wait=wait_exponential(multiplier=2.0, min=2.0, max=30.0), retry=retry_if_exception_type(_RETRYABLE_EXCEPTIONS), reraise=True, ) diff --git a/tests/unit/test_llm_client.py b/tests/unit/test_llm_client.py index 32a2677..8514319 100644 --- a/tests/unit/test_llm_client.py +++ b/tests/unit/test_llm_client.py @@ -75,7 +75,7 @@ def test_completion_retries_on_connection_error(mocker): with pytest.raises(openai.APIConnectionError): client.complete(g, system="sys", user="usr") - assert fake_openai.chat.completions.create.call_count == 3 + assert fake_openai.chat.completions.create.call_count == 5 def test_completion_uses_custom_model_tier_c(mocker):