Skip to content

Commit 4a253ec

Browse files
authored
Merge pull request #1006 from UiPath/feat/ecs-interrupt-models
feat: suspend mechanism support for ecs long-running workflows
2 parents 234ac9a + d80e687 commit 4a253ec

File tree

10 files changed

+612
-22
lines changed

10 files changed

+612
-22
lines changed

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
[project]
22
name = "uipath"
3-
version = "2.2.32"
3+
version = "2.2.33"
44
description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools."
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
77
dependencies = [
8-
"uipath-runtime>=0.2.5, <0.3.0",
8+
"uipath-runtime>=0.2.7, <0.3.0",
99
"uipath-core>=0.1.3, <0.2.0",
1010
"click>=8.3.1",
1111
"httpx>=0.28.1",
@@ -127,9 +127,9 @@ init_typed = true
127127
warn_required_dynamic_aliases = true
128128

129129
[tool.pytest.ini_options]
130-
testpaths = ["tests"]
131-
python_files = "test_*.py"
132-
addopts = "-ra -q --cov"
130+
#testpaths = ["tests"]
131+
#python_files = "test_*.py"
132+
#addopts = "-ra -q --cov"
133133
asyncio_default_fixture_loop_scope = "function"
134134
asyncio_mode = "auto"
135135

src/uipath/platform/common/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111
from ._folder_context import FolderContext
1212
from .auth import TokenData
1313
from .interrupt_models import (
14+
CreateBatchTransform,
15+
CreateDeepRag,
1416
CreateEscalation,
1517
CreateTask,
1618
InvokeProcess,
19+
WaitBatchTransform,
20+
WaitDeepRag,
1721
WaitEscalation,
1822
WaitJob,
1923
WaitTask,
@@ -36,4 +40,8 @@
3640
"WaitTask",
3741
"WaitJob",
3842
"PagedResult",
43+
"CreateDeepRag",
44+
"WaitDeepRag",
45+
"CreateBatchTransform",
46+
"WaitBatchTransform",
3947
]

src/uipath/platform/common/interrupt_models.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
"""Models for interrupt operations in UiPath platform."""
22

3-
from typing import Any, Dict, Optional
3+
from typing import Annotated, Any, Dict, Optional
44

5-
from pydantic import BaseModel
5+
from pydantic import BaseModel, Field
66

77
from ..action_center import Task
8+
from ..context_grounding import (
9+
BatchTransformCreationResponse,
10+
BatchTransformOutputColumn,
11+
CitationMode,
12+
DeepRagCreationResponse,
13+
)
814
from ..orchestrator import Job
915

1016

@@ -55,3 +61,47 @@ class WaitEscalation(WaitTask):
5561
"""Model representing a wait escalation operation."""
5662

5763
pass
64+
65+
66+
class CreateDeepRag(BaseModel):
67+
"""Model representing a Deep RAG task creation."""
68+
69+
name: str
70+
index_name: Annotated[str, Field(max_length=512)]
71+
prompt: Annotated[str, Field(max_length=250000)]
72+
glob_pattern: Annotated[str, Field(max_length=512, default="*")] = "**"
73+
citation_mode: CitationMode = CitationMode.SKIP
74+
index_folder_key: str | None = None
75+
index_folder_path: str | None = None
76+
77+
78+
class WaitDeepRag(BaseModel):
79+
"""Model representing a wait Deep RAG task."""
80+
81+
deep_rag: DeepRagCreationResponse
82+
index_folder_path: Optional[str] = None
83+
index_folder_key: Optional[str] = None
84+
85+
86+
class CreateBatchTransform(BaseModel):
87+
"""Model representing a Batch Transform task creation."""
88+
89+
name: str
90+
index_name: str
91+
prompt: Annotated[str, Field(max_length=250000)]
92+
output_columns: list[BatchTransformOutputColumn]
93+
storage_bucket_folder_path_prefix: Annotated[str | None, Field(max_length=512)] = (
94+
None
95+
)
96+
enable_web_search_grounding: bool = False
97+
destination_path: str
98+
index_folder_key: str | None = None
99+
index_folder_path: str | None = None
100+
101+
102+
class WaitBatchTransform(BaseModel):
103+
"""Model representing a wait Batch Transform task."""
104+
105+
batch_transform: BatchTransformCreationResponse
106+
index_folder_path: Optional[str] = None
107+
index_folder_key: Optional[str] = None

src/uipath/platform/context_grounding/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
BatchTransformOutputColumn,
77
BatchTransformResponse,
88
BatchTransformStatus,
9+
Citation,
910
CitationMode,
1011
ContextGroundingQueryResponse,
1112
DeepRagCreationResponse,
@@ -60,4 +61,5 @@
6061
"OneDriveSourceConfig",
6162
"PreProcessing",
6263
"SourceConfig",
64+
"Citation",
6365
]

