Skip to content

Commit f7c6fe8

Browse files
authored
feat: add timeout param to all chat model providers (#785)
1 parent 32ea652 commit f7c6fe8

File tree

9 files changed

+107
-8
lines changed

9 files changed

+107
-8
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.9.31"
3+
version = "0.9.32"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_langchain/_utils/_environment.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
def get_execution_folder_path() -> str | None:
55
"""Reads the agent's executing folder path from the runtime environment."""
66
return os.environ.get("UIPATH_FOLDER_PATH")
7+
8+
9+
def get_default_timeout() -> float:
10+
return float(os.getenv("UIPATH_TIMEOUT_SECONDS", "300"))

src/uipath_langchain/_utils/_request_mixin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
UiPathRuntimeError,
2828
)
2929

30+
from uipath_langchain._utils._environment import get_default_timeout
3031
from uipath_langchain._utils._settings import (
3132
UiPathClientFactorySettings,
3233
UiPathClientSettings,
@@ -129,8 +130,7 @@ class UiPathRequestMixin(BaseModel):
129130
)
130131
default_request_timeout: Any = Field(
131132
default_factory=lambda data: float(
132-
getattr(data["settings"], "timeout_seconds", None)
133-
or os.getenv("UIPATH_TIMEOUT_SECONDS", "120")
133+
getattr(data["settings"], "timeout_seconds", None) or get_default_timeout()
134134
),
135135
alias="timeout",
136136
)

src/uipath_langchain/_utils/_settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class UiPathClientSettings(BaseSettings):
4242
requesting_feature: str = Field(
4343
default="langgraph-agent", alias="UIPATH_REQUESTING_FEATURE"
4444
)
45-
timeout_seconds: str = Field(default="120", alias="UIPATH_TIMEOUT_SECONDS")
45+
timeout_seconds: str = Field(default="300", alias="UIPATH_TIMEOUT_SECONDS")
4646
action_name: str = Field(default="DefaultActionName", alias="UIPATH_ACTION_NAME")
4747
action_id: str = Field(default="DefaultActionId", alias="UIPATH_ACTION_ID")
4848

src/uipath_langchain/chat/bedrock.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
resource_override,
1414
)
1515

16+
from uipath_langchain._utils._environment import get_default_timeout
17+
1618
from .http_client import build_uipath_headers, resolve_gateway_url
1719
from .http_client.header_capture import HeaderCapture
1820
from .http_client.retryers.bedrock import AsyncBedrockRetryer, BedrockRetryer
@@ -68,6 +70,7 @@ def __init__(
6870
agenthub_config: Optional[str] = None,
6971
byo_connection_id: Optional[str] = None,
7072
header_capture: HeaderCapture | None = None,
73+
timeout: float | None = None,
7174
):
7275
self.model = model
7376
self.token = token
@@ -78,6 +81,7 @@ def __init__(
7881
self._url: Optional[str] = None
7982
self._is_override: bool = False
8083
self.header_capture = header_capture
84+
self.timeout = timeout if timeout is not None else get_default_timeout()
8185

8286
@property
8387
def endpoint(self) -> str:
@@ -120,7 +124,7 @@ def get_client(self):
120124
verify=ca_bundle if ca_bundle is not None else False,
121125
config=self._unsigned_config(
122126
retries={"total_max_attempts": 1},
123-
read_timeout=300,
127+
read_timeout=self.timeout,
124128
),
125129
)
126130
client.meta.events.register(
@@ -180,6 +184,7 @@ def __init__(
180184
byo_connection_id: Optional[str] = None,
181185
retryer: Optional[Retrying] = None,
182186
aretryer: Optional[AsyncRetrying] = None,
187+
timeout: float | None = None,
183188
**kwargs,
184189
):
185190
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
@@ -205,6 +210,7 @@ def __init__(
205210
api_flavor="converse",
206211
agenthub_config=agenthub_config,
207212
byo_connection_id=byo_connection_id,
213+
timeout=timeout,
208214
)
209215

210216
kwargs["client"] = passthrough_client.get_client()
@@ -242,6 +248,7 @@ def __init__(
242248
byo_connection_id: Optional[str] = None,
243249
retryer: Optional[Retrying] = None,
244250
aretryer: Optional[AsyncRetrying] = None,
251+
timeout: float | None = None,
245252
**kwargs,
246253
):
247254
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
@@ -270,6 +277,7 @@ def __init__(
270277
agenthub_config=agenthub_config,
271278
byo_connection_id=byo_connection_id,
272279
header_capture=header_capture,
280+
timeout=timeout,
273281
)
274282

275283
kwargs["client"] = passthrough_client.get_client()

src/uipath_langchain/chat/openai.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
resource_override,
1212
)
1313

