From 98c54a54d29dc334a17d75411323e127be6767f7 Mon Sep 17 00:00:00 2001 From: Caleb Martin Date: Tue, 8 Sep 2026 16:09:10 -0700 Subject: [PATCH] feat(context): forward the searchDuringIngestion opt-in to the retriever An agent pointed at a context index that was still ingesting failed outright instead of searching the documents indexed so far. The SDK now exposes search_during_ingestion on the unified search methods, and AgentContextSettings carries the matching searchDuringIngestion flag that the agent designer writes into agent.json. Thread that setting through: ContextGroundingRetriever gains a search_during_ingestion field forwarded to unified_search and unified_search_async, and the semantic context tool reads it off the resource settings. The default is unchanged (False), so an ingesting index still fails fast. The failure now names the toggle, since that is the actionable fix. Requires uipath-platform>=0.2.27 and uipath>=2.14.13, which is where the parameter and the settings field land. Co-Authored-By: Claude Opus 5 --- pyproject.toml | 6 +- .../agent/tools/context_tool.py | 7 +- .../retrievers/context_grounding_retriever.py | 3 + tests/agent/tools/test_context_tool.py | 73 +++++++++++++++++++ 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8837991fc..bc76c8f34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [project] name = "uipath-langchain" -version = "0.17.5" +version = "0.17.6" description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ - "uipath>=2.14.6, <2.15.0", + "uipath>=2.14.14, <2.15.0", "uipath-core>=0.5.29, <0.6.0", - "uipath-platform>=0.2.24, <0.3.0", + "uipath-platform>=0.2.29, <0.3.0", "uipath-runtime>=0.13.0, <0.14.0", "uipath-llm-client>=1.18.0, <1.19.0", "langgraph>=1.1.8, <2.0.0", diff --git a/src/uipath_langchain/agent/tools/context_tool.py b/src/uipath_langchain/agent/tools/context_tool.py index 7c1c1a508..f8cf638ac 100644 --- a/src/uipath_langchain/agent/tools/context_tool.py +++ b/src/uipath_langchain/agent/tools/context_tool.py @@ -219,6 +219,7 @@ def handle_semantic_search( static_folder_path_prefix = _resolve_static_folder_path_prefix(resource) result_count = resource.settings.result_count threshold = resource.settings.threshold + search_during_ingestion = resource.settings.search_during_ingestion static = is_static_query(resource) prompt = resource.settings.query.value if static else None @@ -287,6 +288,7 @@ async def context_tool_fn( scope_folder=resolved_folder_path_prefix, scope_extension=file_extension, include_system_indexes=debug_run, + search_during_ingestion=search_during_ingestion, ) actual_query = prompt or query @@ -304,7 +306,10 @@ async def context_tool_fn( raise AgentRuntimeError( code=AgentRuntimeErrorCode.CONTEXT_GROUNDING_INDEX_INGESTION_IN_PROGRESS, title=f"Context grounding index '{resource.index_name}' is still ingesting", - detail=str(e), + detail=( + f"{e}. Enable 'search during ingestion' on this context resource " + "to search the documents indexed so far instead of failing." + ), category=UiPathErrorCategory.USER, ) from e except EnrichedException as e: diff --git a/src/uipath_langchain/retrievers/context_grounding_retriever.py b/src/uipath_langchain/retrievers/context_grounding_retriever.py index bdc9d8d8b..3fcc915c6 100644 --- a/src/uipath_langchain/retrievers/context_grounding_retriever.py +++ b/src/uipath_langchain/retrievers/context_grounding_retriever.py @@ -22,6 +22,7 @@ class ContextGroundingRetriever(BaseRetriever): scope_folder: str | None = None scope_extension: str | None = None include_system_indexes: bool = False + search_during_ingestion: bool = False def _build_scope(self) -> UnifiedSearchScope | None: if self.scope_folder or self.scope_extension: @@ -54,6 +55,7 @@ def _get_relevant_documents( folder_path=self.folder_path, folder_key=self.folder_key, include_system_indexes=self.include_system_indexes, + search_during_ingestion=self.search_during_ingestion, ) values = result.semantic_results.values if result.semantic_results else [] @@ -100,6 +102,7 @@ async def _aget_relevant_documents( folder_path=self.folder_path, folder_key=self.folder_key, include_system_indexes=self.include_system_indexes, + search_during_ingestion=self.search_during_ingestion, ) values = result.semantic_results.values if result.semantic_results else [] diff --git a/tests/agent/tools/test_context_tool.py b/tests/agent/tools/test_context_tool.py index f41ff8b4c..93ff9228b 100644 --- a/tests/agent/tools/test_context_tool.py +++ b/tests/agent/tools/test_context_tool.py @@ -53,6 +53,7 @@ def _make_context_resource( retrieval_mode=AgentContextRetrievalMode.SEMANTIC, folder_path_prefix=None, context_type="index", + search_during_ingestion=False, **kwargs, ): """Helper to create an AgentContextResourceConfig.""" @@ -73,6 +74,7 @@ def _make_context_resource( ), citation_mode=citation_mode_value, folder_path_prefix=folder_path_prefix, + search_during_ingestion=search_during_ingestion, ), is_enabled=True, **kwargs, @@ -1281,3 +1283,74 @@ async def test_resolves_system_index_and_runs_unified_search( assert any("/v2/indexes/allacrossfolders" in u for u in urls) assert any("/v2/indexes/allsystemindexes" in u for u in urls) assert any("/v1.2/search/sys-1" in u for u in urls) + + +class TestSearchDuringIngestionSetting: + """searchDuringIngestion on the context resource reaches the retriever.""" + + @staticmethod + async def _retriever_kwargs_for(resource): + """Invoke the semantic tool and return the ContextGroundingRetriever kwargs.""" + with patch( + "uipath_langchain.agent.tools.context_tool.ContextGroundingRetriever" + ) as mock_retriever_class: + mock_retriever = AsyncMock() + mock_retriever.ainvoke.return_value = [] + mock_retriever_class.return_value = mock_retriever + + tool = handle_semantic_search("semantic_tool", resource) + assert tool.coroutine is not None + await tool.coroutine(query="test query") + + mock_retriever_class.assert_called_once() + return mock_retriever_class.call_args.kwargs + + @pytest.mark.asyncio + async def test_defaults_to_not_searching_during_ingestion(self): + resource = _make_context_resource( + name="semantic_tool", + retrieval_mode=AgentContextRetrievalMode.SEMANTIC, + query_variant="dynamic", + ) + + kwargs = await self._retriever_kwargs_for(resource) + + assert kwargs["search_during_ingestion"] is False + + @pytest.mark.asyncio + async def test_agent_json_predating_the_toggle_keeps_failing_fast(self): + """An agent.json with no searchDuringIngestion key must not opt in.""" + resource = AgentContextResourceConfig.model_validate( + { + "$resourceType": "context", + "name": "semantic_tool", + "description": "docs index", + "contextType": "index", + "indexName": "test-index", + "folderPath": "/test/folder", + "isEnabled": True, + "settings": { + "threshold": 0, + "resultCount": 3, + "retrievalMode": "Semantic", + "query": {"description": "The query.", "variant": "Dynamic"}, + }, + } + ) + + kwargs = await self._retriever_kwargs_for(resource) + + assert kwargs["search_during_ingestion"] is False + + @pytest.mark.asyncio + async def test_opt_in_is_forwarded_to_the_retriever(self): + resource = _make_context_resource( + name="semantic_tool", + retrieval_mode=AgentContextRetrievalMode.SEMANTIC, + query_variant="dynamic", + search_during_ingestion=True, + ) + + kwargs = await self._retriever_kwargs_for(resource) + + assert kwargs["search_during_ingestion"] is True