src/uipath/platform/context_grounding/context_grounding.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,28 @@ class DeepRagStatus(str, Enum):
4141
FAILED = "Failed"
4242

4343

44+
class Citation(BaseModel):
45+
"""Model representing a deep RAG citation."""
46+
47+
ordinal: int
48+
page_number: int = Field(alias="pageNumber")
49+
source: str
50+
reference: str
51+
52+
model_config = ConfigDict(
53+
validate_by_name=True,
54+
validate_by_alias=True,
55+
use_enum_values=True,
56+
arbitrary_types_allowed=True,
57+
extra="allow",
58+
)
59+
60+
4461
class DeepRagContent(BaseModel):
4562
"""Model representing a deep RAG task content."""
4663

4764
text: str
48-
citations: list[str]
65+
citations: list[Citation]
4966

5067
model_config = ConfigDict(
5168
validate_by_name=True,

src/uipath/platform/documents/_documents_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,6 @@ def classify(
834834
tag=tag,
835835
document_id=document_id,
836836
)
837-
838837
return self._wait_for_classification(
839838
project_id=project_id,
840839
project_type=project_type,

src/uipath/platform/resume_triggers/_protocol.py

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Implementation of UiPath resume trigger protocols."""
22

33
import json
4+
import os
45
import uuid
56
from typing import Any
67

@@ -21,13 +22,19 @@
2122
from uipath.platform.action_center import Task
2223
from uipath.platform.action_center.tasks import TaskStatus
2324
from uipath.platform.common import (
25+
CreateBatchTransform,
26+
CreateDeepRag,
2427
CreateEscalation,
2528
CreateTask,
2629
InvokeProcess,
30+
WaitBatchTransform,
31+
WaitDeepRag,
2732
WaitEscalation,
2833
WaitJob,
2934
WaitTask,
3035
)
36+
from uipath.platform.context_grounding import DeepRagStatus
37+
from uipath.platform.errors import BatchTransformNotCompleteException
3138
from uipath.platform.orchestrator.job import JobState
3239
from uipath.platform.resume_triggers._enums import PropertyName, TriggerMarker
3340

@@ -55,7 +62,8 @@ class UiPathResumeTriggerReader:
5562
Implements UiPathResumeTriggerReaderProtocol.
5663
"""
5764

58-
def _extract_name_hint(self, field_name: str, payload: Any) -> str | None:
65+
def _extract_field(self, field_name: str, payload: Any) -> str | None:
66+
"""Extracts a field from the payload and returns it if it exists."""
5967
if not payload:
6068
return payload
6169

@@ -100,7 +108,7 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None:
100108
trigger.item_key,
101109
app_folder_key=trigger.folder_key,
102110
app_folder_path=trigger.folder_path,
103-
app_name=self._extract_name_hint("app_name", trigger.payload),
111+
app_name=self._extract_field("app_name", trigger.payload),
104112
)
105113
pending_status = TaskStatus.PENDING.value
106114
unassigned_status = TaskStatus.UNASSIGNED.value
@@ -137,7 +145,7 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None:
137145
trigger.item_key,
138146
folder_key=trigger.folder_key,
139147
folder_path=trigger.folder_path,
140-
process_name=self._extract_name_hint("name", trigger.payload),
148+
process_name=self._extract_field("name", trigger.payload),
141149
)
142150
job_state = (job.state or "").lower()
143151
successful_state = JobState.SUCCESSFUL.value
@@ -177,6 +185,64 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None:
177185
}
178186

