Skip to content

Commit 3c8ebcc

Browse files
feat: retry HTTP 408/502/503/504 by default and default max_retries to 3 (#89)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d132c3b commit 3c8ebcc

15 files changed

Lines changed: 258 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
All notable changes to `uipath_llm_client` (core package) will be documented in this file.
44

5+
## [1.13.0] - 2026-05-27
6+
7+
### Added
8+
- `UiPathRequestTimeoutError` (HTTP 408) and `UiPathBadGatewayError` (HTTP 502) exception classes. Both are registered in `_STATUS_CODE_TO_EXCEPTION`, re-exported from `uipath.llm_client`, and inherit from `UiPathAPIError` for compatibility with existing handlers.
9+
10+
### Changed
11+
- **Default retry set expanded.** `_DEFAULT_RETRY_ON_EXCEPTIONS` in `uipath.llm_client.utils.retry` now covers `UiPathRequestTimeoutError` (408), `UiPathRateLimitError` (429), `UiPathBadGatewayError` (502), `UiPathServiceUnavailableError` (503), `UiPathGatewayTimeoutError` (504), and `UiPathTooManyRequestsError` (529) — up from `{429, 529}`. Applies to every provider client (`UiPathOpenAI`, `UiPathAnthropic*`, `UiPathGoogle`) since they all share the same `UiPathHttpxClient`-backed retry transport. `Retry-After` / `x-retry-after` headers and exponential backoff with jitter behave as before.
12+
- **`UiPathHttpxClient` / `UiPathHttpxAsyncClient` default `max_retries` raised from `0` to `3`.** Callers that pass `max_retries=None` (or omit it entirely) now get 3 retries by default. Pass `max_retries=0` explicitly to opt out — `max_retries=0` continues to disable retries, so the opt-out path is unchanged.
13+
514
## [1.12.2] - 2026-05-24
615

716
### Changed

packages/uipath_langchain_client/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to `uipath_langchain_client` will be documented in this file.
44

5+
## [1.13.0] - 2026-05-27
6+
7+
### Changed
8+
- **`UiPathBaseLLMClient.max_retries` field default raised from `0` to `3`.** Every LangChain chat and embedding client built on this base (`UiPathChat`, `UiPathChatOpenAI`, `UiPathAzureChatOpenAI`, `UiPathChatAnthropic`, `UiPathChatAnthropicBedrock`, `UiPathChatBedrock`, `UiPathChatBedrockConverse`, `UiPathChatVertexAI`, `UiPathChatFireworks`, `UiPathChatLiteLLM`, plus the matching embeddings classes) now retries failed requests 3 times by default. Pass `max_retries=0` explicitly to disable retries — the opt-out path is unchanged. Combined with the expanded default retry set in `uipath-llm-client` 1.13.0, every LangChain client now retries on HTTP 408, 429, 502, 503, 504, and 529 out of the box.
9+
- Bumped `uipath-llm-client` floor to `>=1.13.0` to pick up the expanded default retry set and the new `UiPathRequestTimeoutError` / `UiPathBadGatewayError` typed exceptions.
10+
511
## [1.12.2] - 2026-05-24
612

713
### Changed

packages/uipath_langchain_client/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
88
"langchain>=1.2.15,<2.0.0",
9-
"uipath-llm-client>=1.12.2,<2.0.0",
9+
"uipath-llm-client>=1.13.0,<2.0.0",
1010
]
1111

1212
[project.optional-dependencies]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LangChain Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services via LangChain."
3-
__version__ = "1.12.2"
3+
__version__ = "1.13.0"

