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