Skip to content

Commit 3b5e880

Browse files
feat(agent): add quickForm escalation tool
1 parent a3d3ddd commit 3b5e880

7 files changed

Lines changed: 458 additions & 23 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.11.14"
3+
version = "0.11.15"
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"
77
dependencies = [
8-
"uipath>=2.10.74, <2.11.0",
8+
"uipath>=2.10.79, <2.11.0",
99
"uipath-core>=0.5.17, <0.6.0",
1010
"uipath-platform>=0.1.61, <0.2.0",
1111
"uipath-runtime>=0.11.0, <0.12.0",

src/uipath_langchain/agent/tools/escalation_tool.py

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
AgentEscalationRecipient,
1515
AgentEscalationRecipientType,
1616
AgentEscalationResourceConfig,
17+
AgentQuickFormEscalationChannel,
1718
ArgumentEmailRecipient,
1819
ArgumentGroupNameRecipient,
1920
AssetRecipient,
21+
EscalationChannel,
2022
LowCodeAgentDefinition,
2123
StandardRecipient,
2224
)
@@ -37,7 +39,12 @@
3739
StructuredToolWithArgumentProperties,
3840
)
3941

40-
from ..exceptions import AgentRuntimeError, AgentRuntimeErrorCode
42+
from ..exceptions import (
43+
AgentRuntimeError,
44+
AgentRuntimeErrorCode,
45+
AgentStartupError,
46+
AgentStartupErrorCode,
47+
)
4148
from ..react.types import AgentGraphState
4249
from .escalation_memory import (
4350
EscalationMemorySettings,
@@ -250,14 +257,79 @@ def _get_exported_trace_id(trace_id: str | None) -> str | None:
250257
return trace_id
251258

252259

260+
def _try_get_channel_app_name(channel: EscalationChannel) -> str | None:
261+
return (
262+
channel.properties.app_name
263+
if isinstance(channel, AgentEscalationChannel)
264+
else None
265+
)
266+
267+
268+
async def create_task_for_channel(
269+
client: UiPath,
270+
channel: EscalationChannel,
271+
*,
272+
title: str,
273+
data: dict[str, Any],
274+
recipient: TaskRecipient | None,
275+
folder_path: str | None,
276+
) -> Task:
277+
"""Create the human task backing an escalation channel."""
278+
if isinstance(channel, AgentQuickFormEscalationChannel):
279+
schema_id = channel.properties.schema_id
280+
assert schema_id is not None
281+
return await client.tasks.create_quickform_async(
282+
title=title,
283+
task_schema_key=schema_id,
284+
schema=channel.properties.form_schema,
285+
data=data,
286+
folder_path=folder_path,
287+
recipient=recipient,
288+
priority=channel.priority,
289+
labels=channel.labels,
290+
is_actionable_message_enabled=channel.properties.is_actionable_message_enabled,
291+
actionable_message_metadata=channel.properties.actionable_message_meta_data,
292+
)
293+
return await client.tasks.create_async(
294+
title=title,
295+
data=data,
296+
app_name=channel.properties.app_name,
297+
app_folder_path=folder_path,
298+
recipient=recipient,
299+
priority=channel.priority,
300+
labels=channel.labels,
301+
is_actionable_message_enabled=channel.properties.is_actionable_message_enabled,
302+
actionable_message_metadata=channel.properties.actionable_message_meta_data,
303+
)
304+
305+
306+
def _resolve_channel(resource: AgentEscalationResourceConfig) -> EscalationChannel:
307+
"""Return the escalation's channel, validating quick-form configuration."""
308+
channel = resource.channels[0]
309+
if (
310+
isinstance(channel, AgentQuickFormEscalationChannel)
311+
and channel.properties.schema_id is None
312+
):
313+
raise AgentStartupError(
314+
code=AgentStartupErrorCode.INVALID_TOOL_CONFIG,
315+
title="Quick form escalation is missing a schema id",
316+
detail=(
317+
f"Escalation '{channel.name}' has a quick form "
318+
"schema without a schemaId."
319+
),
320+
category=UiPathErrorCategory.USER,
321+
)
322+
return channel
323+
324+
253325
def create_escalation_tool(
254326
resource: AgentEscalationResourceConfig,
255327
agent: LowCodeAgentDefinition | None = None,
256328
) -> StructuredTool:
257-
"""Uses interrupt() for Action Center human-in-the-loop."""
329+
"""Build the human-in-the-loop escalation tool for an escalation resource."""
258330

259331
tool_name: str = f"escalate_{sanitize_tool_name(resource.name)}"
260-
channel: AgentEscalationChannel = resource.channels[0]
332+
channel: EscalationChannel = _resolve_channel(resource)
261333

262334
input_model: Any = create_model(channel.input_schema)
263335
output_model: Any = create_model(channel.output_schema)
@@ -327,16 +399,13 @@ async def escalate(**_tool_kwargs: Any):
327399
@durable_interrupt
328400
async def create_escalation_task():
329401
client = UiPath()
330-
created_task = await client.tasks.create_async(
402+
created_task = await create_task_for_channel(
403+
client,
404+
channel,
331405
title=task_title,
332406
data=serialized_data,
333-
app_name=channel.properties.app_name,
334-
app_folder_path=folder_path,
335407
recipient=recipient,
336-
priority=channel.priority,
337-
labels=channel.labels,
338-
is_actionable_message_enabled=channel.properties.is_actionable_message_enabled,
339-
actionable_message_metadata=channel.properties.actionable_message_meta_data,
408+
folder_path=folder_path,
340409
)
341410

342411
if created_task.id is not None:
@@ -345,7 +414,7 @@ async def create_escalation_task():
345414
return WaitEscalation(
346415
action=created_task,
347416
app_folder_path=folder_path,
348-
app_name=channel.properties.app_name,
417+
app_name=_try_get_channel_app_name(channel),
349418
recipient=recipient,
350419
)
351420

@@ -487,7 +556,7 @@ async def escalation_wrapper(
487556
argument_properties=channel.argument_properties,
488557
metadata={
489558
"tool_type": "escalation",
490-
"display_name": channel.properties.app_name,
559+
"display_name": _try_get_channel_app_name(channel) or channel.name,
491560
"channel_type": channel.type,
492561
"recipient": None,
493562
"args_schema": input_model,

src/uipath_langchain/agent/tools/ixp_escalation_tool.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
from langchain_core.messages import ToolCall
77
from langchain_core.tools import StructuredTool
88
from pydantic import BaseModel
9-
from uipath.agent.models.agent import AgentIxpVsEscalationResourceConfig
9+
from uipath.agent.models.agent import (
10+
AgentEscalationChannel,
11+
AgentIxpVsEscalationResourceConfig,
12+
)
1013
from uipath.eval.mocks import mockable
1114
from uipath.platform import UiPath
1215
from uipath.platform.common import WaitDocumentExtractionValidation
@@ -25,7 +28,12 @@
2528
ToolWrapperReturnType,
2629
)
2730

28-
from ..exceptions import AgentRuntimeError, AgentRuntimeErrorCode
31+
from ..exceptions import (
32+
AgentRuntimeError,
33+
AgentRuntimeErrorCode,
34+
AgentStartupError,
35+
AgentStartupErrorCode,
36+
)
2937
from .structured_tool_with_output_type import StructuredToolWithOutputType
3038
from .utils import (
3139
resolve_task_title,
@@ -42,6 +50,24 @@ class EmptyInput(BaseModel):
4250
pass
4351

4452

53+
def _resolve_action_center_channel(
54+
resource: AgentIxpVsEscalationResourceConfig,
55+
) -> AgentEscalationChannel:
56+
"""Return the VS escalation's Action Center channel, rejecting quick forms."""
57+
channel = resource.channels[0]
58+
if not isinstance(channel, AgentEscalationChannel):
59+
raise AgentStartupError(
60+
code=AgentStartupErrorCode.INVALID_TOOL_CONFIG,
61+
title="Unsupported VS escalation channel",
62+
detail=(
63+
f"VS escalation '{resource.name}' must use an Action Center channel "
64+
f"but received channel type '{channel.type.value}'."
65+
),
66+
category=UiPathErrorCategory.USER,
67+
)
68+
return channel
69+
70+
4571
def create_ixp_escalation_tool(
4672
resource: AgentIxpVsEscalationResourceConfig,
4773
) -> StructuredTool:
@@ -51,7 +77,7 @@ def create_ixp_escalation_tool(
5177
storage_bucket_folder_path: str = (
5278
resource.vs_escalation_properties.storage_bucket_folder_path
5379
)
54-
channel = resource.channels[0]
80+
channel = _resolve_action_center_channel(resource)
5581
action_priority = ActionPriority.from_str(channel.priority)
5682
ixp_tool_name: str = resource.vs_escalation_properties.ixp_tool_id
5783

0 commit comments

Comments
 (0)