Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/uipath-platform/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-platform"
version = "0.2.28"
version = "0.2.29"
description = "HTTP client library for programmatic access to UiPath Platform"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,7 @@ def unified_search(
folder_key: Optional[str] = None,
folder_path: Optional[str] = None,
include_system_indexes: bool = False,
search_during_ingestion: bool = False,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 3c011bc.

SDK_REFERENCE.md and CLI_REFERENCE.md now carry the new parameter:

  • unified_search / unified_search_async signatures gain search_during_ingestion: bool=False
  • the CLI reference lists --search-during-ingestion

One deliberate deviation from "regenerate": running scripts/update_agents_md.py in full produces +232/-24 lines, because the committed copy has drifted since 2026-06-16 and is missing unrelated surface from other PRs (38 entities methods, 7 governance, plus tasks/processes/mcp). Rather than attribute that churn to a context-grounding PR, I hand-applied only the three lines belonging to this change — then re-ran the generator and diffed to confirm my lines are byte-identical to what it emits, so the next just build won't move them.

The remaining drift is worth a separate regeneration PR.

) -> UnifiedQueryResult:
"""Perform a unified search on a context grounding index.

Expand All @@ -1687,16 +1688,26 @@ def unified_search(
include_system_indexes (bool): If True, fall back to tenant-wide
system indexes when the index is not found in folder or
across-folders listings. Defaults to False.
search_during_ingestion (bool): If True, query the index even while an
ingestion is in progress, returning results from the documents
indexed so far. Defaults to False, which raises
IngestionInProgressException instead.

Returns:
UnifiedQueryResult: The unified search result containing semantic and/or tabular results.

Raises:
IngestionInProgressException: If the index is still ingesting and
search_during_ingestion is False.
"""
index = self.retrieve(
name,
folder_key=folder_key,
folder_path=folder_path,
include_system_indexes=include_system_indexes,
)
if not search_during_ingestion and index and index.in_progress_ingestion():
raise IngestionInProgressException(index_name=name)

folder_key = folder_key or index.folder_key

Expand Down Expand Up @@ -1733,6 +1744,7 @@ async def unified_search_async(
folder_key: Optional[str] = None,
folder_path: Optional[str] = None,
include_system_indexes: bool = False,
search_during_ingestion: bool = False,
) -> UnifiedQueryResult:
"""Asynchronously perform a unified search on a context grounding index.

Expand All @@ -1751,17 +1763,25 @@ async def unified_search_async(
include_system_indexes (bool): If True, fall back to tenant-wide
system indexes when the index is not found in folder or
across-folders listings. Defaults to False.
search_during_ingestion (bool): If True, query the index even while an
ingestion is in progress, returning results from the documents
indexed so far. Defaults to False, which raises
IngestionInProgressException instead.

Returns:
UnifiedQueryResult: The unified search result containing semantic and/or tabular results.

Raises:
IngestionInProgressException: If the index is still ingesting and
search_during_ingestion is False.
"""
index = await self.retrieve_async(
name,
folder_key=folder_key,
folder_path=folder_path,
include_system_indexes=include_system_indexes,
)
if index and index.in_progress_ingestion():
if not search_during_ingestion and index and index.in_progress_ingestion():
raise IngestionInProgressException(index_name=name)

folder_key = folder_key or index.folder_key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
from uipath.platform.context_grounding._context_grounding_service import (
ContextGroundingService,
)
from uipath.platform.errors import ContextGroundingIndexNotFoundError
from uipath.platform.errors import (
ContextGroundingIndexNotFoundError,
IngestionInProgressException,
)
from uipath.platform.orchestrator._buckets_service import BucketsService
from uipath.platform.orchestrator._folder_service import FolderService