179187
return trigger_response
188+
case UiPathResumeTriggerType.DEEP_RAG:
189+
if trigger.item_key:
190+
deep_rag = await uipath.context_grounding.retrieve_deep_rag_async(
191+
trigger.item_key,
192+
index_name=self._extract_field("index_name", trigger.payload),
193+
)
194+
deep_rag_status = deep_rag.last_deep_rag_status
195+
196+
if deep_rag_status in (
197+
DeepRagStatus.QUEUED,
198+
DeepRagStatus.IN_PROGRESS,
199+
):
200+
raise UiPathPendingTriggerError(
201+
ErrorCategory.SYSTEM,
202+
f"DeepRag is not finished yet. Current status: {deep_rag_status}",
203+
)
204+
205+
if deep_rag_status != DeepRagStatus.SUCCESSFUL:
206+
raise UiPathFaultedTriggerError(
207+
ErrorCategory.USER,
208+
f"DeepRag '{deep_rag.name}' did not finish successfully.",
209+
)
210+
211+
trigger_response = deep_rag.content
212+
213+
# if response is an empty dictionary, use Deep Rag state as placeholder value
214+
if not trigger_response:
215+
trigger_response = {
216+
"status": deep_rag_status,
217+
PropertyName.INTERNAL.value: TriggerMarker.NO_CONTENT.value,
218+
}
219+
else:
220+
trigger_response = trigger_response.model_dump()
221+
222+
return trigger_response
223+
224+
case UiPathResumeTriggerType.BATCH_RAG:
225+
if trigger.item_key:
226+
destination_path = self._extract_field(
227+
"destination_path", trigger.payload
228+
)
229+
assert destination_path is not None
230+
try:
231+
await uipath.context_grounding.download_batch_transform_result_async(
232+
trigger.item_key,
233+
destination_path,
234+
validate_status=True,
235+
index_name=self._extract_field(
236+
"index_name", trigger.payload
237+
),
238+
)
239+
except BatchTransformNotCompleteException as e:
240+
raise UiPathPendingTriggerError(
241+
ErrorCategory.SYSTEM,
242+
f"{e.message}",
243+
) from e
244+
245+
return f"Batch transform completed. Modified file available at {os.path.abspath(destination_path)}"
180246

