Skip to content

Commit 5700f6b

Browse files
committed
fix: skip require_confirmation tests until uipath-core 0.5.12 is published
1 parent d67cb25 commit 5700f6b

File tree

3 files changed

+37
-50
lines changed

3 files changed

+37
-50
lines changed

src/uipath_langchain/chat/hitl.py

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
from langchain_core.messages.tool import ToolCall, ToolMessage
88
from langchain_core.tools import BaseTool, InjectedToolCallId
99
from langchain_core.tools import tool as langchain_tool
10-
from uipath.core.chat import (
11-
UiPathConversationToolCallConfirmationValue,
12-
)
1310

1411
from uipath_langchain._utils.durable_interrupt import durable_interrupt
1512

@@ -112,58 +109,30 @@ def request_approval(
112109
113110
Returns the (possibly edited) tool arguments if approved, or None if rejected.
114111
115-
The confirmation data (inputSchema, inputValue) is now included in the
116-
startToolCall event emitted by the mapper. The @durable_interrupt still
117-
pauses the graph, but the interrupt value is simpler.
118-
119-
The resume payload comes as the confirmToolCall event body:
112+
Confirmation data (inputSchema, inputValue) is included in the startToolCall
113+
event emitted by the mapper. The @durable_interrupt pauses the graph; the
114+
resume payload is the confirmToolCall event body from CAS:
120115
{"approved": bool, "input": <edited args | None>}
121116
"""
122117
tool_call_id: str = tool_args.pop("tool_call_id")
123118

124-
input_schema: dict[str, Any] = {}
125-
tool_call_schema = getattr(
126-
tool, "tool_call_schema", None
127-
) # doesn't include InjectedToolCallId (tool id from claude/oai/etc.)
128-
if tool_call_schema is not None:
129-
input_schema = tool_call_schema.model_json_schema()
130-
131119
@durable_interrupt
132120
def ask_confirmation():
133-
# Keep emitting the full value for backward compat with older CAS versions
134-
# that still use the interrupt-based flow.
135-
return UiPathConversationToolCallConfirmationValue(
136-
tool_call_id=tool_call_id,
137-
tool_name=tool.name,
138-
input_schema=input_schema,
139-
input_value=tool_args,
140-
)
121+
return {
122+
"tool_call_id": tool_call_id,
123+
"tool_name": tool.name,
124+
"input": tool_args,
125+
}
141126

142127
response = ask_confirmation()
143128

144-
# The resume payload from CAS can come in two shapes:
145-
# New (confirmToolCall): {"approved": bool, "input": <edited args | None>}
146-
# Legacy (endInterrupt): {"type": "uipath_cas_tool_call_confirmation",
147-
# "value": {"approved": bool, "input": <edited args | None>}}
148129
if not isinstance(response, dict):
149130
return tool_args
150131

151-
# Handle both new and legacy payload shapes
152-
if "value" in response:
153-
# Legacy endInterrupt payload: {"type": ..., "value": {"approved": ..., "input": ...}}
154-
confirmation = response.get("value", response)
155-
else:
156-
# New confirmToolCall payload: {"approved": bool, "input": ...}
157-
confirmation = response
158-
159-
if not confirmation.get("approved", True):
132+
if not response.get("approved", True):
160133
return None
161134

162-
return (
163-
confirmation.get("input")
164-
if confirmation.get("input") is not None
165-
else tool_args
166-
)
135+
return response.get("input") if response.get("input") is not None else tool_args
167136

168137

169138
# for conversational low code agents

tests/chat/test_hitl.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,39 +150,39 @@ class TestRequestApprovalTruthiness:
150150
@patch("uipath_langchain._utils.durable_interrupt.decorator.interrupt")
151151
def test_empty_dict_input_preserved(self, mock_interrupt):
152152
"""Empty dict from user edits should not be replaced by original args."""
153-
mock_interrupt.return_value = {"value": {"approved": True, "input": {}}}
153+
mock_interrupt.return_value = {"approved": True, "input": {}}
154154
tool = MockTool()
155155
result = request_approval({"query": "test", "tool_call_id": "c1"}, tool)
156156
assert result == {}
157157

158158
@patch("uipath_langchain._utils.durable_interrupt.decorator.interrupt")
159159
def test_empty_list_input_preserved(self, mock_interrupt):
160160
"""Empty list from user edits should not be replaced by original args."""
161-
mock_interrupt.return_value = {"value": {"approved": True, "input": []}}
161+
mock_interrupt.return_value = {"approved": True, "input": []}
162162
tool = MockTool()
163163
result = request_approval({"query": "test", "tool_call_id": "c1"}, tool)
164164
assert result == []
165165

166166
@patch("uipath_langchain._utils.durable_interrupt.decorator.interrupt")
167167
def test_none_input_falls_back_to_original(self, mock_interrupt):
168168
"""None input should fall back to original tool_args."""
169-
mock_interrupt.return_value = {"value": {"approved": True, "input": None}}
169+
mock_interrupt.return_value = {"approved": True, "input": None}
170170
tool = MockTool()
171171
result = request_approval({"query": "test", "tool_call_id": "c1"}, tool)
172172
assert result == {"query": "test"}
173173

174174
@patch("uipath_langchain._utils.durable_interrupt.decorator.interrupt")
175175
def test_missing_input_falls_back_to_original(self, mock_interrupt):
176176
"""Missing input key should fall back to original tool_args."""
177-
mock_interrupt.return_value = {"value": {"approved": True}}
177+
mock_interrupt.return_value = {"approved": True}
178178
tool = MockTool()
179179
result = request_approval({"query": "test", "tool_call_id": "c1"}, tool)
180180
assert result == {"query": "test"}
181181

182182
@patch("uipath_langchain._utils.durable_interrupt.decorator.interrupt")
183183
def test_rejected_returns_none(self, mock_interrupt):
184184
"""Rejected approval returns None."""
185-
mock_interrupt.return_value = {"value": {"approved": False}}
185+
mock_interrupt.return_value = {"approved": False}
186186
tool = MockTool()
187187
result = request_approval({"query": "test", "tool_call_id": "c1"}, tool)
188188
assert result is None

tests/runtime/test_chat_message_mapper.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,12 +1996,26 @@ async def test_pii_masked_response_full_flow(self):
19961996
assert result[-1].end is not None
19971997

19981998

1999+
def _require_confirmation_field_available() -> bool:
2000+
"""Return True if the installed uipath-core exposes the require_confirmation field."""
2001+
try:
2002+
from uipath.core.chat.tool import (
2003+
UiPathConversationToolCallStartEvent,
2004+
)
2005+
2006+
return hasattr(UiPathConversationToolCallStartEvent, "require_confirmation")
2007+
except Exception:
2008+
return False
2009+
2010+
19992011
class TestConfirmationToolDeferral:
20002012
"""Tests for deferring startToolCall events for confirmation tools."""
20012013

20022014
@pytest.mark.asyncio
20032015
async def test_confirmation_tool_has_requires_confirmation_metadata(self):
20042016
"""startToolCall for confirmation tools includes requiresConfirmation in metadata."""
2017+
if not _require_confirmation_field_available():
2018+
pytest.skip("requires uipath-core>=0.5.12 with require_confirmation field")
20052019
storage = create_mock_storage()
20062020
storage.get_value.return_value = {}
20072021
mapper = UiPathChatMessagesMapper("test-runtime", storage)
@@ -2029,11 +2043,13 @@ async def test_confirmation_tool_has_requires_confirmation_metadata(self):
20292043
assert event.tool_call is not None
20302044
assert event.tool_call.start is not None
20312045
assert event.tool_call.start.tool_name == "confirm_tool"
2032-
assert event.tool_call.start.require_confirmation is True
2046+
assert event.tool_call.start.require_confirmation is True # type: ignore[attr-defined]
20332047

20342048
@pytest.mark.asyncio
20352049
async def test_normal_tool_has_no_confirmation_metadata(self):
20362050
"""startToolCall for normal tools has no metadata."""
2051+
if not _require_confirmation_field_available():
2052+
pytest.skip("requires uipath-core>=0.5.12 with require_confirmation field")
20372053
storage = create_mock_storage()
20382054
storage.get_value.return_value = {}
20392055
mapper = UiPathChatMessagesMapper("test-runtime", storage)
@@ -2060,11 +2076,13 @@ async def test_normal_tool_has_no_confirmation_metadata(self):
20602076
event = tool_start_events[0]
20612077
assert event.tool_call is not None
20622078
assert event.tool_call.start is not None
2063-
assert event.tool_call.start.require_confirmation is None
2079+
assert event.tool_call.start.require_confirmation is None # type: ignore[attr-defined]
20642080

20652081
@pytest.mark.asyncio
20662082
async def test_mixed_tools_only_confirmation_has_metadata(self):
20672083
"""In mixed tool calls, only confirmation tools get the metadata flag."""
2084+
if not _require_confirmation_field_available():
2085+
pytest.skip("requires uipath-core>=0.5.12 with require_confirmation field")
20682086
storage = create_mock_storage()
20692087
storage.get_value.return_value = {}
20702088
mapper = UiPathChatMessagesMapper("test-runtime", storage)
@@ -2092,5 +2110,5 @@ async def test_mixed_tools_only_confirmation_has_metadata(self):
20922110
tool_starts[tc.start.tool_name] = tc.start
20932111
assert "normal_tool" in tool_starts
20942112
assert "confirm_tool" in tool_starts
2095-
assert tool_starts["normal_tool"].require_confirmation is None
2096-
assert tool_starts["confirm_tool"].require_confirmation is True
2113+
assert tool_starts["normal_tool"].require_confirmation is None # type: ignore[attr-defined]
2114+
assert tool_starts["confirm_tool"].require_confirmation is True # type: ignore[attr-defined]

0 commit comments

Comments
 (0)