From 94c30aa41e713cd8d38dbdc9e30a934b0bfc8301 Mon Sep 17 00:00:00 2001 From: Caleb Martin Date: Tue, 13 Jan 2026 16:42:41 -0800 Subject: [PATCH 1/3] feat: add ecs jit sdk --- .../platform/common/interrupt_models.py | 18 ++ .../platform/context_grounding/__init__.py | 4 + .../_context_grounding_service.py | 126 ++++++++++++-- .../context_grounding/context_grounding.py | 16 ++ .../context_grounding_index.py | 4 +- .../context_grounding_payloads.py | 23 +++ .../platform/resume_triggers/_protocol.py | 73 +++++++- tests/cli/test_hitl.py | 156 ++++++++++++++++++ .../test_context_grounding_service.py | 111 +++++++++++++ uv.lock | 1 + 10 files changed, 511 insertions(+), 21 deletions(-) diff --git a/src/uipath/platform/common/interrupt_models.py b/src/uipath/platform/common/interrupt_models.py index 80d38d676..6754c48e2 100644 --- a/src/uipath/platform/common/interrupt_models.py +++ b/src/uipath/platform/common/interrupt_models.py @@ -4,12 +4,17 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator +from uipath.platform.context_grounding.context_grounding_index import ( + ContextGroundingIndex, +) + from ..action_center.tasks import Task, TaskRecipient from ..context_grounding import ( BatchTransformCreationResponse, BatchTransformOutputColumn, CitationMode, DeepRagCreationResponse, + EphemeralIndexUsage, ) from ..documents import FileContent, StartExtractionResponse from ..orchestrator.job import Job @@ -85,6 +90,19 @@ class WaitDeepRag(BaseModel): index_folder_key: str | None = None +class CreateEphemeralIndex(BaseModel): + """Model representing a Ephemeral Index task creation.""" + + usage: EphemeralIndexUsage + attachments: list[str] + + +class WaitEphemeralIndex(BaseModel): + """Model representing a wait Ephemeral Index task.""" + + index: ContextGroundingIndex + + class CreateBatchTransform(BaseModel): """Model representing a Batch Transform task creation.""" diff --git a/src/uipath/platform/context_grounding/__init__.py b/src/uipath/platform/context_grounding/__init__.py index 1682bc5b8..9272a331b 100644 --- a/src/uipath/platform/context_grounding/__init__.py +++ b/src/uipath/platform/context_grounding/__init__.py @@ -12,6 +12,8 @@ DeepRagCreationResponse, DeepRagResponse, DeepRagStatus, + EphemeralIndexUsage, + IndexStatus, ) from .context_grounding_index import ContextGroundingIndex from .context_grounding_payloads import ( @@ -52,8 +54,10 @@ "DeepRagCreationResponse", "DeepRagResponse", "DeepRagStatus", + "IndexStatus", "DropboxDataSource", "DropboxSourceConfig", + "EphemeralIndexUsage", "GoogleDriveDataSource", "GoogleDriveSourceConfig", "Indexer", diff --git a/src/uipath/platform/context_grounding/_context_grounding_service.py b/src/uipath/platform/context_grounding/_context_grounding_service.py index b7152da61..01ec22095 100644 --- a/src/uipath/platform/context_grounding/_context_grounding_service.py +++ b/src/uipath/platform/context_grounding/_context_grounding_service.py @@ -3,7 +3,6 @@ import httpx from pydantic import Field, TypeAdapter -from typing_extensions import deprecated from ..._utils import Endpoint, RequestSpec, header_folder, resource_override from ..._utils._ssl_context import get_httpx_client_kwargs @@ -30,13 +29,16 @@ ContextGroundingQueryResponse, DeepRagCreationResponse, DeepRagResponse, + EphemeralIndexUsage, ) from .context_grounding_index import ContextGroundingIndex from .context_grounding_payloads import ( + AttachmentsDataSource, BucketDataSource, BucketSourceConfig, ConfluenceDataSource, ConfluenceSourceConfig, + CreateEphemeralIndexPayload, CreateIndexPayload, DropboxDataSource, DropboxSourceConfig, @@ -276,7 +278,6 @@ async def retrieve_async( raise Exception("ContextGroundingIndex not found") from e @traced(name="contextgrounding_retrieve_by_id", run_type="uipath") - @deprecated("Use retrieve instead") def retrieve_by_id( self, id: str, @@ -296,11 +297,7 @@ def retrieve_by_id( Returns: Any: The index information, including its configuration and metadata. """ - spec = self._retrieve_by_id_spec( - id, - folder_key=folder_key, - folder_path=folder_path, - ) + spec = self._retrieve_by_id_spec(id) return self.request( spec.method, @@ -309,7 +306,6 @@ def retrieve_by_id( ).json() @traced(name="contextgrounding_retrieve_by_id", run_type="uipath") - @deprecated("Use retrieve_async instead") async def retrieve_by_id_async( self, id: str, @@ -329,11 +325,7 @@ async def retrieve_by_id_async( Returns: Any: The index information, including its configuration and metadata. """ - spec = self._retrieve_by_id_spec( - id, - folder_key=folder_key, - folder_path=folder_path, - ) + spec = self._retrieve_by_id_spec(id) response = await self.request_async( spec.method, @@ -453,6 +445,66 @@ async def create_index_async( return ContextGroundingIndex.model_validate(response.json()) + @resource_override(resource_type="index") + @traced(name="contextgrounding_create_ephemeral_index", run_type="uipath") + def create_ephemeral_index( + self, + usage: EphemeralIndexUsage, + attachments: list[str], + ) -> ContextGroundingIndex: + """Create a new ephemeral context grounding index. + + Args: + usage (EphemeralIndexUsage): The task type for the ephemeral index (DeepRAG or BatchRAG) + attachments (list[str]): The list of attachments ids from which the ephemeral index will be created + + Returns: + ContextGroundingIndex: The created index information. + """ + spec = self._create_ephemeral_spec( + usage, + attachments, + ) + + response = self.request( + spec.method, + spec.endpoint, + json=spec.json, + headers=spec.headers, + ) + + return ContextGroundingIndex.model_validate(response.json()) + + @resource_override(resource_type="index") + @traced(name="contextgrounding_create_ephemeral_index", run_type="uipath") + async def create_ephemeral_index_async( + self, + usage: EphemeralIndexUsage, + attachments: list[str], + ) -> ContextGroundingIndex: + """Create a new ephemeral context grounding index. + + Args: + usage (EphemeralIndexUsage): The task type for the ephemeral index (DeepRAG or BatchRAG) + attachments (list[str]): The list of attachments ids from which the ephemeral index will be created + + Returns: + ContextGroundingIndex: The created index information. + """ + spec = self._create_ephemeral_spec( + usage, + attachments, + ) + + response = await self.request_async( + spec.method, + spec.endpoint, + json=spec.json, + headers=spec.headers, + ) + + return ContextGroundingIndex.model_validate(response.json()) + @resource_override(resource_type="index", resource_identifier="index_name") @traced(name="contextgrounding_retrieve_deep_rag", run_type="uipath") def retrieve_deep_rag( @@ -1197,6 +1249,34 @@ def _create_spec( }, ) + def _create_ephemeral_spec( + self, + usage: str, + attachments: list[str], + ) -> RequestSpec: + """Create request spec for ephemeral index creation. + + Args: + usage (str): The task in which the ephemeral index will be used for + attachments (list[str]): The list of attachments ids from which the ephemeral index will be created + + Returns: + RequestSpec for the create index request + """ + data_source_dict = self._build_ephemeral_data_source(attachments) + + payload = CreateEphemeralIndexPayload( + usage=usage, + data_source=data_source_dict, + ) + + return RequestSpec( + method="POST", + endpoint=Endpoint("/ecs_/v2/indexes/createephemeral"), + json=payload.model_dump(by_alias=True, exclude_none=True), + headers={}, + ) + def _build_data_source(self, source: SourceConfig) -> Dict[str, Any]: """Build data source configuration from typed source config. @@ -1265,6 +1345,22 @@ def _build_data_source(self, source: SourceConfig) -> Dict[str, Any]: return data_source.model_dump(by_alias=True, exclude_none=True) + def _build_ephemeral_data_source(self, attachments: list[str]) -> Dict[str, Any]: + """Build data source configuration from typed source config. + + Args: + attachments (list[str]): The list of attachments ids from which the ephemeral index will be created + + Returns: + Dictionary with data source configuration for API + """ + data_source = AttachmentsDataSource(attachments=attachments) + return data_source.model_dump( + by_alias=True, + exclude_none=True, + mode="json", + ) + def _retrieve_by_id_spec( self, id: str, @@ -1272,7 +1368,6 @@ def _retrieve_by_id_spec( folder_path: Optional[str] = None, ) -> RequestSpec: folder_key = self._resolve_folder_key(folder_key, folder_path) - return RequestSpec( method="GET", endpoint=Endpoint(f"/ecs_/v2/indexes/{id}"), @@ -1422,9 +1517,6 @@ def _resolve_folder_key(self, folder_key, folder_path): else None ) - if folder_key is None: - raise ValueError("ContextGrounding: Failed to resolve folder key") - return folder_key def _extract_bucket_info(self, index: ContextGroundingIndex) -> Tuple[str, str]: diff --git a/src/uipath/platform/context_grounding/context_grounding.py b/src/uipath/platform/context_grounding/context_grounding.py index 885b02e56..26de4261a 100644 --- a/src/uipath/platform/context_grounding/context_grounding.py +++ b/src/uipath/platform/context_grounding/context_grounding.py @@ -32,6 +32,13 @@ class CitationMode(str, Enum): INLINE = "Inline" +class EphemeralIndexUsage(str, Enum): + """Enum representing possible ephemeral index usage types.""" + + DEEP_RAG = "DeepRAG" + BATCH_RAG = "BatchRAG" + + class DeepRagStatus(str, Enum): """Enum representing possible deep RAG tasks status.""" @@ -41,6 +48,15 @@ class DeepRagStatus(str, Enum): FAILED = "Failed" +class IndexStatus(str, Enum): + """Enum representing possible index tasks status.""" + + QUEUED = "Queued" + IN_PROGRESS = "InProgress" + SUCCESSFUL = "Successful" + FAILED = "Failed" + + class Citation(BaseModel): """Model representing a deep RAG citation.""" diff --git a/src/uipath/platform/context_grounding/context_grounding_index.py b/src/uipath/platform/context_grounding/context_grounding_index.py index 4cbea632a..698fa2b8a 100644 --- a/src/uipath/platform/context_grounding/context_grounding_index.py +++ b/src/uipath/platform/context_grounding/context_grounding_index.py @@ -44,9 +44,9 @@ class ContextGroundingIndex(BaseModel): extra="allow", ) - @field_serializer("last_ingested", "last_queried", when_used="json") + @field_serializer("last_ingested", "last_queried") def serialize_datetime(self, value): - """Serialize datetime fields to ISO 8601 format for JSON output.""" + """Serialize datetime fields to ISO 8601 format.""" if isinstance(value, datetime): return value.isoformat() if value else None return value diff --git a/src/uipath/platform/context_grounding/context_grounding_payloads.py b/src/uipath/platform/context_grounding/context_grounding_payloads.py index 4b64778d7..9060a8dcb 100644 --- a/src/uipath/platform/context_grounding/context_grounding_payloads.py +++ b/src/uipath/platform/context_grounding/context_grounding_payloads.py @@ -82,6 +82,12 @@ class ConfluenceDataSource(DataSourceBase): space_id: str = Field(alias="spaceId", description="Space ID") +class AttachmentsDataSource(BaseModel): + """Data source configuration for Attachments.""" + + attachments: list[str] = Field(description="List of attachment ids") + + class Indexer(BaseModel): """Configuration for periodic indexing of data sources.""" @@ -136,6 +142,23 @@ class CreateIndexPayload(BaseModel): model_config = ConfigDict(populate_by_name=True) +class CreateEphemeralIndexPayload(BaseModel): + """Payload for creating an ephemeral context grounding index. + + Note: data_source is Dict[str, Any] because it may contain additional + fields like 'indexer' that are added dynamically based on configuration. + The data source is still validated through the _build_data_source method + which uses typed models internally. + """ + + usage: str = Field(description="Index usage") + data_source: Dict[str, Any] = Field( + alias="dataSource", description="Data source configuration" + ) + + model_config = ConfigDict(populate_by_name=True) + + # user-facing source configuration models class BaseSourceConfig(BaseModel): """Base configuration for all source types.""" diff --git a/src/uipath/platform/resume_triggers/_protocol.py b/src/uipath/platform/resume_triggers/_protocol.py index 8abf6e790..b9af806d4 100644 --- a/src/uipath/platform/resume_triggers/_protocol.py +++ b/src/uipath/platform/resume_triggers/_protocol.py @@ -35,8 +35,16 @@ WaitJob, WaitTask, ) -from uipath.platform.common.interrupt_models import InvokeSystemAgent, WaitSystemAgent -from uipath.platform.context_grounding import DeepRagStatus +from uipath.platform.common.interrupt_models import ( + CreateEphemeralIndex, + InvokeSystemAgent, + WaitEphemeralIndex, + WaitSystemAgent, +) +from uipath.platform.context_grounding import DeepRagStatus, IndexStatus +from uipath.platform.context_grounding.context_grounding_index import ( + ContextGroundingIndex, +) from uipath.platform.errors import ( BatchTransformNotCompleteException, OperationNotCompleteException, @@ -227,6 +235,35 @@ async def read_trigger(self, trigger: UiPathResumeTrigger) -> Any | None: return trigger_response + case UiPathResumeTriggerType.EPHEMERAL_INDEX: + if trigger.item_key: + index = await uipath.context_grounding.retrieve_by_id_async( + trigger.item_key + ) + + ephemeral_index = ContextGroundingIndex(**index) + + ephemeral_index_status = ephemeral_index.last_ingestion_status + + if ephemeral_index_status in ( + IndexStatus.QUEUED, + IndexStatus.IN_PROGRESS, + ): + raise UiPathPendingTriggerError( + ErrorCategory.SYSTEM, + f"Index ingestion is not finished yet. Current status: {ephemeral_index_status}", + ) + + if ephemeral_index_status != IndexStatus.SUCCESSFUL: + raise UiPathFaultedTriggerError( + ErrorCategory.USER, + f"Index ingestion '{ephemeral_index.name}' did not finish successfully.", + ) + + trigger_response = ephemeral_index.model_dump() + + return trigger_response + case UiPathResumeTriggerType.BATCH_RAG: if trigger.item_key: destination_path = self._extract_field( @@ -349,6 +386,10 @@ async def create_trigger(self, suspend_value: Any) -> UiPathResumeTrigger: await self._handle_deep_rag_job_trigger( suspend_value, resume_trigger ) + case UiPathResumeTriggerType.EPHEMERAL_INDEX: + await self._handle_ephemeral_index_job_trigger( + suspend_value, resume_trigger + ) case UiPathResumeTriggerType.BATCH_RAG: await self._handle_batch_rag_job_trigger( suspend_value, resume_trigger @@ -388,6 +429,8 @@ def _determine_trigger_type(self, value: Any) -> UiPathResumeTriggerType: return UiPathResumeTriggerType.JOB if isinstance(value, (CreateDeepRag, WaitDeepRag)): return UiPathResumeTriggerType.DEEP_RAG + if isinstance(value, (CreateEphemeralIndex, WaitEphemeralIndex)): + return UiPathResumeTriggerType.EPHEMERAL_INDEX if isinstance(value, (CreateBatchTransform, WaitBatchTransform)): return UiPathResumeTriggerType.BATCH_RAG if isinstance(value, (DocumentExtraction, WaitDocumentExtraction)): @@ -414,6 +457,8 @@ def _determine_trigger_name(self, value: Any) -> UiPathResumeTriggerName: return UiPathResumeTriggerName.JOB if isinstance(value, (CreateDeepRag, WaitDeepRag)): return UiPathResumeTriggerName.DEEP_RAG + if isinstance(value, (CreateEphemeralIndex, WaitEphemeralIndex)): + return UiPathResumeTriggerName.EPHEMERAL_INDEX if isinstance(value, (CreateBatchTransform, WaitBatchTransform)): return UiPathResumeTriggerName.BATCH_RAG if isinstance(value, (DocumentExtraction, WaitDocumentExtraction)): @@ -479,6 +524,30 @@ async def _handle_deep_rag_job_trigger( raise Exception("Failed to start deep rag") resume_trigger.item_key = deep_rag.id + async def _handle_ephemeral_index_job_trigger( + self, value: Any, resume_trigger: UiPathResumeTrigger + ) -> None: + """Handle ephemeral index. + + Args: + value: The suspend value (CreateEphemeralIndex or WaitEphemeralIndex) + resume_trigger: The resume trigger to populate + + """ + if isinstance(value, WaitEphemeralIndex): + resume_trigger.item_key = value.index.id + elif isinstance(value, CreateEphemeralIndex): + uipath = UiPath() + ephemeral_index = ( + await uipath.context_grounding.create_ephemeral_index_async( + usage=value.usage, + attachments=value.attachments, + ) + ) + if not ephemeral_index: + raise Exception("Failed to create ephemeral index") + resume_trigger.item_key = ephemeral_index.id + async def _handle_batch_rag_job_trigger( self, value: Any, resume_trigger: UiPathResumeTrigger ) -> None: diff --git a/tests/cli/test_hitl.py b/tests/cli/test_hitl.py index 5b0ec6344..8430756da 100644 --- a/tests/cli/test_hitl.py +++ b/tests/cli/test_hitl.py @@ -27,6 +27,10 @@ WaitSystemAgent, WaitTask, ) +from uipath.platform.common.interrupt_models import ( + CreateEphemeralIndex, + WaitEphemeralIndex, +) from uipath.platform.context_grounding import ( BatchTransformCreationResponse, BatchTransformOutputColumn, @@ -34,6 +38,11 @@ CitationMode, DeepRagCreationResponse, DeepRagStatus, + EphemeralIndexUsage, + IndexStatus, +) +from uipath.platform.context_grounding.context_grounding_index import ( + ContextGroundingIndex, ) from uipath.platform.orchestrator import ( Job, @@ -561,6 +570,95 @@ async def test_read_batch_rag_trigger_pending( reader = UiPathResumeTriggerReader() await reader.read_trigger(resume_trigger) + @pytest.mark.anyio + async def test_read_ephemeral_index_trigger_successful( + self, + setup_test_env: None, + ) -> None: + """Test reading a successful ephemeral index trigger.""" + index_id = "test-ephemeral-index-id" + index_data = { + "id": index_id, + "name": "test-index", + "lastIngestionStatus": IndexStatus.SUCCESSFUL.value, + } + + mock_retrieve_by_id = AsyncMock(return_value=index_data) + + with patch( + "uipath.platform.context_grounding._context_grounding_service.ContextGroundingService.retrieve_by_id_async", + new=mock_retrieve_by_id, + ): + resume_trigger = UiPathResumeTrigger( + trigger_type=UiPathResumeTriggerType.EPHEMERAL_INDEX, + item_key=index_id, + ) + reader = UiPathResumeTriggerReader() + result = await reader.read_trigger(resume_trigger) + + assert isinstance(result, dict) + assert result["id"] == index_id + mock_retrieve_by_id.assert_called_once_with(index_id) + + @pytest.mark.anyio + async def test_read_ephemeral_index_trigger_pending( + self, + setup_test_env: None, + ) -> None: + """Test reading a pending ephemeral index trigger raises pending error.""" + from uipath.core.errors import UiPathPendingTriggerError + + index_id = "test-ephemeral-index-id" + index_data = { + "id": index_id, + "name": "test-index", + "lastIngestionStatus": IndexStatus.IN_PROGRESS.value, + } + + mock_retrieve_by_id = AsyncMock(return_value=index_data) + + with patch( + "uipath.platform.context_grounding._context_grounding_service.ContextGroundingService.retrieve_by_id_async", + new=mock_retrieve_by_id, + ): + resume_trigger = UiPathResumeTrigger( + trigger_type=UiPathResumeTriggerType.EPHEMERAL_INDEX, + item_key=index_id, + ) + + with pytest.raises(UiPathPendingTriggerError): + reader = UiPathResumeTriggerReader() + await reader.read_trigger(resume_trigger) + + @pytest.mark.anyio + async def test_read_ephemeral_index_trigger_failed( + self, + setup_test_env: None, + ) -> None: + """Test reading a failed ephemeral index trigger raises faulted error.""" + index_id = "test-ephemeral-index-id" + index_data = { + "id": index_id, + "name": "test-index", + "lastIngestionStatus": IndexStatus.FAILED.value, + } + + mock_retrieve_by_id = AsyncMock(return_value=index_data) + + with patch( + "uipath.platform.context_grounding._context_grounding_service.ContextGroundingService.retrieve_by_id_async", + new=mock_retrieve_by_id, + ): + resume_trigger = UiPathResumeTrigger( + trigger_type=UiPathResumeTriggerType.EPHEMERAL_INDEX, + item_key=index_id, + ) + + with pytest.raises(UiPathFaultedTriggerError) as exc_info: + reader = UiPathResumeTriggerReader() + await reader.read_trigger(resume_trigger) + assert exc_info.value.args[0] == ErrorCategory.USER + class TestHitlProcessor: """Tests for the HitlProcessor class.""" @@ -882,6 +980,64 @@ async def test_create_resume_trigger_wait_batch_transform( assert resume_trigger.trigger_type == UiPathResumeTriggerType.BATCH_RAG assert resume_trigger.item_key == batch_transform_id + @pytest.mark.anyio + async def test_create_resume_trigger_create_ephemeral_index( + self, + setup_test_env: None, + ) -> None: + """Test creating a resume trigger for CreateEphemeralIndex.""" + index_id = "test-ephemeral-index-id" + attachments = ["attachment-uuid-1", "attachment-uuid-2"] + create_ephemeral_index = CreateEphemeralIndex( + usage=EphemeralIndexUsage.DEEP_RAG, + attachments=attachments, + ) + + mock_index = ContextGroundingIndex( + id=index_id, + name="ephemeral-index", + last_ingestion_status=IndexStatus.QUEUED.value, + ) + mock_create_ephemeral_index = AsyncMock(return_value=mock_index) + + with patch( + "uipath.platform.context_grounding._context_grounding_service.ContextGroundingService.create_ephemeral_index_async", + new=mock_create_ephemeral_index, + ): + processor = UiPathResumeTriggerCreator() + resume_trigger = await processor.create_trigger(create_ephemeral_index) + + assert resume_trigger is not None + assert ( + resume_trigger.trigger_type == UiPathResumeTriggerType.EPHEMERAL_INDEX + ) + assert resume_trigger.item_key == index_id + mock_create_ephemeral_index.assert_called_once_with( + usage=create_ephemeral_index.usage, + attachments=create_ephemeral_index.attachments, + ) + + @pytest.mark.anyio + async def test_create_resume_trigger_wait_ephemeral_index( + self, + setup_test_env: None, + ) -> None: + """Test creating a resume trigger for WaitEphemeralIndex.""" + index_id = "test-ephemeral-index-id" + ephemeral_index = ContextGroundingIndex( + id=index_id, + name="ephemeral-index", + last_ingestion_status=IndexStatus.IN_PROGRESS.value, + ) + wait_ephemeral_index = WaitEphemeralIndex(index=ephemeral_index) + + processor = UiPathResumeTriggerCreator() + resume_trigger = await processor.create_trigger(wait_ephemeral_index) + + assert resume_trigger is not None + assert resume_trigger.trigger_type == UiPathResumeTriggerType.EPHEMERAL_INDEX + assert resume_trigger.item_key == index_id + class TestDocumentExtractionModels: """Tests for document extraction models.""" diff --git a/tests/sdk/services/test_context_grounding_service.py b/tests/sdk/services/test_context_grounding_service.py index 72efef02c..a746d9e39 100644 --- a/tests/sdk/services/test_context_grounding_service.py +++ b/tests/sdk/services/test_context_grounding_service.py @@ -1760,6 +1760,117 @@ def test_download_batch_transform_result_creates_nested_directories( assert destination.read_bytes() == b"col1,col2\nval1,val2" assert destination.parent.exists() + def test_create_ephemeral_index( + self, + httpx_mock: HTTPXMock, + service: ContextGroundingService, + base_url: str, + org: str, + tenant: str, + version: str, + ) -> None: + import uuid + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/ecs_/v2/indexes/createephemeral", + status_code=200, + json={ + "id": "ephemeral-index-id", + "name": "ephemeral-index", + "lastIngestionStatus": "Queued", + }, + ) + + attachment_ids = [str(uuid.uuid4()), str(uuid.uuid4())] + index = service.create_ephemeral_index( + usage="DeepRAG", + attachments=attachment_ids, + ) + + assert isinstance(index, ContextGroundingIndex) + assert index.id == "ephemeral-index-id" + assert index.name == "ephemeral-index" + assert index.last_ingestion_status == "Queued" + + sent_requests = httpx_mock.get_requests() + if sent_requests is None: + raise Exception("No request was sent") + + assert sent_requests[0].method == "POST" + assert ( + sent_requests[0].url + == f"{base_url}{org}{tenant}/ecs_/v2/indexes/createephemeral" + ) + + request_data = json.loads(sent_requests[0].content) + assert request_data["usage"] == "DeepRAG" + assert "dataSource" in request_data + assert request_data["dataSource"]["attachments"] == [ + str(att) for att in attachment_ids + ] + + assert HEADER_USER_AGENT in sent_requests[0].headers + assert ( + sent_requests[0].headers[HEADER_USER_AGENT] + == f"UiPath.Python.Sdk/UiPath.Python.Sdk.Activities.ContextGroundingService.create_ephemeral_index/{version}" + ) + + @pytest.mark.anyio + async def test_create_ephemeral_index_async( + self, + httpx_mock: HTTPXMock, + service: ContextGroundingService, + base_url: str, + org: str, + tenant: str, + version: str, + ) -> None: + import uuid + + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}/ecs_/v2/indexes/createephemeral", + status_code=200, + json={ + "id": "ephemeral-index-id", + "name": "ephemeral-index", + "lastIngestionStatus": "Queued", + }, + ) + + attachment_ids = [str(uuid.uuid4()), str(uuid.uuid4())] + index = await service.create_ephemeral_index_async( + usage="DeepRAG", + attachments=attachment_ids, + ) + + assert isinstance(index, ContextGroundingIndex) + assert index.id == "ephemeral-index-id" + assert index.name == "ephemeral-index" + assert index.last_ingestion_status == "Queued" + + sent_requests = httpx_mock.get_requests() + if sent_requests is None: + raise Exception("No request was sent") + + assert sent_requests[0].method == "POST" + assert ( + sent_requests[0].url + == f"{base_url}{org}{tenant}/ecs_/v2/indexes/createephemeral" + ) + + request_data = json.loads(sent_requests[0].content) + assert request_data["usage"] == "DeepRAG" + assert "dataSource" in request_data + assert request_data["dataSource"]["attachments"] == [ + str(att) for att in attachment_ids + ] + + assert HEADER_USER_AGENT in sent_requests[0].headers + assert ( + sent_requests[0].headers[HEADER_USER_AGENT] + == f"UiPath.Python.Sdk/UiPath.Python.Sdk.Activities.ContextGroundingService.create_ephemeral_index_async/{version}" + ) + @pytest.mark.anyio async def test_download_batch_transform_result_async_creates_nested_directories( self, diff --git a/uv.lock b/uv.lock index e8204d477..eba38283b 100644 --- a/uv.lock +++ b/uv.lock @@ -2492,6 +2492,7 @@ wheels = [ [[package]] name = "uipath" version = "2.6.0" + source = { editable = "." } dependencies = [ { name = "applicationinsights" }, From feb7340cba28d5274209b080a58e2c16ecef52e9 Mon Sep 17 00:00:00 2001 From: Caleb Martin Date: Fri, 23 Jan 2026 14:24:00 -0800 Subject: [PATCH 2/3] fix: update version --- pyproject.toml | 1 + .../context_grounding/_context_grounding_service.py | 13 +++++++++++-- uv.lock | 1 - 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0c895a43a..33ec48407 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [project] name = "uipath" version = "2.6.0" + description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/src/uipath/platform/context_grounding/_context_grounding_service.py b/src/uipath/platform/context_grounding/_context_grounding_service.py index 01ec22095..31cadcc0a 100644 --- a/src/uipath/platform/context_grounding/_context_grounding_service.py +++ b/src/uipath/platform/context_grounding/_context_grounding_service.py @@ -297,7 +297,11 @@ def retrieve_by_id( Returns: Any: The index information, including its configuration and metadata. """ - spec = self._retrieve_by_id_spec(id) + spec = self._retrieve_by_id_spec( + id, + folder_key=folder_key, + folder_path=folder_path, + ) return self.request( spec.method, @@ -325,7 +329,11 @@ async def retrieve_by_id_async( Returns: Any: The index information, including its configuration and metadata. """ - spec = self._retrieve_by_id_spec(id) + spec = self._retrieve_by_id_spec( + id, + folder_key=folder_key, + folder_path=folder_path, + ) response = await self.request_async( spec.method, @@ -1368,6 +1376,7 @@ def _retrieve_by_id_spec( folder_path: Optional[str] = None, ) -> RequestSpec: folder_key = self._resolve_folder_key(folder_key, folder_path) + return RequestSpec( method="GET", endpoint=Endpoint(f"/ecs_/v2/indexes/{id}"), diff --git a/uv.lock b/uv.lock index eba38283b..e8204d477 100644 --- a/uv.lock +++ b/uv.lock @@ -2492,7 +2492,6 @@ wheels = [ [[package]] name = "uipath" version = "2.6.0" - source = { editable = "." } dependencies = [ { name = "applicationinsights" }, From ed292fe11c314b376dedffea5aa7189671c6b9de Mon Sep 17 00:00:00 2001 From: Caleb Martin Date: Sun, 25 Jan 2026 10:23:30 -0800 Subject: [PATCH 3/3] fix: version bump --- pyproject.toml | 3 +-- uv.lock | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 33ec48407..9d78f227e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,6 @@ [project] name = "uipath" -version = "2.6.0" - +version = "2.6.1" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/uv.lock b/uv.lock index e8204d477..0bca67c6f 100644 --- a/uv.lock +++ b/uv.lock @@ -2491,7 +2491,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.6.0" +version = "2.6.1" source = { editable = "." } dependencies = [ { name = "applicationinsights" },