Skip to content

Commit ee98f4d

Browse files
authored
feat: add support for system indexes [ECS-1791] (#1605)
1 parent 1732f7a commit ee98f4d

9 files changed

Lines changed: 683 additions & 22 deletions

File tree

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.1.41"
3+
version = "0.1.42"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/context_grounding/_context_grounding_service.py

Lines changed: 131 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ..errors import (
2020
BatchTransformFailedException,
2121
BatchTransformNotCompleteException,
22+
ContextGroundingIndexNotFoundError,
2223
IngestionInProgressException,
2324
UnsupportedDataSourceException,
2425
)
@@ -256,37 +257,81 @@ async def retrieve_across_folders_async(
256257
ContextGroundingIndex.model_validate(item) for item in response["value"]
257258
]
258259

260+
@traced(name="contextgrounding_retrieve_system_indexes", run_type="uipath")
261+
def _retrieve_system_indexes(
262+
self,
263+
name: Optional[str] = None,
264+
) -> List[ContextGroundingIndex]:
265+
spec = self._retrieve_system_indexes_spec(name=name)
266+
267+
response = self.request(
268+
spec.method,
269+
spec.endpoint,
270+
params=spec.params,
271+
).json()
272+
273+
return [
274+
ContextGroundingIndex.model_validate(item) for item in response["value"]
275+
]
276+
277+
@traced(name="contextgrounding_retrieve_system_indexes", run_type="uipath")
278+
async def _retrieve_system_indexes_async(
279+
self,
280+
name: Optional[str] = None,
281+
) -> List[ContextGroundingIndex]:
282+
spec = self._retrieve_system_indexes_spec(name=name)
283+
284+
response = (
285+
await self.request_async(
286+
spec.method,
287+
spec.endpoint,
288+
params=spec.params,
289+
)
290+
).json()
291+
292+
return [
293+
ContextGroundingIndex.model_validate(item) for item in response["value"]
294+
]
295+
259296
@resource_override(resource_type="index")
260297
@traced(name="contextgrounding_retrieve", run_type="uipath")
261298
def retrieve(
262299
self,
263300
name: str,
264301
folder_key: Optional[str] = None,
265302
folder_path: Optional[str] = None,
303+
include_system_indexes: bool = False,
266304
) -> ContextGroundingIndex:
267305
"""Retrieve context grounding index information by its name.
268306
269307
If no folder_key or folder_path is provided and no folder context is
270-
configured, falls back to searching across all folders.
308+
configured, falls back to searching across all folders. When
309+
``include_system_indexes`` is True, an additional fallback against
310+
system indexes is attempted before raising not-found.
271311
272312
Args:
273313
name (str): The name of the context index to retrieve.
274314
folder_key (Optional[str]): The key of the folder where the index resides.
275315
folder_path (Optional[str]): The path of the folder where the index resides.
316+
include_system_indexes (bool): If True, fall back to system indexes
317+
when the index is not found in the per-folder or across-folders listings.
318+
Defaults to False.
276319
277320
Returns:
278321
ContextGroundingIndex: The index information, including its configuration and metadata if found.
279322
280323
Raises:
281-
Exception: If no index with the given name is found.
324+
ContextGroundingIndexNotFoundError: If no index with the given name is found.
282325
"""
283326
resolved_folder_key = self._resolve_folder_key(folder_key, folder_path)
284327
if not resolved_folder_key:
285328
indexes = self.retrieve_across_folders(name=name)
286329
try:
287330
return next(index for index in indexes if index.name == name)
288-
except StopIteration as e:
289-
raise Exception("ContextGroundingIndex not found") from e
331+
except StopIteration:
332+
if include_system_indexes:
333+
return self._retrieve_from_system_indexes(name)
334+
raise ContextGroundingIndexNotFoundError(name) from None
290335

291336
spec = self._retrieve_spec(
292337
name,
@@ -305,8 +350,10 @@ def retrieve(
305350
for item in response["value"]
306351
if item["name"] == name
307352
)
308-
except StopIteration as e:
309-
raise Exception("ContextGroundingIndex not found") from e
353+
except StopIteration:
354+
if include_system_indexes:
355+
return self._retrieve_from_system_indexes(name)
356+
raise ContextGroundingIndexNotFoundError(name) from None
310357

311358
@resource_override(resource_type="index")
312359
@traced(name="contextgrounding_retrieve", run_type="uipath")
@@ -315,30 +362,38 @@ async def retrieve_async(
315362
name: str,
316363
folder_key: Optional[str] = None,
317364
folder_path: Optional[str] = None,
365+
include_system_indexes: bool = False,
318366
) -> ContextGroundingIndex:
319367
"""Asynchronously retrieve context grounding index information by its name.
320368
321369
If no folder_key or folder_path is provided and no folder context is
322-
configured, falls back to searching across all folders.
370+
configured, falls back to searching across all folders. When
371+
``include_system_indexes`` is True, an additional fallback against
372+
system indexes is attempted before raising not-found.
323373
324374
Args:
325375
name (str): The name of the context index to retrieve.
326376
folder_key (Optional[str]): The key of the folder where the index resides.
327377
folder_path (Optional[str]): The path of the folder where the index resides.
378+
include_system_indexes (bool): If True, fall back to system indexes when
379+
the index is not found in the per-folder or across-folders listings.
380+
Defaults to False.
328381
329382
Returns:
330383
ContextGroundingIndex: The index information, including its configuration and metadata if found.
331384
332385
Raises:
333-
Exception: If no index with the given name is found.
386+
ContextGroundingIndexNotFoundError: If no index with the given name is found.
334387
"""
335388
resolved_folder_key = self._resolve_folder_key(folder_key, folder_path)
336389
if not resolved_folder_key:
337390
indexes = await self.retrieve_across_folders_async(name=name)
338391
try:
339392
return next(index for index in indexes if index.name == name)
340-
except StopIteration as e:
341-
raise Exception("ContextGroundingIndex not found") from e
393+
except StopIteration:
394+
if include_system_indexes:
395+
return await self._retrieve_from_system_indexes_async(name)
396+
raise ContextGroundingIndexNotFoundError(name) from None
342397

343398
spec = self._retrieve_spec(
344399
name,
@@ -359,8 +414,26 @@ async def retrieve_async(
359414
for item in response["value"]
360415
if item["name"] == name
361416
)
362-
except StopIteration as e:
363-
raise Exception("ContextGroundingIndex not found") from e
417+
except StopIteration:
418+
if include_system_indexes:
419+
return await self._retrieve_from_system_indexes_async(name)
420+
raise ContextGroundingIndexNotFoundError(name) from None
421+
422+
def _retrieve_from_system_indexes(self, name: str) -> ContextGroundingIndex:
423+
indexes = self._retrieve_system_indexes(name=name)
424+
try:
425+
return next(index for index in indexes if index.name == name)
426+
except StopIteration:
427+
raise ContextGroundingIndexNotFoundError(name) from None
428+
429+
async def _retrieve_from_system_indexes_async(
430+
self, name: str
431+
) -> ContextGroundingIndex:
432+
indexes = await self._retrieve_system_indexes_async(name=name)
433+
try:
434+
return next(index for index in indexes if index.name == name)
435+
except StopIteration:
436+
raise ContextGroundingIndexNotFoundError(name) from None
364437

365438
@traced(name="contextgrounding_list", run_type="uipath")
366439
def list(
@@ -1489,6 +1562,7 @@ def unified_search(
14891562
scope: Optional[UnifiedSearchScope] = None,
14901563
folder_key: Optional[str] = None,
14911564
folder_path: Optional[str] = None,
1565+
include_system_indexes: bool = False,
14921566
) -> UnifiedQueryResult:
14931567
"""Perform a unified search on a context grounding index.
14941568
@@ -1504,11 +1578,19 @@ def unified_search(
15041578
scope (Optional[UnifiedSearchScope]): Optional search scope (folder, extension).
15051579
folder_key (Optional[str]): The key of the folder where the index resides.
15061580
folder_path (Optional[str]): The path of the folder where the index resides.
1581+
include_system_indexes (bool): If True, fall back to tenant-wide
1582+
system indexes when the index is not found in folder or
1583+
across-folders listings. Defaults to False.
15071584
15081585
Returns:
15091586
UnifiedQueryResult: The unified search result containing semantic and/or tabular results.
15101587
"""
1511-
index = self.retrieve(name, folder_key=folder_key, folder_path=folder_path)
1588+
index = self.retrieve(
1589+
name,
1590+
folder_key=folder_key,
1591+
folder_path=folder_path,
1592+
include_system_indexes=include_system_indexes,
1593+
)
15121594

15131595
folder_key = folder_key or index.folder_key
15141596

@@ -1544,6 +1626,7 @@ async def unified_search_async(
15441626
scope: Optional[UnifiedSearchScope] = None,
15451627
folder_key: Optional[str] = None,
15461628
folder_path: Optional[str] = None,
1629+
include_system_indexes: bool = False,
15471630
) -> UnifiedQueryResult:
15481631
"""Asynchronously perform a unified search on a context grounding index.
15491632
@@ -1559,12 +1642,18 @@ async def unified_search_async(
15591642
scope (Optional[UnifiedSearchScope]): Optional search scope (folder, extension).
15601643
folder_key (Optional[str]): The key of the folder where the index resides.
15611644
folder_path (Optional[str]): The path of the folder where the index resides.
1645+
include_system_indexes (bool): If True, fall back to tenant-wide
1646+
system indexes when the index is not found in folder or
1647+
across-folders listings. Defaults to False.
15621648
15631649
Returns:
15641650
UnifiedQueryResult: The unified search result containing semantic and/or tabular results.
15651651
"""
15661652
index = await self.retrieve_async(
1567-
name, folder_key=folder_key, folder_path=folder_path
1653+
name,
1654+
folder_key=folder_key,
1655+
folder_path=folder_path,
1656+
include_system_indexes=include_system_indexes,
15681657
)
15691658
if index and index.in_progress_ingestion():
15701659
raise IngestionInProgressException(index_name=name)
@@ -1911,6 +2000,16 @@ def _ingest_spec(
19112000
},
19122001
)
19132002

2003+
@staticmethod
2004+
def _odata_name_filter(name: str) -> str:
2005+
"""Build an OData ``Name eq '<name>'`` filter with single quotes escaped.
2006+
2007+
OData string literals escape ``'`` by doubling it. URL encoding of the
2008+
resulting filter is handled by the HTTP client when params are passed
2009+
as a dict.
2010+
"""
2011+
return "Name eq '{}'".format(name.replace("'", "''"))
2012+
19142013
def _retrieve_across_folders_spec(
19152014
self,
19162015
name: Optional[str] = None,
@@ -1919,14 +2018,30 @@ def _retrieve_across_folders_spec(
19192018
"$expand": "dataSource",
19202019
}
19212020
if name:
1922-
params["$filter"] = f"Name eq '{name}'"
2021+
params["$filter"] = self._odata_name_filter(name)
19232022

19242023
return RequestSpec(
19252024
method="GET",
19262025
endpoint=Endpoint("/ecs_/v2/indexes/allacrossfolders"),
19272026
params=params,
19282027
)
19292028

2029+
def _retrieve_system_indexes_spec(
2030+
self,
2031+
name: Optional[str] = None,
2032+
) -> RequestSpec:
2033+
params: Dict[str, str] = {
2034+
"$expand": "dataSource",
2035+
}
2036+
if name:
2037+
params["$filter"] = self._odata_name_filter(name)
2038+
2039+
return RequestSpec(
2040+
method="GET",
2041+
endpoint=Endpoint("/ecs_/v2/indexes/allsystemindexes"),
2042+
params=params,
2043+
)
2044+
19302045
def _list_spec(
19312046
self,
19322047
folder_key: Optional[str] = None,
@@ -1954,7 +2069,7 @@ def _retrieve_spec(
19542069
method="GET",
19552070
endpoint=Endpoint("/ecs_/v2/indexes"),
19562071
params={
1957-
"$filter": f"Name eq '{name}'",
2072+
"$filter": self._odata_name_filter(name),
19582073
"$expand": "dataSource",
19592074
},
19602075
headers={

packages/uipath-platform/src/uipath/platform/errors/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- FolderNotFoundException: Raised when a folder cannot be found
99
- UnsupportedDataSourceException: Raised when an operation is attempted on an unsupported data source type
1010
- IngestionInProgressException: Raised when a search is attempted on an index during ingestion
11+
- ContextGroundingIndexNotFoundError: Raised when a context grounding index cannot be resolved by name
1112
- BatchTransformFailedException: Raised when a batch transform has failed
1213
- BatchTransformNotCompleteException: Raised when attempting to get results from an incomplete batch transform
1314
- OperationNotCompleteException: Raised when attempting to get results from an incomplete operation
@@ -18,6 +19,9 @@
1819
from ._base_url_missing_error import BaseUrlMissingError
1920
from ._batch_transform_failed_exception import BatchTransformFailedException
2021
from ._batch_transform_not_complete_exception import BatchTransformNotCompleteException
22+
from ._context_grounding_index_not_found_exception import (
23+
ContextGroundingIndexNotFoundError,
24+
)
2125
from ._enriched_exception import EnrichedException, ExtractedErrorInfo
2226
from ._folder_not_found_exception import FolderNotFoundException
2327
from ._ingestion_in_progress_exception import IngestionInProgressException
@@ -30,6 +34,7 @@
3034
"BaseUrlMissingError",
3135
"BatchTransformFailedException",
3236
"BatchTransformNotCompleteException",
37+
"ContextGroundingIndexNotFoundError",
3338
"EnrichedException",
3439
"ExtractedErrorInfo",
3540
"FolderNotFoundException",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import Optional
2+
3+
4+
class ContextGroundingIndexNotFoundError(Exception):
5+
"""Raised when a context grounding index cannot be resolved by name."""
6+
7+
def __init__(self, index_name: Optional[str] = None):
8+
self.index_name = index_name
9+
if index_name:
10+
self.message = f"ContextGroundingIndex '{index_name}' not found"
11+
else:
12+
self.message = "ContextGroundingIndex not found"
13+
super().__init__(self.message)

0 commit comments

Comments
 (0)