packages/uipath_langchain_client/src/uipath_langchain_client/base_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ class UiPathBaseLLMClient(BaseModel, ABC):
145145
description="Client-side request timeout in seconds",
146146
)
147147
max_retries: int = Field(
148-
default=0,
149-
description="Maximum number of retries for failed requests",
148+
default=3,
149+
description="Maximum number of retries for failed requests. Pass 0 to disable retries.",
150150
)
151151
retry_config: RetryConfig | None = Field(
152152
default=None,

src/uipath/llm_client/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@
3939
from uipath.llm_client.utils.exceptions import (
4040
UiPathAPIError,
4141
UiPathAuthenticationError,
42+
UiPathBadGatewayError,
4243
UiPathBadRequestError,
4344
UiPathConflictError,
4445
UiPathGatewayTimeoutError,
4546
UiPathInternalServerError,
4647
UiPathNotFoundError,
4748
UiPathPermissionDeniedError,
4849
UiPathRateLimitError,
50+
UiPathRequestTimeoutError,
4951
UiPathRequestTooLargeError,
5052
UiPathServiceUnavailableError,
5153
UiPathTooManyRequestsError,
@@ -69,13 +71,15 @@
6971
# Exceptions
7072
"UiPathAPIError",
7173
"UiPathAuthenticationError",
74+
"UiPathBadGatewayError",
7275
"UiPathBadRequestError",
7376
"UiPathConflictError",
7477
"UiPathGatewayTimeoutError",
7578
"UiPathInternalServerError",
7679
"UiPathNotFoundError",
7780
"UiPathPermissionDeniedError",
7881
"UiPathRateLimitError",
82+
"UiPathRequestTimeoutError",
7983
"UiPathRequestTooLargeError",
8084
"UiPathServiceUnavailableError",
8185
"UiPathTooManyRequestsError",
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__title__ = "UiPath LLM Client"
22
__description__ = "A Python client for interacting with UiPath's LLM services."
3-
__version__ = "1.12.2"
3+
__version__ = "1.13.0"

src/uipath/llm_client/httpx_client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@
6868
# Sentinel to distinguish "not provided" from an explicit ``None`` / ``False``.
6969
_UNSET: Any = object()
7070

71+
# Default applied when ``max_retries`` is left as ``None``. Callers can still
72+
# opt out by passing ``max_retries=0`` explicitly.
73+
_DEFAULT_MAX_RETRIES: typing.Final[int] = 3
74+
7175

7276
class UiPathHttpxClient(Client):
7377
"""Synchronous HTTP client configured for UiPath LLM services.
@@ -135,8 +139,8 @@ def __init__(
135139
captured_headers: Case-insensitive header name prefixes to capture from
136140
responses. Captured headers are stored in a ContextVar and can be
137141
retrieved with get_captured_response_headers(). Defaults to ("x-uipath-",).
138-
max_retries: Maximum retry attempts for failed requests. Defaults to 0
139-
(retries disabled). Set to a positive integer to enable retries.
142+
max_retries: Maximum retry attempts for failed requests. Defaults to 3
143+
when left as ``None``. Pass ``0`` to disable retries explicitly.
140144
retry_config: Custom retry configuration (backoff, retryable status codes).
141145
logger: Logger instance for request/response logging.
142146
auth: HTTP authentication (same as httpx.Client). Derived from
@@ -193,7 +197,7 @@ def __init__(
193197
# Setup retry transport if not provided
194198
if transport is None:
195199
transport = RetryableHTTPTransport(
196-
max_retries=max_retries if max_retries is not None else 0,
200+
max_retries=max_retries if max_retries is not None else _DEFAULT_MAX_RETRIES,
197201
retry_config=retry_config,
198202
logger=logger,
199203
)
@@ -354,7 +358,7 @@ def __init__(
354358
# Setup retry transport if not provided
355359
if transport is None:
356360
transport = RetryableAsyncHTTPTransport(
357-
max_retries=max_retries if max_retries is not None else 0,
361+
max_retries=max_retries if max_retries is not None else _DEFAULT_MAX_RETRIES,
358362
retry_config=retry_config,
359363
logger=logger,
360364
)

src/uipath/llm_client/utils/exceptions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ class UiPathNotFoundError(UiPathAPIError):
118118
status_code: Literal[404] = 404 # pyright: ignore[reportIncompatibleVariableOverride]
119119

120120

121+
class UiPathRequestTimeoutError(UiPathAPIError):
122+
"""HTTP 408 Request Timeout error."""
123+
124+
status_code: Literal[408] = 408 # pyright: ignore[reportIncompatibleVariableOverride]
125+
126+
121127
class UiPathConflictError(UiPathAPIError):
122128
"""HTTP 409 Conflict error."""
123129

@@ -211,6 +217,12 @@ class UiPathInternalServerError(UiPathAPIError):
211217
status_code: Literal[500] = 500 # pyright: ignore[reportIncompatibleVariableOverride]
212218

213219

220+
class UiPathBadGatewayError(UiPathAPIError):
221+
"""HTTP 502 Bad Gateway error."""
222+
223+
status_code: Literal[502] = 502 # pyright: ignore[reportIncompatibleVariableOverride]
224+
225+
214226
class UiPathServiceUnavailableError(UiPathAPIError):
215227
"""HTTP 503 Service Unavailable error."""
216228

@@ -234,11 +246,13 @@ class UiPathTooManyRequestsError(UiPathAPIError):
234246
401: UiPathAuthenticationError,
235247
403: UiPathPermissionDeniedError,
236248
404: UiPathNotFoundError,
249+
408: UiPathRequestTimeoutError,
237250
409: UiPathConflictError,
238251
413: UiPathRequestTooLargeError,
239252
422: UiPathUnprocessableEntityError,
240253
429: UiPathRateLimitError,
241254
500: UiPathInternalServerError,
255+
502: UiPathBadGatewayError,
242256
503: UiPathServiceUnavailableError,
243257
504: UiPathGatewayTimeoutError,
244258
529: UiPathTooManyRequestsError,
@@ -266,11 +280,13 @@ def raise_for_status() -> Response:
266280
"UiPathAuthenticationError",
267281
"UiPathPermissionDeniedError",
268282
"UiPathNotFoundError",
283+
"UiPathRequestTimeoutError",
269284
"UiPathConflictError",
270285
"UiPathRequestTooLargeError",
271286
"UiPathUnprocessableEntityError",
272287
"UiPathRateLimitError",
273288
"UiPathInternalServerError",
289+
"UiPathBadGatewayError",
274290
"UiPathServiceUnavailableError",
275291
"UiPathGatewayTimeoutError",
276292
"UiPathTooManyRequestsError",

src/uipath/llm_client/utils/retry.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,22 @@
4949

5050
from uipath.llm_client.utils.exceptions import (
5151
UiPathAPIError,
52+
UiPathBadGatewayError,
53+
UiPathGatewayTimeoutError,
5254
UiPathRateLimitError,
55+
UiPathRequestTimeoutError,
56+
UiPathServiceUnavailableError,
5357
UiPathTooManyRequestsError,
5458
)
5559

5660
# Default retry configuration values
61+
# Status codes retried by default: 408, 429, 502, 503, 504, 529.
5762
_DEFAULT_RETRY_ON_EXCEPTIONS: tuple[type[Exception], ...] = (
63+
UiPathRequestTimeoutError,
5864
UiPathRateLimitError,
65+
UiPathBadGatewayError,
66+
UiPathServiceUnavailableError,
67+
UiPathGatewayTimeoutError,
5968
UiPathTooManyRequestsError,
6069
)
6170
_DEFAULT_INITIAL_DELAY: float = 2.0
@@ -127,7 +136,7 @@ class RetryConfig(TypedDict):
127136
128137
Attributes:
129138
retry_on_exceptions: Tuple of exception types to retry on.
130-
Defaults to (UiPathRateLimitError,).
139+
Defaults to the typed exceptions for HTTP 408, 429, 502, 503, 504, 529.
131140
initial_delay: Initial delay in seconds before first retry.
132141
Defaults to 2.0.
133142
max_delay: Maximum delay in seconds between retries.

0 commit comments

Comments
 (0)