Skip to content

Commit 4a7c2f5

Browse files
authored
fix bedrock with file attachments (#45)
1 parent 081b87e commit 4a7c2f5

4 files changed

Lines changed: 53 additions & 7 deletions

File tree

packages/uipath_langchain_client/CHANGELOG.md

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

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

5+
## [1.5.4] - 2026-03-19
6+
7+
### Fix
8+
- Fix bedrock clients with file attachments
9+
510
## [1.5.3] - 2026-03-18
611

712
### Fix
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.5.3"
3+
__version__ = "1.5.4"

packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/chat_models.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,32 @@
1515
try:
1616
from anthropic import AnthropicBedrock, AsyncAnthropicBedrock
1717
from langchain_aws.chat_models import ChatBedrock, ChatBedrockConverse
18+
from langchain_aws.chat_models import bedrock as _bedrock_module
1819
from langchain_aws.chat_models.anthropic import ChatAnthropicBedrock
1920

2021
from uipath_langchain_client.clients.bedrock.utils import WrappedBotoClient
22+
23+
_original_format_data_content_block = _bedrock_module._format_data_content_block
24+
25+
def _patched_format_data_content_block(block: dict) -> dict:
26+
"""Extended version that also handles file/document blocks for Anthropic API."""
27+
if block["type"] == "file":
28+
if "base64" not in block and block.get("source_type") != "base64":
29+
raise ValueError("File data only supported through in-line base64 format.")
30+
if "mime_type" not in block:
31+
raise ValueError("mime_type key is required for base64 data.")
32+
return {
33+
"type": "document",
34+
"source": {
35+
"type": "base64",
36+
"media_type": block["mime_type"],
37+
"data": block.get("base64") or block.get("data", ""),
38+
},
39+
}
40+
return _original_format_data_content_block(block)
41+
42+
_bedrock_module._format_data_content_block = _patched_format_data_content_block
43+
2144
except ImportError as e:
2245
raise ImportError(
2346
"The 'aws' extra is required to use UiPathBedrockChatModel and UiPathBedrockChatModelConverse. "

packages/uipath_langchain_client/src/uipath_langchain_client/clients/bedrock/utils.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ def __init__(self, region_name: str = "PLACEHOLDER"):
2929
self.events = _MockEventHooks()
3030

3131

32+
def _serialize_bytes(obj: Any) -> Any:
33+
"""Recursively encode bytes values to base64 strings for JSON serialization.
34+
35+
This mimics boto3's serializer which re-encodes bytes to base64 before
36+
sending as JSON. Needed because LangChain's ChatBedrockConverse decodes
37+
base64 content (images, PDFs) into raw bytes objects.
38+
"""
39+
if isinstance(obj, bytes):
40+
return base64.b64encode(obj).decode("utf-8")
41+
if isinstance(obj, dict):
42+
return {k: _serialize_bytes(v) for k, v in obj.items()}
43+
if isinstance(obj, list):
44+
return [_serialize_bytes(item) for item in obj]
45+
return obj
46+
47+
3248
class WrappedBotoClient:
3349
def __init__(self, httpx_client: Client | None = None, region_name: str = "PLACEHOLDER"):
3450
self.httpx_client = httpx_client
@@ -37,7 +53,7 @@ def __init__(self, httpx_client: Client | None = None, region_name: str = "PLACE
3753
def _stream_generator(self, request_body: dict[str, Any]) -> Iterator[dict[str, Any]]:
3854
if self.httpx_client is None:
3955
raise ValueError("httpx_client is not set")
40-
with self.httpx_client.stream("POST", "/", json=request_body) as response:
56+
with self.httpx_client.stream("POST", "/", json=_serialize_bytes(request_body)) as response:
4157
buffer = EventStreamBuffer()
4258
for chunk in response.iter_bytes():
4359
buffer.add_data(chunk)
@@ -69,11 +85,13 @@ def converse(
6985
raise ValueError("httpx_client is not set")
7086
return self.httpx_client.post(
7187
"/",
72-
json={
73-
"messages": messages,
74-
"system": system,
75-
**params,
76-
},
88+
json=_serialize_bytes(
89+
{
90+
"messages": messages,
91+
"system": system,
92+
**params,
93+
}
94+
),
7795
).json()
7896

7997
def converse_stream(

0 commit comments

Comments
 (0)