Skip to content

Commit 899a056

Browse files
chore: pass bedrock_client with UNSIGNED to prevent IMDS timeout from langchain_aws (#693)
1 parent 8fecff6 commit 899a056

4 files changed

Lines changed: 90 additions & 15 deletions

File tree

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.8.23"
3+
version = "0.8.24"
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/chat/bedrock.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,26 @@ def _capture_response_headers(self, parsed, model, **kwargs):
9595
if self.header_capture:
9696
self.header_capture.set(dict(headers))
9797

98-
def get_client(self):
99-
session = boto3.Session(
98+
def _build_session(self):
99+
return boto3.Session(
100100
aws_access_key_id="none",
101101
aws_secret_access_key="none",
102102
region_name="none",
103103
)
104+
105+
def _unsigned_config(self, **overrides):
106+
return botocore.config.Config(
107+
signature_version=botocore.UNSIGNED,
108+
**overrides,
109+
)
110+
111+
def get_client(self):
112+
session = self._build_session()
104113
client = session.client(
105114
"bedrock-runtime",
106-
config=botocore.config.Config(
107-
retries={
108-
"total_max_attempts": 1,
109-
},
115+
config=self._unsigned_config(
116+
retries={"total_max_attempts": 1},
110117
read_timeout=300,
111-
signature_version=botocore.UNSIGNED,
112118
),
113119
)
114120
client.meta.events.register(
@@ -119,6 +125,13 @@ def get_client(self):
119125
)
120126
return client
121127

128+
def get_bedrock_client(self):
129+
session = self._build_session()
130+
return session.client(
131+
"bedrock",
132+
config=self._unsigned_config(),
133+
)
134+
122135
def _modify_request(self, request, **kwargs):
123136
"""Intercept boto3 request and redirect to LLM Gateway."""
124137
# Detect streaming based on URL suffix:
@@ -186,8 +199,8 @@ def __init__(
186199
byo_connection_id=byo_connection_id,
187200
)
188201

189-
client = passthrough_client.get_client()
190-
kwargs["client"] = client
202+
kwargs["client"] = passthrough_client.get_client()
203+
kwargs["bedrock_client"] = passthrough_client.get_bedrock_client()
191204
kwargs["model"] = model_name
192205
super().__init__(**kwargs)
193206
self.model = model_name
@@ -251,8 +264,8 @@ def __init__(
251264
header_capture=header_capture,
252265
)
253266

254-
client = passthrough_client.get_client()
255-
kwargs["client"] = client
267+
kwargs["client"] = passthrough_client.get_client()
268+
kwargs["bedrock_client"] = passthrough_client.get_bedrock_client()
256269
kwargs["model"] = model_name
257270
kwargs["header_capture"] = header_capture
258271
super().__init__(**kwargs)

tests/chat/test_bedrock.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,22 @@
1111
from uipath_langchain.chat.bedrock import (
1212
AwsBedrockCompletionsPassthroughClient,
1313
UiPathChatBedrock,
14+
UiPathChatBedrockConverse,
1415
)
1516

1617

1718
class TestGetClientSkipsImds:
18-
def test_client_creation_does_not_trigger_credential_resolution(self, caplog):
19+
def _assert_no_credential_resolution(self, caplog, client):
20+
assert caplog.records
21+
credential_log_records = [
22+
r for r in caplog.records if r.name.startswith("botocore.credentials")
23+
]
24+
assert not credential_log_records, (
25+
f"Unexpected credential resolution: {[r.getMessage() for r in credential_log_records]}"
26+
)
27+
assert client._request_signer._signature_version == botocore.UNSIGNED
28+
29+
def test_get_client_does_not_trigger_credential_resolution(self, caplog):
1930
passthrough = AwsBedrockCompletionsPassthroughClient(
2031
model="anthropic.claude-haiku-4-5-20251001",
2132
token="test-token",
@@ -25,14 +36,65 @@ def test_client_creation_does_not_trigger_credential_resolution(self, caplog):
2536
with caplog.at_level(logging.DEBUG, logger="botocore"):
2637
client = passthrough.get_client()
2738

39+
self._assert_no_credential_resolution(caplog, client)
40+
41+
def test_get_bedrock_client_does_not_trigger_credential_resolution(self, caplog):
42+
passthrough = AwsBedrockCompletionsPassthroughClient(
43+
model="anthropic.claude-haiku-4-5-20251001",
44+
token="test-token",
45+
api_flavor="converse",
46+
)
47+
48+
with caplog.at_level(logging.DEBUG, logger="botocore"):
49+
client = passthrough.get_bedrock_client()
50+
51+
self._assert_no_credential_resolution(caplog, client)
52+
53+
@patch.dict(
54+
os.environ,
55+
{
56+
"UIPATH_URL": "https://example.com",
57+
"UIPATH_ORGANIZATION_ID": "org",
58+
"UIPATH_TENANT_ID": "tenant",
59+
"UIPATH_ACCESS_TOKEN": "token",
60+
},
61+
)
62+
def test_uipath_chat_bedrock_converse_init_does_not_trigger_credential_resolution(
63+
self, caplog
64+
):
65+
with caplog.at_level(logging.DEBUG, logger="botocore"):
66+
UiPathChatBedrockConverse()
67+
68+
assert caplog.records
69+
credential_log_records = [
70+
r for r in caplog.records if r.name.startswith("botocore.credentials")
71+
]
72+
assert not credential_log_records, (
73+
f"Unexpected credential resolution: {[r.getMessage() for r in credential_log_records]}"
74+
)
75+
76+
@patch.dict(
77+
os.environ,
78+
{
79+
"UIPATH_URL": "https://example.com",
80+
"UIPATH_ORGANIZATION_ID": "org",
81+
"UIPATH_TENANT_ID": "tenant",
82+
"UIPATH_ACCESS_TOKEN": "token",
83+
},
84+
)
85+
def test_uipath_chat_bedrock_init_does_not_trigger_credential_resolution(
86+
self, caplog
87+
):
88+
with caplog.at_level(logging.DEBUG, logger="botocore"):
89+
UiPathChatBedrock()
90+
2891
assert caplog.records
2992
credential_log_records = [
3093
r for r in caplog.records if r.name.startswith("botocore.credentials")
3194
]
3295
assert not credential_log_records, (
3396
f"Unexpected credential resolution: {[r.getMessage() for r in credential_log_records]}"
3497
)
35-
assert client._request_signer._signature_version == botocore.UNSIGNED
3698

3799

38100
class TestConvertFileBlocksToAnthropicDocuments:

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)