Expand Down Expand Up @@ -3839,6 +3842,123 @@ async def test_unified_search_async(
assert response.semantic_results.metadata is not None
assert len(response.semantic_results.values) == 1

def _mock_index_lookup(
self,
httpx_mock: HTTPXMock,
base_url: str,
org: str,
tenant: str,
ingestion_status: str,
) -> None:
"""Mock the folder + index lookup that precedes a unified search."""
for _ in range(2):
httpx_mock.add_response(
url=f"{base_url}{org}{tenant}/orchestrator_/api/FoldersNavigation/GetFoldersForCurrentUser?searchText=test-folder-path&skip=0&take=20",
status_code=200,
json={
"PageItems": [
{
"Key": "test-folder-key",
"FullyQualifiedName": "test-folder-path",
}
]
},
)
httpx_mock.add_response(
url=f"{base_url}{org}{tenant}/ecs_/v2/indexes?$filter=Name eq 'test-index'&$expand=dataSource",
status_code=200,
json={
"value": [
{
"id": "test-index-id",
"name": "test-index",
"lastIngestionStatus": ingestion_status,
}
]
},
)

@pytest.mark.anyio
@pytest.mark.httpx_mock(assert_all_responses_were_requested=False)
@pytest.mark.parametrize("ingestion_status", ["Queued", "InProgress"])
async def test_unified_search_async_blocks_during_ingestion_by_default(
self,
httpx_mock: HTTPXMock,
service: ContextGroundingService,
base_url: str,
org: str,
tenant: str,
ingestion_status: str,
) -> None:
self._mock_index_lookup(httpx_mock, base_url, org, tenant, ingestion_status)

with pytest.raises(IngestionInProgressException):
await service.unified_search_async(name="test-index", query="test query")

@pytest.mark.httpx_mock(assert_all_responses_were_requested=False)
@pytest.mark.parametrize("ingestion_status", ["Queued", "InProgress"])
def test_unified_search_blocks_during_ingestion_by_default(
self,
httpx_mock: HTTPXMock,
service: ContextGroundingService,
base_url: str,
org: str,
tenant: str,
ingestion_status: str,
) -> None:
self._mock_index_lookup(httpx_mock, base_url, org, tenant, ingestion_status)

with pytest.raises(IngestionInProgressException):
service.unified_search(name="test-index", query="test query")

@pytest.mark.anyio
@pytest.mark.parametrize("ingestion_status", ["Queued", "InProgress"])
async def test_unified_search_async_opt_in_searches_during_ingestion(
self,
httpx_mock: HTTPXMock,
service: ContextGroundingService,
base_url: str,
org: str,
tenant: str,
ingestion_status: str,
) -> None:
self._mock_index_lookup(httpx_mock, base_url, org, tenant, ingestion_status)
httpx_mock.add_response(
url=f"{base_url}{org}{tenant}/ecs_/v1.2/search/test-index-id",
status_code=200,
json={
"semanticResults": {
"metadata": {
"operation_id": "test-op",
"strategy": "test-strategy",
},
"values": [
{
"id": "result-1",
"source": "test-source",
"page_number": 1,
"content": "Partially ingested content",
"score": 0.95,
}
],
},
"explanation": "test explanation",
},
)

response = await service.unified_search_async(
name="test-index",
query="test query",
search_during_ingestion=True,
)

assert isinstance(response, UnifiedQueryResult)
assert response.semantic_results is not None
assert len(response.semantic_results.values) == 1
assert (
response.semantic_results.values[0].content == "Partially ingested content"
)

def test_unified_search_with_scope(
self,
httpx_mock: HTTPXMock,
Expand Down
2 changes: 1 addition & 1 deletion packages/uipath-platform/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/uipath/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "uipath"
version = "2.14.13"
version = "2.14.14"
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"
dependencies = [
"uipath-core>=0.5.30, <0.6.0",
"uipath-runtime>=0.13.1, <0.14.0",
"uipath-platform>=0.2.27, <0.3.0",
"uipath-platform>=0.2.29, <0.3.0",
"click>=8.3.1",
"httpx>=0.28.1",
"pyjwt>=2.10.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,12 @@ def ingest_index(
default="Semantic",
help="Search mode (default: Semantic)",
)
@click.option(
"--search-during-ingestion",
is_flag=True,
default=False,
help="Search the documents indexed so far even while ingestion is in progress",
)
@common_service_options
@service_command
def search_index(
Expand All @@ -473,6 +479,7 @@ def search_index(
limit: int,
threshold: float,
search_mode: str,
search_during_ingestion: bool,
folder_path: Optional[str],
folder_key: Optional[str],
format: Optional[str],
Expand All @@ -494,6 +501,7 @@ def search_index(
number_of_results=limit,
threshold=threshold,
search_mode=SearchMode(search_mode),
search_during_ingestion=search_during_ingestion,
folder_path=folder_path,
folder_key=folder_key,
)
Expand Down
1 change: 1 addition & 0 deletions packages/uipath/src/uipath/_resources/CLI_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,7 @@ Options:
- `--limit`: Maximum number of results (default: 10) (default: `10`)
- `--threshold`: Minimum similarity threshold (default: 0.0) (default: `0.0`)
- `--search-mode`: Search mode (default: Semantic) (default: `Semantic`)
- `--search-during-ingestion`: Search the documents indexed so far even while ingestion is in progress
- `--folder-path`: Folder path (e.g., "Shared"). Can also be set via UIPATH_FOLDER_PATH environment variable. (default: `Sentinel.UNSET`)
- `--folder-key`: Folder key (UUID) (default: `Sentinel.UNSET`)
- `--format`: Output format (overrides global) (default: `Sentinel.UNSET`)
Expand Down
4 changes: 2 additions & 2 deletions packages/uipath/src/uipath/_resources/SDK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,10 +405,10 @@ sdk.context_grounding.start_deep_rag_ephemeral(name: str, prompt: Annotated[str,
sdk.context_grounding.start_deep_rag_ephemeral_async(name: str, prompt: Annotated[str, FieldInfo(annotation=NoneType, required=True, metadata=[MaxLen(max_length=250000)])], glob_pattern: Annotated[str, FieldInfo(annotation=NoneType, required=False, default='*', metadata=[MaxLen(max_length=512)])]="**", citation_mode: <enum 'CitationMode="CitationMode.SKIP", index_id: Optional[Annotated[str, FieldInfo(annotation=NoneType, required=True, metadata=[MaxLen(max_length=512)])]]=None) -> uipath.platform.context_grounding.context_grounding.DeepRagCreationResponse

# Perform a unified search on a context grounding index.
sdk.context_grounding.unified_search(name: str, query: str, search_mode: <enum 'SearchMode="SearchMode.SEMANTIC", number_of_results: int=10, threshold: float=0.0, scope: Optional[uipath.platform.context_grounding.context_grounding.UnifiedSearchScope]=None, folder_key: Optional[str]=None, folder_path: Optional[str]=None, include_system_indexes: bool=False) -> uipath.platform.context_grounding.context_grounding.UnifiedQueryResult
sdk.context_grounding.unified_search(name: str, query: str, search_mode: <enum 'SearchMode="SearchMode.SEMANTIC", number_of_results: int=10, threshold: float=0.0, scope: Optional[uipath.platform.context_grounding.context_grounding.UnifiedSearchScope]=None, folder_key: Optional[str]=None, folder_path: Optional[str]=None, include_system_indexes: bool=False, search_during_ingestion: bool=False) -> uipath.platform.context_grounding.context_grounding.UnifiedQueryResult

# Asynchronously perform a unified search on a context grounding index.
sdk.context_grounding.unified_search_async(name: str, query: str, search_mode: <enum 'SearchMode="SearchMode.SEMANTIC", number_of_results: int=10, threshold: float=0.0, scope: Optional[uipath.platform.context_grounding.context_grounding.UnifiedSearchScope]=None, folder_key: Optional[str]=None, folder_path: Optional[str]=None, include_system_indexes: bool=False) -> uipath.platform.context_grounding.context_grounding.UnifiedQueryResult
sdk.context_grounding.unified_search_async(name: str, query: str, search_mode: <enum 'SearchMode="SearchMode.SEMANTIC", number_of_results: int=10, threshold: float=0.0, scope: Optional[uipath.platform.context_grounding.context_grounding.UnifiedSearchScope]=None, folder_key: Optional[str]=None, folder_path: Optional[str]=None, include_system_indexes: bool=False, search_during_ingestion: bool=False) -> uipath.platform.context_grounding.context_grounding.UnifiedQueryResult

```

Expand Down
9 changes: 9 additions & 0 deletions packages/uipath/src/uipath/agent/models/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,15 @@ class AgentContextSettings(BaseCfg):
output_columns: Optional[List[AgentContextOutputColumn]] = Field(
None, alias="outputColumns"
)
search_during_ingestion: bool = Field(
default=False,
alias="searchDuringIngestion",
description=(
"Allow the agent to query this index while an ingestion is still in "
"progress, returning results from the documents indexed so far. When "
"False, a search against an ingesting index fails instead."
),
)


class AgentContextResourceConfig(BaseAgentResourceConfig):
Expand Down
47 changes: 47 additions & 0 deletions packages/uipath/tests/agent/models/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
AgentClientSideToolResourceConfig,
AgentContextResourceConfig,
AgentContextRetrievalMode,
AgentContextSettings,
AgentContextType,
AgentCustomGuardrail,
AgentDefinition,
Expand Down Expand Up @@ -4778,3 +4779,49 @@ def test_native_bag_survives_verbatim_by_alias(self):
self._agent_settings(modelSettings=native)
)
assert settings.model_dump(by_alias=True)["modelSettings"] == native


class TestSearchDuringIngestion:
"""settings.searchDuringIngestion opts a context resource into mid-ingestion search."""

def _context_settings(self, **extra: Any) -> dict[str, Any]:
return {
"threshold": 0,
"resultCount": 3,
"retrievalMode": "Semantic",
"query": {"description": "The query.", "variant": "Dynamic"},
**extra,
}

def test_defaults_to_false_when_absent(self):
settings = AgentContextSettings.model_validate(self._context_settings())
assert settings.search_during_ingestion is False

@pytest.mark.parametrize(
"key", ["searchDuringIngestion", "search_during_ingestion"]
)
def test_opt_in_parsed_from_alias_and_field_name(self, key: str):
settings = AgentContextSettings.model_validate(
self._context_settings(**{key: True})
)
assert settings.search_during_ingestion is True

def test_round_trips_by_alias(self):
settings = AgentContextSettings.model_validate(
self._context_settings(searchDuringIngestion=True)
)
assert settings.model_dump(by_alias=True)["searchDuringIngestion"] is True

def test_reaches_the_resource_config(self):
resource = AgentContextResourceConfig.model_validate(
{
"$resourceType": "context",
"folderPath": "TestFolder",
"indexName": "Test Index",
"name": "Test Context",
"description": "Context that may be searched mid-ingestion",
"settings": self._context_settings(searchDuringIngestion=True),
}
)
assert resource.settings is not None
assert resource.settings.search_during_ingestion is True
4 changes: 2 additions & 2 deletions packages/uipath/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading