Skip to content

Commit c95793b

Browse files
authored
feat: implement UnifiedSearch (search v1.2) method [ECS-1656] (#1427)
1 parent 0673c42 commit c95793b

4 files changed

Lines changed: 555 additions & 1 deletion

File tree

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99
Citation,
1010
CitationMode,
1111
ContextGroundingQueryResponse,
12+
ContextGroundingSearchResultItem,
1213
DeepRagContent,
1314
DeepRagCreationResponse,
1415
DeepRagResponse,
1516
DeepRagStatus,
1617
EphemeralIndexUsage,
1718
IndexStatus,
19+
SearchMode,
20+
SemanticSearchOptions,
21+
SemanticSearchResult,
22+
UnifiedQueryResult,
23+
UnifiedSearchScope,
1824
)
1925
from .context_grounding_index import ContextGroundingIndex
2026
from .context_grounding_payloads import (
@@ -49,6 +55,7 @@
4955
"ConnectionSourceConfig",
5056
"ContextGroundingIndex",
5157
"ContextGroundingQueryResponse",
58+
"ContextGroundingSearchResultItem",
5259
"ContextGroundingService",
5360
"CreateIndexPayload",
5461
"DeepRagCreationResponse",
@@ -64,6 +71,11 @@
6471
"Indexer",
6572
"OneDriveDataSource",
6673
"OneDriveSourceConfig",
74+
"SearchMode",
75+
"SemanticSearchOptions",
76+
"SemanticSearchResult",
6777
"SourceConfig",
78+
"UnifiedQueryResult",
79+
"UnifiedSearchScope",
6880
"Citation",
6981
]

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

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import httpx
55
from pydantic import Field, TypeAdapter
6+
from typing_extensions import deprecated
67
from uipath.core.tracing import traced
78

89
from ..common._base_service import BaseService
@@ -33,6 +34,9 @@
3334
DeepRagCreationResponse,
3435
DeepRagResponse,
3536
EphemeralIndexUsage,
37+
SearchMode,
38+
UnifiedQueryResult,
39+
UnifiedSearchScope,
3640
)
3741
from .context_grounding_index import ContextGroundingIndex
3842
from .context_grounding_payloads import (
@@ -1274,16 +1278,20 @@ async def start_deep_rag_ephemeral_async(
12741278

12751279
@resource_override(resource_type="index")
12761280
@traced(name="contextgrounding_search", run_type="uipath")
1281+
@deprecated("Use unified_search instead.")
12771282
def search(
12781283
self,
12791284
name: str,
12801285
query: str,
12811286
number_of_results: int = 10,
1287+
threshold: Optional[float] = None,
12821288
folder_key: Optional[str] = None,
12831289
folder_path: Optional[str] = None,
12841290
) -> List[ContextGroundingQueryResponse]:
12851291
"""Search for contextual information within a specific index.
12861292
1293+
This method is deprecated. Use unified_search instead.
1294+
12871295
This method performs a semantic search against the specified context index,
12881296
helping to find relevant information that can be used in automation processes.
12891297
The search is powered by AI and understands natural language queries.
@@ -1293,6 +1301,7 @@ def search(
12931301
query (str): The search query in natural language.
12941302
number_of_results (int, optional): Maximum number of results to return.
12951303
Defaults to 10.
1304+
threshold (float): Minimum similarity threshold. Defaults to 0.0.
12961305
12971306
Returns:
12981307
List[ContextGroundingQueryResponse]: A list of search results, each containing
@@ -1308,6 +1317,7 @@ def search(
13081317
name,
13091318
query,
13101319
number_of_results,
1320+
threshold=threshold if threshold is not None else 0.0,
13111321
folder_key=folder_key,
13121322
folder_path=folder_path,
13131323
)
@@ -1325,16 +1335,20 @@ def search(
13251335

13261336
@resource_override(resource_type="index")
13271337
@traced(name="contextgrounding_search", run_type="uipath")
1338+
@deprecated("Use unified_search_async instead.")
13281339
async def search_async(
13291340
self,
13301341
name: str,
13311342
query: str,
13321343
number_of_results: int = 10,
1344+
threshold: Optional[float] = None,
13331345
folder_key: Optional[str] = None,
13341346
folder_path: Optional[str] = None,
13351347
) -> List[ContextGroundingQueryResponse]:
13361348
"""Search asynchronously for contextual information within a specific index.
13371349
1350+
This method is deprecated. Use unified_search_async instead.
1351+
13381352
This method performs a semantic search against the specified context index,
13391353
helping to find relevant information that can be used in automation processes.
13401354
The search is powered by AI and understands natural language queries.
@@ -1344,6 +1358,7 @@ async def search_async(
13441358
query (str): The search query in natural language.
13451359
number_of_results (int, optional): Maximum number of results to return.
13461360
Defaults to 10.
1361+
threshold (float): Minimum similarity threshold. Defaults to 0.0.
13471362
13481363
Returns:
13491364
List[ContextGroundingQueryResponse]: A list of search results, each containing
@@ -1363,6 +1378,7 @@ async def search_async(
13631378
name,
13641379
query,
13651380
number_of_results,
1381+
threshold=threshold if threshold is not None else 0.0,
13661382
folder_key=folder_key,
13671383
folder_path=folder_path,
13681384
)
@@ -1378,6 +1394,120 @@ async def search_async(
13781394
response.json()
13791395
)
13801396

1397+
@resource_override(resource_type="index")
1398+
@traced(name="contextgrounding_unified_search", run_type="uipath")
1399+
def unified_search(
1400+
self,
1401+
name: str,
1402+
query: str,
1403+
search_mode: SearchMode = SearchMode.AUTO,
1404+
number_of_results: int = 10,
1405+
threshold: float = 0.0,
1406+
scope: Optional[UnifiedSearchScope] = None,
1407+
folder_key: Optional[str] = None,
1408+
folder_path: Optional[str] = None,
1409+
) -> UnifiedQueryResult:
1410+
"""Perform a unified search on a context grounding index.
1411+
1412+
This method performs a unified search (v1.2) against the specified context index,
1413+
supporting both semantic and tabular search modes.
1414+
1415+
Args:
1416+
name (str): The name of the context index to search in.
1417+
query (str): The search query in natural language.
1418+
search_mode (SearchMode): The search mode to use. Defaults to AUTO.
1419+
number_of_results (int): Maximum number of results to return. Defaults to 10.
1420+
threshold (float): Minimum similarity threshold. Defaults to 0.0.
1421+
scope (Optional[UnifiedSearchScope]): Optional search scope (folder, extension).
1422+
folder_key (Optional[str]): The key of the folder where the index resides.
1423+
folder_path (Optional[str]): The path of the folder where the index resides.
1424+
1425+
Returns:
1426+
UnifiedQueryResult: The unified search result containing semantic and/or tabular results.
1427+
"""
1428+
index = self.retrieve(name, folder_key=folder_key, folder_path=folder_path)
1429+
1430+
folder_key = folder_key or index.folder_key
1431+
1432+
spec = self._unified_search_spec(
1433+
index_id=index.id,
1434+
query=query,
1435+
search_mode=search_mode,
1436+
number_of_results=number_of_results,
1437+
threshold=threshold,
1438+
scope=scope,
1439+
folder_key=folder_key,
1440+
folder_path=folder_path,
1441+
)
1442+
1443+
response = self.request(
1444+
spec.method,
1445+
spec.endpoint,
1446+
json=spec.json,
1447+
headers=spec.headers,
1448+
)
1449+
1450+
return UnifiedQueryResult.model_validate(response.json())
1451+
1452+
@resource_override(resource_type="index")
1453+
@traced(name="contextgrounding_unified_search", run_type="uipath")
1454+
async def unified_search_async(
1455+
self,
1456+
name: str,
1457+
query: str,
1458+
search_mode: SearchMode = SearchMode.AUTO,
1459+
number_of_results: int = 10,
1460+
threshold: float = 0.0,
1461+
scope: Optional[UnifiedSearchScope] = None,
1462+
folder_key: Optional[str] = None,
1463+
folder_path: Optional[str] = None,
1464+
) -> UnifiedQueryResult:
1465+
"""Asynchronously perform a unified search on a context grounding index.
1466+
1467+
This method performs a unified search (v1.2) against the specified context index,
1468+
supporting both semantic and tabular search modes.
1469+
1470+
Args:
1471+
name (str): The name of the context index to search in.
1472+
query (str): The search query in natural language.
1473+
search_mode (SearchMode): The search mode to use. Defaults to AUTO.
1474+
number_of_results (int): Maximum number of results to return. Defaults to 10.
1475+
threshold (float): Minimum similarity threshold. Defaults to 0.0.
1476+
scope (Optional[UnifiedSearchScope]): Optional search scope (folder, extension).
1477+
folder_key (Optional[str]): The key of the folder where the index resides.
1478+
folder_path (Optional[str]): The path of the folder where the index resides.
1479+
1480+
Returns:
1481+
UnifiedQueryResult: The unified search result containing semantic and/or tabular results.
1482+
"""
1483+
index = await self.retrieve_async(
1484+
name, folder_key=folder_key, folder_path=folder_path
1485+
)
1486+
if index and index.in_progress_ingestion():
1487+
raise IngestionInProgressException(index_name=name)
1488+
1489+
folder_key = folder_key or index.folder_key
1490+
1491+
spec = self._unified_search_spec(
1492+
index_id=index.id,
1493+
query=query,
1494+
search_mode=search_mode,
1495+
number_of_results=number_of_results,
1496+
threshold=threshold,
1497+
scope=scope,
1498+
folder_key=folder_key,
1499+
folder_path=folder_path,
1500+
)
1501+
1502+
response = await self.request_async(
1503+
spec.method,
1504+
spec.endpoint,
1505+
json=spec.json,
1506+
headers=spec.headers,
1507+
)
1508+
1509+
return UnifiedQueryResult.model_validate(response.json())
1510+
13811511
@traced(name="contextgrounding_ingest_data", run_type="uipath")
13821512
def ingest_data(
13831513
self,
@@ -1757,6 +1887,7 @@ def _search_spec(
17571887
name: str,
17581888
query: str,
17591889
number_of_results: int = 10,
1890+
threshold: float = 0.0,
17601891
folder_key: Optional[str] = None,
17611892
folder_path: Optional[str] = None,
17621893
) -> RequestSpec:
@@ -1766,14 +1897,54 @@ def _search_spec(
17661897
method="POST",
17671898
endpoint=Endpoint("/ecs_/v1/search"),
17681899
json={
1769-
"query": {"query": query, "numberOfResults": number_of_results},
1900+
"query": {
1901+
"query": query,
1902+
"numberOfResults": number_of_results,
1903+
"threshold": threshold,
1904+
},
17701905
"schema": {"name": name},
17711906
},
17721907
headers={
17731908
**header_folder(folder_key, None),
17741909
},
17751910
)
17761911

1912+
def _unified_search_spec(
1913+
self,
1914+
index_id: str,
1915+
query: str,
1916+
search_mode: SearchMode = SearchMode.AUTO,
1917+
number_of_results: int = 10,
1918+
threshold: float = 0.0,
1919+
scope: Optional[UnifiedSearchScope] = None,
1920+
folder_key: Optional[str] = None,
1921+
folder_path: Optional[str] = None,
1922+
) -> RequestSpec:
1923+
folder_key = self._resolve_folder_key(folder_key, folder_path)
1924+
1925+
json_body: Dict[str, Any] = {
1926+
"searchMode": search_mode.value
1927+
if isinstance(search_mode, SearchMode)
1928+
else search_mode,
1929+
"query": query,
1930+
"semanticSearchOptions": {
1931+
"numberOfResults": number_of_results,
1932+
"threshold": threshold,
1933+
},
1934+
}
1935+
1936+
if scope is not None:
1937+
json_body["scope"] = scope.model_dump(by_alias=True, exclude_none=True)
1938+
1939+
return RequestSpec(
1940+
method="POST",
1941+
endpoint=Endpoint(f"/ecs_/v1.2/search/{index_id}"),
1942+
json=json_body,
1943+
headers={
1944+
**header_folder(folder_key, None),
1945+
},
1946+
)
1947+
17771948
def _deep_rag_creation_spec(
17781949
self,
17791950
index_id: str,

0 commit comments

Comments
 (0)