181247
case UiPathResumeTriggerType.API:
182248
if trigger.api_resume and trigger.api_resume.inbox_id:
@@ -190,6 +256,7 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None:
190256
f"Failed to get trigger payload"
191257
f"Error fetching API trigger payload for inbox {trigger.api_resume.inbox_id}: {str(e)}",
192258
) from e
259+
193260
case _:
194261
raise UiPathFaultedTriggerError(
195262
ErrorCategory.SYSTEM,
@@ -256,6 +323,15 @@ async def create_trigger(self, suspend_value: Any) -> UiPathResumeTrigger:
256323
case UiPathResumeTriggerType.API:
257324
self._handle_api_trigger(suspend_value, resume_trigger)
258325

326+
case UiPathResumeTriggerType.DEEP_RAG:
327+
await self._handle_deep_rag_job_trigger(
328+
suspend_value, resume_trigger, uipath
329+
)
330+
case UiPathResumeTriggerType.BATCH_RAG:
331+
await self._handle_batch_rag_job_trigger(
332+
suspend_value, resume_trigger, uipath
333+
)
334+
259335
case _:
260336
raise UiPathFaultedTriggerError(
261337
ErrorCategory.SYSTEM,
@@ -283,6 +359,10 @@ def _determine_trigger_type(self, value: Any) -> UiPathResumeTriggerType:
283359
return UiPathResumeTriggerType.TASK
284360
if isinstance(value, (InvokeProcess, WaitJob)):
285361
return UiPathResumeTriggerType.JOB
362+
if isinstance(value, (CreateDeepRag, WaitDeepRag)):
363+
return UiPathResumeTriggerType.DEEP_RAG
364+
if isinstance(value, (CreateBatchTransform, WaitBatchTransform)):
365+
return UiPathResumeTriggerType.BATCH_RAG
286366
# default to API trigger
287367
return UiPathResumeTriggerType.API
288368

@@ -301,6 +381,10 @@ def _determine_trigger_name(self, value: Any) -> UiPathResumeTriggerName:
301381
return UiPathResumeTriggerName.TASK
302382
if isinstance(value, (InvokeProcess, WaitJob)):
303383
return UiPathResumeTriggerName.JOB
384+
if isinstance(value, (CreateDeepRag, WaitDeepRag)):
385+
return UiPathResumeTriggerName.DEEP_RAG
386+
if isinstance(value, (CreateBatchTransform, WaitBatchTransform)):
387+
return UiPathResumeTriggerName.BATCH_RAG
304388
# default to API trigger
305389
return UiPathResumeTriggerName.API
306390

@@ -333,6 +417,63 @@ async def _handle_task_trigger(
333417
raise Exception("Failed to create action")
334418
resume_trigger.item_key = action.key
335419

420+
async def _handle_deep_rag_job_trigger(
421+
self, value: Any, resume_trigger: UiPathResumeTrigger, uipath: UiPath
422+
) -> None:
423+
"""Handle job-type resume triggers.
424+
425+
Args:
426+
value: The suspend value (InvokeProcess or WaitJob)
427+
resume_trigger: The resume trigger to populate
428+
uipath: The UiPath client instance
429+
"""
430+
resume_trigger.folder_path = value.index_folder_path
431+
resume_trigger.folder_key = value.index_folder_key
432+
if isinstance(value, WaitDeepRag):
433+
resume_trigger.item_key = value.deep_rag.id
434+
elif isinstance(value, CreateDeepRag):
435+
deep_rag = await uipath.context_grounding.start_deep_rag_async(
436+
name=value.name,
437+
index_name=value.index_name,
438+
prompt=value.prompt,
439+
glob_pattern=value.glob_pattern,
440+
citation_mode=value.citation_mode,
441+
folder_path=value.index_folder_path,
442+
folder_key=value.index_folder_key,
443+
)
444+
if not deep_rag:
445+
raise Exception("Failed to start deep rag")
446+
resume_trigger.item_key = deep_rag.id
447+
448+
async def _handle_batch_rag_job_trigger(
449+
self, value: Any, resume_trigger: UiPathResumeTrigger, uipath: UiPath
450+
) -> None:
451+
"""Handle job-type resume triggers.
452+
453+
Args:
454+
value: The suspend value (InvokeProcess or WaitJob)
455+
resume_trigger: The resume trigger to populate
456+
uipath: The UiPath client instance
457+
"""
458+
resume_trigger.folder_path = value.index_folder_path
459+
resume_trigger.folder_key = value.index_folder_key
460+
if isinstance(value, WaitBatchTransform):
461+
resume_trigger.item_key = value.batch_transform.id
462+
elif isinstance(value, CreateBatchTransform):
463+
batch_transform = await uipath.context_grounding.start_batch_transform_async(
464+
name=value.name,
465+
index_name=value.index_name,
466+
prompt=value.prompt,
467+
output_columns=value.output_columns,
468+
storage_bucket_folder_path_prefix=value.storage_bucket_folder_path_prefix,
469+
enable_web_search_grounding=value.enable_web_search_grounding,
470+
folder_path=value.index_folder_path,
471+
folder_key=value.index_folder_key,
472+
)
473+
if not batch_transform:
474+
raise Exception("Failed to start batch transform")
475+
resume_trigger.item_key = batch_transform.id
476+
336477
async def _handle_job_trigger(
337478
self, value: Any, resume_trigger: UiPathResumeTrigger, uipath: UiPath
338479
) -> None:

0 commit comments

Comments
 (0)