Skip to content

Commit 8ff4412

Browse files
authored
Fix/fix anthropic payload handler (#724)
1 parent 11ec113 commit 8ff4412

File tree

4 files changed

+75
-8
lines changed

4 files changed

+75
-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.5"
3+
version = "0.9.6"
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/handlers/bedrock.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def get_tool_binding_kwargs(
8686
parallel_tool_calls: bool = True,
8787
strict_mode: bool = False,
8888
) -> dict[str, Any]:
89+
_thinking = (getattr(self.model, "model_kwargs", None) or {}).get("thinking")
8990
thinking_enabled = (
90-
getattr(self.model, "model_kwargs", {}).get("thinking", {}).get("type")
91-
== "enabled"
91+
isinstance(_thinking, dict) and _thinking.get("type") == "enabled"
9292
)
9393
# Anthropic models via Invoke API don't support forced tool use with extended thinking
9494
if thinking_enabled and tool_choice == "any":
@@ -142,11 +142,11 @@ def get_tool_binding_kwargs(
142142
parallel_tool_calls: bool = True,
143143
strict_mode: bool = False,
144144
) -> dict[str, Any]:
145+
_thinking = (
146+
getattr(self.model, "additional_model_request_fields", None) or {}
147+
).get("thinking")
145148
thinking_enabled = (
146-
getattr(self.model, "additional_model_request_fields", {})
147-
.get("thinking", {})
148-
.get("type")
149-
== "enabled"
149+
isinstance(_thinking, dict) and _thinking.get("type") == "enabled"
150150
)
151151
# Anthropic models via Converse API don't support forced tool use with extended thinking
152152
if thinking_enabled and tool_choice == "any":

tests/chat/test_bedrock_payload_handler.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,70 @@ def test_invoke_snake_case_key_ignored(self) -> None:
231231
"""Converse handler must not react to stop_reason (snake_case)."""
232232
msg = AIMessage(content="", response_metadata={"stop_reason": "max_tokens"})
233233
self.handler.check_stop_reason(msg) # should not raise
234+
235+
236+
# ---------------------------------------------------------------------------
237+
# Null-safety: thinking_enabled detection
238+
# ---------------------------------------------------------------------------
239+
240+
241+
class TestBedrockInvokeThinkingNullSafety:
242+
"""model_kwargs or its nested values may be None — must not raise."""
243+
244+
def test_model_kwargs_is_none(self) -> None:
245+
model = type("FakeChatBedrock", (), {})()
246+
model.model_kwargs = None
247+
handler = BedrockInvokePayloadHandler(model)
248+
result = handler.get_tool_binding_kwargs([], "any")
249+
assert result["tool_choice"] == "any"
250+
251+
def test_thinking_value_is_none(self) -> None:
252+
model = type("FakeChatBedrock", (), {})()
253+
model.model_kwargs = {"thinking": None}
254+
handler = BedrockInvokePayloadHandler(model)
255+
result = handler.get_tool_binding_kwargs([], "any")
256+
assert result["tool_choice"] == "any"
257+
258+
def test_model_kwargs_attribute_missing(self) -> None:
259+
model = type("FakeChatBedrock", (), {})()
260+
handler = BedrockInvokePayloadHandler(model)
261+
result = handler.get_tool_binding_kwargs([], "any")
262+
assert result["tool_choice"] == "any"
263+
264+
def test_thinking_value_is_non_dict_truthy(self) -> None:
265+
model = type("FakeChatBedrock", (), {})()
266+
model.model_kwargs = {"thinking": "enabled"}
267+
handler = BedrockInvokePayloadHandler(model)
268+
result = handler.get_tool_binding_kwargs([], "any")
269+
assert result["tool_choice"] == "any"
270+
271+
272+
class TestBedrockConverseThinkingNullSafety:
273+
"""additional_model_request_fields or its nested values may be None — must not raise."""
274+
275+
def test_additional_fields_is_none(self) -> None:
276+
model = type("FakeChatBedrockConverse", (), {})()
277+
model.additional_model_request_fields = None
278+
handler = BedrockConversePayloadHandler(model)
279+
result = handler.get_tool_binding_kwargs([], "any")
280+
assert result["tool_choice"] == "any"
281+
282+
def test_thinking_value_is_none(self) -> None:
283+
model = type("FakeChatBedrockConverse", (), {})()
284+
model.additional_model_request_fields = {"thinking": None}
285+
handler = BedrockConversePayloadHandler(model)
286+
result = handler.get_tool_binding_kwargs([], "any")
287+
assert result["tool_choice"] == "any"
288+
289+
def test_thinking_value_is_non_dict_truthy(self) -> None:
290+
model = type("FakeChatBedrockConverse", (), {})()
291+
model.additional_model_request_fields = {"thinking": "enabled"}
292+
handler = BedrockConversePayloadHandler(model)
293+
result = handler.get_tool_binding_kwargs([], "any")
294+
assert result["tool_choice"] == "any"
295+
296+
def test_additional_fields_attribute_missing(self) -> None:
297+
model = type("FakeChatBedrockConverse", (), {})()
298+
handler = BedrockConversePayloadHandler(model)
299+
result = handler.get_tool_binding_kwargs([], "any")
300+
assert result["tool_choice"] == "any"

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)