Skip to content

Commit aeb2cea

Browse files
add support for asset recipient in escalations
1 parent e8ba143 commit aeb2cea

File tree

2 files changed

+138
-4
lines changed

2 files changed

+138
-4
lines changed

src/uipath_langchain/agent/tools/escalation_tool.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
from langgraph.types import Command, interrupt
1111
from uipath.agent.models.agent import (
1212
AgentEscalationChannel,
13+
AgentEscalationRecipient,
1314
AgentEscalationRecipientType,
1415
AgentEscalationResourceConfig,
1516
)
1617
from uipath.eval.mocks import mockable
18+
from uipath.platform import UiPath
1719
from uipath.platform.common import CreateEscalation
1820

1921
from ..react.types import AgentGraphNode, AgentTerminationSource
@@ -27,6 +29,34 @@ class EscalationAction(str, Enum):
2729
END = "end"
2830

2931

32+
def resolve_recipient_value(recipient: AgentEscalationRecipient) -> str | None:
33+
"""Resolve recipient value based on recipient type."""
34+
if recipient.type == AgentEscalationRecipientType.ASSET_USER_EMAIL:
35+
return resolve_asset(recipient.asset_name, recipient.folder_path)
36+
37+
# For USER_EMAIL, USER_ID, and GROUP_ID, return the value directly
38+
if hasattr(recipient, "value"):
39+
return recipient.value
40+
41+
return None
42+
43+
44+
def resolve_asset(asset_name: str, folder_path: str) -> str | None:
45+
"""Retrieve asset value."""
46+
try:
47+
client = UiPath()
48+
result = client.assets.retrieve(name=asset_name, folder_path=folder_path)
49+
50+
if not result or not result.value:
51+
raise ValueError(f"Asset '{asset_name}' has no value configured.")
52+
53+
return result.value
54+
except Exception as e:
55+
raise ValueError(
56+
f"Failed to resolve asset '{asset_name}' in folder '{folder_path}': {str(e)}"
57+
) from e
58+
59+
3060
def create_escalation_tool(resource: AgentEscalationResourceConfig) -> StructuredTool:
3161
"""Uses interrupt() for Action Center human-in-the-loop."""
3262

@@ -37,10 +67,7 @@ def create_escalation_tool(resource: AgentEscalationResourceConfig) -> Structure
3767
output_model: Any = create_model(channel.output_schema)
3868

3969
assignee: str | None = (
40-
channel.recipients[0].value
41-
if channel.recipients
42-
and channel.recipients[0].type == AgentEscalationRecipientType.USER_EMAIL
43-
else None
70+
resolve_recipient_value(channel.recipients[0]) if channel.recipients else None
4471
)
4572

4673
@mockable(
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""Tests for escalation_tool.py module."""
2+
3+
import pytest
4+
from unittest.mock import Mock, patch
5+
6+
from uipath.agent.models.agent import (
7+
AgentEscalationRecipientType,
8+
StandardRecipient,
9+
AssetRecipient,
10+
)
11+
from uipath_langchain.agent.tools.escalation_tool import (
12+
resolve_recipient_value,
13+
resolve_asset,
14+
)
15+
16+
17+
class TestResolveAsset:
18+
"""Tests for resolve_asset function."""
19+
20+
@patch("uipath_langchain.agent.tools.escalation_tool.UiPath")
21+
def test_resolve_asset_success(self, mock_uipath):
22+
"""Test successful asset resolution."""
23+
# Mock the asset retrieval
24+
mock_client = Mock()
25+
mock_result = Mock()
26+
mock_result.value = "test@example.com"
27+
mock_client.assets.retrieve.return_value = mock_result
28+
mock_uipath.return_value = mock_client
29+
30+
result = resolve_asset("email_asset", "Shared")
31+
32+
assert result == "test@example.com"
33+
mock_client.assets.retrieve.assert_called_once_with(
34+
name="email_asset", folder_path="Shared"
35+
)
36+
37+
@patch("uipath_langchain.agent.tools.escalation_tool.UiPath")
38+
def test_resolve_asset_not_found(self, mock_uipath):
39+
"""Test asset resolution when asset doesn't exist."""
40+
mock_client = Mock()
41+
mock_client.assets.retrieve.side_effect = Exception("Asset not found")
42+
mock_uipath.return_value = mock_client
43+
44+
with pytest.raises(Exception) as exc_info:
45+
resolve_asset("nonexistent_asset", "Shared")
46+
47+
assert "Asset not found" in str(exc_info.value)
48+
49+
@patch("uipath_langchain.agent.tools.escalation_tool.UiPath")
50+
def test_resolve_asset_raises_error_when_value_is_empty(self, mock_uipath):
51+
"""Test asset resolution raises error when asset value is empty."""
52+
mock_client = Mock()
53+
mock_result = Mock()
54+
mock_result.value = None
55+
mock_client.assets.retrieve.return_value = mock_result
56+
mock_uipath.return_value = mock_client
57+
58+
with pytest.raises(ValueError) as exc_info:
59+
resolve_asset("empty_asset", "Shared")
60+
61+
assert "has no value" in str(exc_info.value)
62+
63+
64+
class TestResolveRecipientValue:
65+
"""Tests for resolve_recipient_value function."""
66+
67+
def test_resolve_recipient_value_returns_email_for_user_email_type(self):
68+
"""Test resolving StandardRecipient with USER_EMAIL."""
69+
recipient = StandardRecipient(
70+
type=AgentEscalationRecipientType.USER_EMAIL, value="user@example.com"
71+
)
72+
73+
result = resolve_recipient_value(recipient)
74+
75+
assert result == "user@example.com"
76+
77+
@patch("uipath_langchain.agent.tools.escalation_tool.resolve_asset")
78+
def test_resolve_recipient_value_calls_resolve_asset_for_asset_recipient(self, mock_resolve_asset):
79+
"""Test resolving AssetRecipient calls resolve_asset."""
80+
mock_resolve_asset.return_value = "asset@example.com"
81+
82+
recipient = AssetRecipient(
83+
type=AgentEscalationRecipientType.ASSET_USER_EMAIL,
84+
asset_name="email_asset",
85+
folder_path="Shared",
86+
)
87+
88+
result = resolve_recipient_value(recipient)
89+
90+
assert result == "asset@example.com"
91+
mock_resolve_asset.assert_called_once_with("email_asset", "Shared")
92+
93+
@patch("uipath_langchain.agent.tools.escalation_tool.resolve_asset")
94+
def test_resolve_recipient_value_propagates_error_when_asset_resolution_fails(self, mock_resolve_asset):
95+
"""Test AssetRecipient when asset resolution fails."""
96+
mock_resolve_asset.side_effect = ValueError("Asset not found")
97+
98+
recipient = AssetRecipient(
99+
type=AgentEscalationRecipientType.ASSET_USER_EMAIL,
100+
asset_name="nonexistent",
101+
folder_path="Shared",
102+
)
103+
104+
with pytest.raises(ValueError) as exc_info:
105+
resolve_recipient_value(recipient)
106+
107+
assert "Asset not found" in str(exc_info.value)

0 commit comments

Comments
 (0)