From cd9fabf17a1061eb3ceac44b8a786d47e0a30ef9 Mon Sep 17 00:00:00 2001 From: Plentiful106 Date: Wed, 7 May 2025 02:17:00 +0000 Subject: [PATCH 1/5] Implement Nonce Error Handling and Recovery --- .../prometheus_swarm/utils/errors.py | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/agent-framework/prometheus_swarm/utils/errors.py b/agent-framework/prometheus_swarm/utils/errors.py index 8e2959c4..2655d37f 100644 --- a/agent-framework/prometheus_swarm/utils/errors.py +++ b/agent-framework/prometheus_swarm/utils/errors.py @@ -1,4 +1,8 @@ -"""Error type for API errors.""" +"""Error types for API and nonce-related errors.""" + +import logging +import time +from typing import Optional, Callable class ClientAPIError(Exception): @@ -14,3 +18,59 @@ def __init__(self, original_error: Exception): super().__init__(original_error.message) else: super().__init__(str(original_error)) + + +class NonceError(Exception): + """Raised when a nonce (number used once) is invalid or has been reused.""" + + def __init__(self, message: str, current_nonce: Optional[str] = None): + """ + Initialize NonceError with a descriptive message and optional current nonce. + + Args: + message (str): Description of the nonce error + current_nonce (Optional[str]): The problematic nonce value + """ + self.current_nonce = current_nonce + super().__init__(message) + + +def nonce_error_handler( + func: Callable, + max_retries: int = 3, + retry_delay: float = 1.0, + logger: Optional[logging.Logger] = None +) -> Callable: + """ + Decorator to handle nonce-related errors with automatic recovery and retries. + + Args: + func (Callable): The function to wrap + max_retries (int): Maximum number of retry attempts + retry_delay (float): Delay between retry attempts in seconds + logger (Optional[logging.Logger]): Logger for tracking retry attempts + + Returns: + Callable: Wrapped function with nonce error handling + """ + def wrapper(*args, **kwargs): + retries = 0 + while retries < max_retries: + try: + return func(*args, **kwargs) + except NonceError as e: + retries += 1 + if logger: + logger.warning( + f"Nonce error encountered: {e}. " + f"Retry attempt {retries}/{max_retries}" + ) + + if retries >= max_retries: + raise + + time.sleep(retry_delay * (2 ** retries)) # Exponential backoff + + raise RuntimeError("Max nonce error retries exceeded") + + return wrapper \ No newline at end of file From 83d6bd0edc34aab500506fe05a2e4b014426a97a Mon Sep 17 00:00:00 2001 From: Plentiful106 Date: Wed, 7 May 2025 02:17:11 +0000 Subject: [PATCH 2/5] Add tests for Nonce Error Handling --- .../tests/unit/utils/test_nonce_errors.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 agent-framework/tests/unit/utils/test_nonce_errors.py diff --git a/agent-framework/tests/unit/utils/test_nonce_errors.py b/agent-framework/tests/unit/utils/test_nonce_errors.py new file mode 100644 index 00000000..15179a63 --- /dev/null +++ b/agent-framework/tests/unit/utils/test_nonce_errors.py @@ -0,0 +1,59 @@ +"""Tests for nonce error handling and recovery.""" + +import logging +import pytest +from prometheus_swarm.utils.errors import NonceError, nonce_error_handler + + +def test_nonce_error_initialization(): + """Test NonceError can be created with message and optional nonce.""" + error = NonceError("Invalid nonce", "abc123") + assert str(error) == "Invalid nonce" + assert error.current_nonce == "abc123" + + +def test_nonce_error_without_nonce(): + """Test NonceError can be created without specifying a nonce.""" + error = NonceError("Nonce reused") + assert str(error) == "Nonce reused" + assert error.current_nonce is None + + +def test_nonce_error_handler_success(): + """Test nonce error handler successfully recovers from nonce errors.""" + mock_attempts = [0] + + @nonce_error_handler + def mock_function(): + mock_attempts[0] += 1 + if mock_attempts[0] < 3: + raise NonceError("Simulated nonce error") + return "Success" + + result = mock_function() + assert result == "Success" + assert mock_attempts[0] == 3 + + +def test_nonce_error_handler_max_retries(): + """Test nonce error handler raises error after max retries.""" + @nonce_error_handler(max_retries=2) + def mock_function(): + raise NonceError("Persistent nonce error") + + with pytest.raises(RuntimeError, match="Max nonce error retries exceeded"): + mock_function() + + +def test_nonce_error_handler_with_logger(caplog): + """Test nonce error handler works with a logger.""" + caplog.set_level(logging.WARNING) + + @nonce_error_handler(logger=logging.getLogger()) + def mock_function(): + raise NonceError("Logged nonce error") + + with pytest.raises(RuntimeError): + mock_function() + + assert "Nonce error encountered: Logged nonce error" in caplog.text \ No newline at end of file From b54c8f709390e8e5403aebf43d631b14c066ea67 Mon Sep 17 00:00:00 2001 From: Plentiful106 Date: Wed, 7 May 2025 02:17:39 +0000 Subject: [PATCH 3/5] Fix nonce error handler to support flexible decorator usage --- .../prometheus_swarm/utils/errors.py | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/agent-framework/prometheus_swarm/utils/errors.py b/agent-framework/prometheus_swarm/utils/errors.py index 2655d37f..c152df1b 100644 --- a/agent-framework/prometheus_swarm/utils/errors.py +++ b/agent-framework/prometheus_swarm/utils/errors.py @@ -2,7 +2,8 @@ import logging import time -from typing import Optional, Callable +from functools import wraps +from typing import Optional, Callable, Any, Union class ClientAPIError(Exception): @@ -36,41 +37,49 @@ def __init__(self, message: str, current_nonce: Optional[str] = None): def nonce_error_handler( - func: Callable, + func: Optional[Callable] = None, + *, max_retries: int = 3, retry_delay: float = 1.0, logger: Optional[logging.Logger] = None -) -> Callable: +) -> Union[Callable, Any]: """ Decorator to handle nonce-related errors with automatic recovery and retries. Args: - func (Callable): The function to wrap + func (Optional[Callable]): The function to wrap max_retries (int): Maximum number of retry attempts retry_delay (float): Delay between retry attempts in seconds logger (Optional[logging.Logger]): Logger for tracking retry attempts Returns: - Callable: Wrapped function with nonce error handling + Wrapped function with nonce error handling """ - def wrapper(*args, **kwargs): - retries = 0 - while retries < max_retries: - try: - return func(*args, **kwargs) - except NonceError as e: - retries += 1 - if logger: - logger.warning( - f"Nonce error encountered: {e}. " - f"Retry attempt {retries}/{max_retries}" - ) - - if retries >= max_retries: - raise - - time.sleep(retry_delay * (2 ** retries)) # Exponential backoff + def decorator(func_to_wrap: Callable) -> Callable: + @wraps(func_to_wrap) + def wrapper(*args, **kwargs): + retries = 0 + while retries < max_retries: + try: + return func_to_wrap(*args, **kwargs) + except NonceError as e: + retries += 1 + if logger: + logger.warning( + f"Nonce error encountered: {e}. " + f"Retry attempt {retries}/{max_retries}" + ) + + if retries >= max_retries: + raise + + time.sleep(retry_delay * (2 ** retries)) # Exponential backoff + + raise RuntimeError("Max nonce error retries exceeded") - raise RuntimeError("Max nonce error retries exceeded") - - return wrapper \ No newline at end of file + return wrapper + + # Support using decorator with or without arguments + if func is None: + return decorator + return decorator(func) \ No newline at end of file From f306b4b4357fe28df4f0a55ea173972c2b6ccf4a Mon Sep 17 00:00:00 2001 From: Plentiful106 Date: Wed, 7 May 2025 02:18:10 +0000 Subject: [PATCH 4/5] Refine nonce error test --- agent-framework/tests/unit/utils/test_nonce_errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent-framework/tests/unit/utils/test_nonce_errors.py b/agent-framework/tests/unit/utils/test_nonce_errors.py index 15179a63..fff5ea9b 100644 --- a/agent-framework/tests/unit/utils/test_nonce_errors.py +++ b/agent-framework/tests/unit/utils/test_nonce_errors.py @@ -53,7 +53,7 @@ def test_nonce_error_handler_with_logger(caplog): def mock_function(): raise NonceError("Logged nonce error") - with pytest.raises(RuntimeError): + with pytest.raises(RuntimeError, match="Max nonce error retries exceeded"): mock_function() assert "Nonce error encountered: Logged nonce error" in caplog.text \ No newline at end of file From 976063493d9ac88d141efe9da2fd61fc59ea10c9 Mon Sep 17 00:00:00 2001 From: Plentiful106 Date: Wed, 7 May 2025 02:18:44 +0000 Subject: [PATCH 5/5] Refine nonce error handler to include stacktrace --- agent-framework/prometheus_swarm/utils/errors.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/agent-framework/prometheus_swarm/utils/errors.py b/agent-framework/prometheus_swarm/utils/errors.py index c152df1b..4d63344e 100644 --- a/agent-framework/prometheus_swarm/utils/errors.py +++ b/agent-framework/prometheus_swarm/utils/errors.py @@ -59,11 +59,13 @@ def decorator(func_to_wrap: Callable) -> Callable: @wraps(func_to_wrap) def wrapper(*args, **kwargs): retries = 0 + last_error = None while retries < max_retries: try: return func_to_wrap(*args, **kwargs) except NonceError as e: retries += 1 + last_error = e if logger: logger.warning( f"Nonce error encountered: {e}. " @@ -71,11 +73,11 @@ def wrapper(*args, **kwargs): ) if retries >= max_retries: - raise + break time.sleep(retry_delay * (2 ** retries)) # Exponential backoff - raise RuntimeError("Max nonce error retries exceeded") + raise RuntimeError("Max nonce error retries exceeded") from last_error return wrapper