14+
from uipath_langchain._utils._environment import get_default_timeout
15+
1416
from .http_client import build_uipath_headers, resolve_gateway_url
1517
from .supported_models import OpenAIModels
1618
from .types import APIFlavor, LLMProvider
@@ -119,6 +121,7 @@ def __init__(
119121
agenthub_config: Optional[str] = None,
120122
extra_headers: Optional[dict[str, str]] = None,
121123
byo_connection_id: Optional[str] = None,
124+
timeout: float | None = None,
122125
**kwargs,
123126
):
124127
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
@@ -148,7 +151,9 @@ def __init__(
148151
url, is_override = self._resolve_url()
149152

150153
client_kwargs = get_httpx_client_kwargs()
151-
client_kwargs["timeout"] = 300.0
154+
client_kwargs["timeout"] = (
155+
timeout if timeout is not None else get_default_timeout()
156+
)
152157
verify = client_kwargs.get("verify", True)
153158

154159
api_flavor = (
@@ -160,6 +165,7 @@ def __init__(
160165
super().__init__(
161166
azure_endpoint=url,
162167
model_name=model_name,
168+
timeout=client_kwargs["timeout"],
163169
default_headers=self._build_headers(token, inject_routing=is_override),
164170
http_async_client=httpx.AsyncClient(
165171
transport=UiPathURLRewriteTransport(verify=verify),

src/uipath_langchain/chat/vertex.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from uipath._utils._ssl_context import get_httpx_client_kwargs
1616
from uipath.platform.common import EndpointManager
1717

18+
from uipath_langchain._utils._environment import get_default_timeout
19+
1820
from .http_client import build_uipath_headers, resolve_gateway_url
1921
from .http_client.header_capture import HeaderCapture
2022
from .http_client.retryers.vertex import AsyncVertexRetryer, VertexRetryer
@@ -165,6 +167,7 @@ def __init__(
165167
byo_connection_id: Optional[str] = None,
166168
retryer: Optional[Retrying] = None,
167169
aretryer: Optional[AsyncRetrying] = None,
170+
timeout: float | None = None,
168171
**kwargs: Any,
169172
):
170173
org_id = org_id or os.getenv("UIPATH_ORGANIZATION_ID")
@@ -194,11 +197,13 @@ def __init__(
194197

195198
header_capture = HeaderCapture(name=f"vertex_headers_{id(self)}")
196199
client_kwargs = get_httpx_client_kwargs(headers=headers)
197-
client_kwargs["timeout"] = 300.0
200+
resolved_timeout = timeout if timeout is not None else get_default_timeout()
201+
client_kwargs["timeout"] = resolved_timeout
198202
verify = client_kwargs.get("verify", True)
199203
merged_headers = client_kwargs.pop("headers", {})
200204

201205
http_options = genai_types.HttpOptions(
206+
timeout=int(resolved_timeout * 1000),
202207
httpx_client=httpx.Client(
203208
transport=_UrlRewriteTransport(
204209
uipath_url, verify=verify, header_capture=header_capture

tests/chat/test_timeout.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""Tests that the timeout parameter propagates correctly to the underlying HTTP clients."""
2+
3+
import os
4+
from unittest.mock import patch
5+
6+
import pytest
7+
8+
BASE_ENV = {
9+
"UIPATH_URL": "https://cloud.uipath.com/org/tenant",
10+
"UIPATH_ORGANIZATION_ID": "org-id",
11+
"UIPATH_TENANT_ID": "tenant-id",
12+
"UIPATH_ACCESS_TOKEN": "test-token",
13+
}
14+
15+
16+
class TestUiPathChatOpenAITimeout:
17+
def _make(self, timeout: float):
18+
from uipath_langchain.chat.openai import UiPathChatOpenAI
19+
20+
with patch.dict(os.environ, BASE_ENV, clear=False):
21+
return UiPathChatOpenAI(timeout=timeout)
22+
23+
def test_default_timeout(self):
24+
llm = self._make(300.0)
25+
assert llm.http_async_client.timeout.read == 300.0
26+
assert llm.http_client.timeout.read == 300.0
27+
28+
def test_custom_timeout_propagates_to_async_client(self):
29+
llm = self._make(600.0)
30+
assert llm.http_async_client.timeout.read == 600.0
31+
32+
def test_custom_timeout_propagates_to_sync_client(self):
33+
llm = self._make(120.0)
34+
assert llm.http_client.timeout.read == 120.0
35+
36+
37+
@pytest.mark.skipif(
38+
pytest.importorskip("google.genai", reason="google-genai not installed") is None,
39+
reason="google-genai not installed",
40+
)
41+
class TestUiPathChatVertexTimeout:
42+
def _make(self, timeout: float):
43+
from uipath_langchain.chat.vertex import UiPathChatVertex
44+
45+
with patch.dict(os.environ, BASE_ENV, clear=False):
46+
return UiPathChatVertex(timeout=timeout)
47+
48+
def test_default_timeout(self):
49+
llm = self._make(300.0)
50+
assert llm.client._api_client._httpx_client.timeout.read == 300.0
51+
52+
def test_custom_timeout_propagates(self):
53+
llm = self._make(600.0)
54+
assert llm.client._api_client._httpx_client.timeout.read == 600.0
55+
56+
57+
class TestAwsBedrockPassthroughClientTimeout:
58+
def _make(self, timeout: float):
59+
from uipath_langchain.chat.bedrock import AwsBedrockCompletionsPassthroughClient
60+
61+
return AwsBedrockCompletionsPassthroughClient(
62+
model="anthropic.claude-haiku-4-5",
63+
token="test-token",
64+
api_flavor="converse",
65+
timeout=timeout,
66+
)
67+
68+
def test_default_timeout(self):
69+
client = self._make(300.0)
70+
boto_client = client.get_client()
71+
assert boto_client.meta.config.read_timeout == 300.0
72+
73+
def test_custom_timeout_propagates(self):
74+
client = self._make(600.0)
75+
boto_client = client.get_client()
76+
assert boto_client.meta.config.read_timeout == 600.0

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)