--- /tmp/evalscope-sync/src-v191/evalscope/utils/function_utils.py 2026-07-21 03:18:18.000000000 +0000 +++ /data1/syy/evalscope/evalstone/evalscope/evalscope/utils/function_utils.py 2026-09-01 03:48:30.955904239 +0000 @@ -68,12 +68,22 @@ return wrapper -def retry_call(func, *args, retries=3, sleep_interval=0, **kwargs): - """Function that retries a function call up to `retries` times if an exception occurs.""" +def retry_call(func, *args, retries=3, sleep_interval=0, on_attempt=None, **kwargs): + """Function that retries a function call up to `retries` times if an exception occurs. + + ``on_attempt(ok, exc=None)`` is invoked once per try (including retries) so + callers can count vendor HTTP success/failure independently of the final + ``generate()`` outcome. + """ for attempt in range(retries): try: - return func(*args, **kwargs) + result = func(*args, **kwargs) + if on_attempt is not None: + on_attempt(True, None) + return result except Exception as e: + if on_attempt is not None: + on_attempt(False, e) if attempt < retries - 1: if sleep_interval > 0: logger.warning(f'Attempt {attempt + 1} / {retries} failed: {e}. Retrying...') @@ -83,13 +93,23 @@ async def async_retry_call( - func: Callable[..., Awaitable[T]], *args, retries: int = 3, sleep_interval: float = 0, **kwargs + func: Callable[..., Awaitable[T]], + *args, + retries: int = 3, + sleep_interval: float = 0, + on_attempt=None, + **kwargs, ) -> T: """Async version of retry_call. Retries an async function call up to `retries` times if an exception occurs.""" for attempt in range(retries): try: - return await func(*args, **kwargs) + result = await func(*args, **kwargs) + if on_attempt is not None: + on_attempt(True, None) + return result except Exception as e: + if on_attempt is not None: + on_attempt(False, e) if attempt < retries - 1: if sleep_interval > 0: logger.warning(f'Attempt {attempt + 1} / {retries} failed: {e}. Retrying...')