From 4cb0a4948cca7626fe5ea4b3db443791d92ed555 Mon Sep 17 00:00:00 2001 From: Caleb Martin Date: Thu, 22 Jan 2026 01:00:27 -0800 Subject: [PATCH 1/2] feat: add deeprag tool --- pyproject.toml | 4 +- .../internal_tools/batch_transform_tool.py | 159 ++++++ .../tools/internal_tools/deeprag_tool.py | 142 +++++ .../internal_tools/internal_tool_factory.py | 4 + .../tools/internal_tools/schema_utils.py | 33 ++ .../internal_tools/test_analyze_files_tool.py | 4 +- .../test_batch_transform_tool.py | 484 ++++++++++++++++++ .../tools/internal_tools/test_deeprag_tool.py | 355 +++++++++++++ tests/agent/tools/test_tool_factory.py | 4 +- uv.lock | 18 +- 10 files changed, 1192 insertions(+), 15 deletions(-) create mode 100644 src/uipath_langchain/agent/tools/internal_tools/batch_transform_tool.py create mode 100644 src/uipath_langchain/agent/tools/internal_tools/deeprag_tool.py create mode 100644 src/uipath_langchain/agent/tools/internal_tools/schema_utils.py create mode 100644 tests/agent/tools/internal_tools/test_batch_transform_tool.py create mode 100644 tests/agent/tools/internal_tools/test_deeprag_tool.py diff --git a/pyproject.toml b/pyproject.toml index 7d50148a..60751af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "uipath-langchain" -version = "0.5.21" +version = "0.5.22" 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.6.22,<2.7.0", + "uipath>=2.7.0,<2.8.0", "uipath-runtime>=0.6.0, <0.7.0", "langgraph>=1.0.0, <2.0.0", "langchain-core>=1.2.5, <2.0.0", diff --git a/src/uipath_langchain/agent/tools/internal_tools/batch_transform_tool.py b/src/uipath_langchain/agent/tools/internal_tools/batch_transform_tool.py new file mode 100644 index 00000000..5654b1be --- /dev/null +++ b/src/uipath_langchain/agent/tools/internal_tools/batch_transform_tool.py @@ -0,0 +1,159 @@ +"""Batch Transform tool for creating and retrieving batch transformations.""" + +import uuid +from typing import Any + +from langchain_core.language_models import BaseChatModel +from langchain_core.messages.tool import ToolCall +from langchain_core.tools import BaseTool, StructuredTool +from langgraph.types import interrupt +from uipath.agent.models.agent import ( + AgentInternalBatchTransformToolProperties, + AgentInternalToolResourceConfig, +) +from uipath.eval.mocks import mockable +from uipath.platform import UiPath +from uipath.platform.common import CreateBatchTransform +from uipath.platform.common.interrupt_models import WaitEphemeralIndex +from uipath.platform.context_grounding import ( + BatchTransformOutputColumn, + EphemeralIndexUsage, +) +from uipath.platform.context_grounding.context_grounding_index import ( + ContextGroundingIndex, +) + +from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model +from uipath_langchain.agent.react.types import AgentGraphState +from uipath_langchain.agent.tools.internal_tools.schema_utils import ( + add_query_field_to_schema, +) +from uipath_langchain.agent.tools.static_args import handle_static_args +from uipath_langchain.agent.tools.structured_tool_with_argument_properties import ( + StructuredToolWithArgumentProperties, +) +from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType +from uipath_langchain.agent.tools.utils import sanitize_tool_name + + +def create_batch_transform_tool( + resource: AgentInternalToolResourceConfig, llm: BaseChatModel +) -> StructuredTool: + """Create a Batch Transform internal tool from resource configuration.""" + if not isinstance(resource.properties, AgentInternalBatchTransformToolProperties): + raise ValueError( + f"Expected AgentInternalBatchTransformToolProperties, got {type(resource.properties)}" + ) + + tool_name = sanitize_tool_name(resource.name) + properties = resource.properties + settings = properties.settings + + # Extract settings + query_setting = settings.query + folder_path_prefix_setting = settings.folder_path_prefix + output_columns_setting = settings.output_columns + web_search_grounding_setting = settings.web_search_grounding + + is_query_static = query_setting and query_setting.variant == "static" + static_query = query_setting.value if is_query_static else None + + static_folder_path_prefix = None + if folder_path_prefix_setting: + static_folder_path_prefix = getattr(folder_path_prefix_setting, "value", None) + + static_web_search = False + if web_search_grounding_setting: + value = getattr(web_search_grounding_setting, "value", None) + static_web_search = value == "Enabled" if value else False + + batch_transform_output_columns = [ + BatchTransformOutputColumn(name=col.name, description=col.description or "") + for col in output_columns_setting + ] + + # Use resource input schema and add query field if dynamic + input_schema = dict(resource.input_schema) + if not is_query_static: + add_query_field_to_schema( + input_schema, + query_description=query_setting.description if query_setting else None, + default_description="Describe the task: what to research, what to synthesize.", + ) + + # Create input model from modified schema + input_model = create_model(input_schema) + output_model = create_model(resource.output_schema) + + @mockable( + name=resource.name, + description=resource.description, + input_schema=input_model.model_json_schema() if input_model else None, + output_schema=output_model.model_json_schema(), + example_calls=[], # Examples cannot be provided for internal tools + ) + async def batch_transform_tool_fn(**kwargs: Any) -> dict[str, Any]: + query = kwargs.get("query") if not is_query_static else static_query + if not query: + raise ValueError("Query is required for Batch Transform tool") + + if "attachment" not in kwargs: + raise ValueError("Argument 'attachment' is not available") + + attachment = kwargs.get("attachment") + if not attachment: + raise ValueError("Attachment is required for Batch Transform tool") + + attachment_id = getattr(attachment, "ID", None) + if not attachment_id: + raise ValueError("Attachment ID is required") + + destination_path = kwargs.get("destination_path", "output.csv") + + uipath = UiPath() + ephemeral_index = await uipath.context_grounding.create_ephemeral_index_async( + usage=EphemeralIndexUsage.BATCH_RAG, + attachments=[attachment_id], + ) + + if ephemeral_index.in_progress_ingestion(): + ephemeral_index_dict = interrupt(WaitEphemeralIndex(index=ephemeral_index)) + ephemeral_index = ContextGroundingIndex(**ephemeral_index_dict) + + return interrupt( + CreateBatchTransform( + name=f"task-{uuid.uuid4()}", + index_name=ephemeral_index.name, + index_id=ephemeral_index.id, + prompt=query, + output_columns=batch_transform_output_columns, + storage_bucket_folder_path_prefix=static_folder_path_prefix, + enable_web_search_grounding=static_web_search, + destination_path=destination_path, + is_ephemeral_index=True, + ) + ) + + # Import here to avoid circular dependency + from uipath_langchain.agent.wrappers import get_job_attachment_wrapper + + job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model) + + async def batch_transform_tool_wrapper( + tool: BaseTool, + call: ToolCall, + state: AgentGraphState, + ) -> ToolWrapperReturnType: + call["args"] = handle_static_args(resource, state, call["args"]) + return await job_attachment_wrapper(tool, call, state) + + tool = StructuredToolWithArgumentProperties( + name=tool_name, + description=resource.description, + args_schema=input_model, + coroutine=batch_transform_tool_fn, + output_type=output_model, + argument_properties=resource.argument_properties, + ) + tool.set_tool_wrappers(awrapper=batch_transform_tool_wrapper) + return tool diff --git a/src/uipath_langchain/agent/tools/internal_tools/deeprag_tool.py b/src/uipath_langchain/agent/tools/internal_tools/deeprag_tool.py new file mode 100644 index 00000000..95280a99 --- /dev/null +++ b/src/uipath_langchain/agent/tools/internal_tools/deeprag_tool.py @@ -0,0 +1,142 @@ +"""Deeprag tool for creation and retrieval of deeprags.""" + +import uuid +from typing import Any + +from langchain_core.language_models import BaseChatModel +from langchain_core.messages.tool import ToolCall +from langchain_core.tools import BaseTool, StructuredTool +from langgraph.types import interrupt +from uipath.agent.models.agent import ( + AgentInternalDeepRagToolProperties, + AgentInternalToolResourceConfig, +) +from uipath.eval.mocks import mockable +from uipath.platform import UiPath +from uipath.platform.common import CreateDeepRag +from uipath.platform.common.interrupt_models import WaitEphemeralIndex +from uipath.platform.context_grounding import ( + CitationMode, + EphemeralIndexUsage, +) +from uipath.platform.context_grounding.context_grounding_index import ( + ContextGroundingIndex, +) + +from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model +from uipath_langchain.agent.react.types import AgentGraphState +from uipath_langchain.agent.tools.internal_tools.schema_utils import ( + add_query_field_to_schema, +) +from uipath_langchain.agent.tools.static_args import handle_static_args +from uipath_langchain.agent.tools.structured_tool_with_argument_properties import ( + StructuredToolWithArgumentProperties, +) +from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType +from uipath_langchain.agent.tools.utils import sanitize_tool_name + + +def create_deeprag_tool( + resource: AgentInternalToolResourceConfig, llm: BaseChatModel +) -> StructuredTool: + """Create a DeepRAG internal tool from resource configuration.""" + if not isinstance(resource.properties, AgentInternalDeepRagToolProperties): + raise ValueError( + f"Expected AgentInternalDeepRagToolProperties, got {type(resource.properties)}" + ) + + tool_name = sanitize_tool_name(resource.name) + properties = resource.properties + settings = properties.settings + + # Extract settings + query_setting = settings.query + citation_mode_setting = settings.citation_mode + + citation_mode = ( + CitationMode(citation_mode_setting.value) + if citation_mode_setting + else CitationMode.INLINE + ) + + is_query_static = query_setting and query_setting.variant == "static" + static_query = query_setting.value if is_query_static else None + + input_schema = dict(resource.input_schema) + if not is_query_static: + add_query_field_to_schema( + input_schema, + query_description=query_setting.description if query_setting else None, + default_description="Describe the task: what to research across documents, what to synthesize and how to cite sources.", + ) + + input_model = create_model(input_schema) + output_model = create_model(resource.output_schema) + + @mockable( + name=resource.name, + description=resource.description, + input_schema=input_model.model_json_schema() if input_model else None, + output_schema=output_model.model_json_schema(), + example_calls=[], # Examples cannot be provided for internal tools + ) + async def deeprag_tool_fn(**kwargs: Any) -> dict[str, Any]: + query = kwargs.get("query") if not is_query_static else static_query + if not query: + raise ValueError("Query is required for DeepRAG tool") + + if "attachment" not in kwargs: + raise ValueError("Argument 'attachment' is not available") + + attachment = kwargs.get("attachment") + if not attachment: + raise ValueError("Attachment is required for DeepRAG tool") + + attachment_id = getattr(attachment, "ID", None) + if not attachment_id: + raise ValueError("Attachment ID is required") + + uipath = UiPath() + ephemeral_index = await uipath.context_grounding.create_ephemeral_index_async( + usage=EphemeralIndexUsage.DEEP_RAG, + attachments=[attachment_id], + ) + + if ephemeral_index.in_progress_ingestion(): + ephemeral_index_dict = interrupt(WaitEphemeralIndex(index=ephemeral_index)) + ephemeral_index = ContextGroundingIndex(**ephemeral_index_dict) + + return interrupt( + CreateDeepRag( + name=f"task-{uuid.uuid4()}", + index_name=ephemeral_index.name, + index_id=ephemeral_index.id, + prompt=query, + citation_mode=citation_mode, + is_ephemeral_index=True, + ) + ) + + # Import here to avoid circular dependency + from uipath_langchain.agent.wrappers import get_job_attachment_wrapper + + job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model) + + async def deeprag_tool_wrapper( + tool: BaseTool, + call: ToolCall, + state: AgentGraphState, + ) -> ToolWrapperReturnType: + call["args"] = handle_static_args(resource, state, call["args"]) + return await job_attachment_wrapper(tool, call, state) + + tool = StructuredToolWithArgumentProperties( + name=tool_name, + description=resource.description, + args_schema=input_model, + coroutine=deeprag_tool_fn, + output_type=output_model, + argument_properties=resource.argument_properties, + ) + tool.set_tool_wrappers(awrapper=deeprag_tool_wrapper) + return tool diff --git a/src/uipath_langchain/agent/tools/internal_tools/internal_tool_factory.py b/src/uipath_langchain/agent/tools/internal_tools/internal_tool_factory.py index 7bf6cf4b..bc88dbbc 100644 --- a/src/uipath_langchain/agent/tools/internal_tools/internal_tool_factory.py +++ b/src/uipath_langchain/agent/tools/internal_tools/internal_tool_factory.py @@ -24,12 +24,16 @@ ) from .analyze_files_tool import create_analyze_file_tool +from .batch_transform_tool import create_batch_transform_tool +from .deeprag_tool import create_deeprag_tool _INTERNAL_TOOL_HANDLERS: dict[ AgentInternalToolType, Callable[[AgentInternalToolResourceConfig, BaseChatModel], StructuredTool], ] = { AgentInternalToolType.ANALYZE_FILES: create_analyze_file_tool, + AgentInternalToolType.DEEP_RAG: create_deeprag_tool, + AgentInternalToolType.BATCH_TRANSFORM: create_batch_transform_tool, } diff --git a/src/uipath_langchain/agent/tools/internal_tools/schema_utils.py b/src/uipath_langchain/agent/tools/internal_tools/schema_utils.py new file mode 100644 index 00000000..888abef4 --- /dev/null +++ b/src/uipath_langchain/agent/tools/internal_tools/schema_utils.py @@ -0,0 +1,33 @@ +"""Utility functions for internal tool schema manipulation.""" + +from typing import Any + + +def add_query_field_to_schema( + input_schema: dict[str, Any], + query_description: str | None = None, + default_description: str = "Query or prompt for the operation.", +) -> None: + """Add a dynamic query field to an input schema. + + This modifies the input schema in-place by adding a 'query' property + and marking it as required. + + Args: + input_schema: The JSON schema dict to modify + query_description: Custom description for the query field + default_description: Default description if query_description is not provided + """ + if "properties" not in input_schema: + input_schema["properties"] = {} + + input_schema["properties"]["query"] = { + "type": "string", + "description": query_description if query_description else default_description, + } + + if "required" not in input_schema: + input_schema["required"] = [] + + if "query" not in input_schema["required"]: + input_schema["required"].append("query") diff --git a/tests/agent/tools/internal_tools/test_analyze_files_tool.py b/tests/agent/tools/internal_tools/test_analyze_files_tool.py index 4ef0a439..ea5e804d 100644 --- a/tests/agent/tools/internal_tools/test_analyze_files_tool.py +++ b/tests/agent/tools/internal_tools/test_analyze_files_tool.py @@ -7,7 +7,7 @@ from langchain_core.messages import AIMessage, HumanMessage from pydantic import BaseModel, ConfigDict, Field from uipath.agent.models.agent import ( - AgentInternalToolProperties, + AgentInternalAnalyzeFilesToolProperties, AgentInternalToolResourceConfig, AgentInternalToolType, ) @@ -60,7 +60,7 @@ def resource_config(self): } output_schema = {"type": "object", "properties": {"result": {"type": "string"}}} - properties = AgentInternalToolProperties( + properties = AgentInternalAnalyzeFilesToolProperties( tool_type=AgentInternalToolType.ANALYZE_FILES ) diff --git a/tests/agent/tools/internal_tools/test_batch_transform_tool.py b/tests/agent/tools/internal_tools/test_batch_transform_tool.py new file mode 100644 index 00000000..d986952a --- /dev/null +++ b/tests/agent/tools/internal_tools/test_batch_transform_tool.py @@ -0,0 +1,484 @@ +"""Tests for batch_transform_tool.py module.""" + +import uuid +from unittest.mock import AsyncMock, Mock, patch + +import pytest +from pydantic import BaseModel, ConfigDict, Field +from uipath.agent.models.agent import ( + AgentContextOutputColumn, + AgentContextQuerySetting, + AgentInternalBatchTransformSettings, + AgentInternalBatchTransformToolProperties, + AgentInternalToolResourceConfig, + AgentInternalToolType, + BatchTransformFileExtension, + BatchTransformFileExtensionSetting, + BatchTransformWebSearchGrounding, + BatchTransformWebSearchGroundingSetting, +) +from uipath.platform.context_grounding.context_grounding_index import ( + ContextGroundingIndex, +) + +from uipath_langchain.agent.tools.internal_tools.batch_transform_tool import ( + create_batch_transform_tool, +) + + +class MockAttachment(BaseModel): + """Mock attachment model for testing.""" + + model_config = ConfigDict(populate_by_name=True) + + ID: str = Field(alias="ID") + FullName: str = Field(alias="FullName") + MimeType: str = Field(alias="MimeType") + + +class TestCreateBatchTransformTool: + """Test cases for create_batch_transform_tool function.""" + + @pytest.fixture + def mock_llm(self): + """Fixture for mock LLM.""" + return AsyncMock() + + @pytest.fixture + def batch_transform_settings_static_query(self): + """Fixture for Batch Transform settings with static query.""" + return AgentInternalBatchTransformSettings( + context_type="attachment", + query=AgentContextQuerySetting( + value="Extract customer data", variant="static" + ), + folder_path_prefix=AgentContextQuerySetting(value="data/"), + file_extension=BatchTransformFileExtensionSetting( + value=BatchTransformFileExtension.CSV + ), + output_columns=[ + AgentContextOutputColumn( + name="customer_name", description="Name of the customer" + ), + AgentContextOutputColumn( + name="email", description="Customer email address" + ), + ], + web_search_grounding=BatchTransformWebSearchGroundingSetting( + value=BatchTransformWebSearchGrounding.DISABLED + ), + ) + + @pytest.fixture + def batch_transform_settings_dynamic_query(self): + """Fixture for Batch Transform settings with dynamic query.""" + return AgentInternalBatchTransformSettings( + context_type="attachment", + query=AgentContextQuerySetting( + description="Enter transformation query", variant="dynamic" + ), + folder_path_prefix=None, + file_extension=BatchTransformFileExtensionSetting( + value=BatchTransformFileExtension.CSV + ), + output_columns=[ + AgentContextOutputColumn( + name="result", description="Transformation result" + ), + ], + web_search_grounding=BatchTransformWebSearchGroundingSetting( + value=BatchTransformWebSearchGrounding.ENABLED + ), + ) + + @pytest.fixture + def resource_config_static(self, batch_transform_settings_static_query): + """Fixture for resource configuration with static query.""" + input_schema = { + "type": "object", + "properties": {"attachment": {"type": "object"}}, + "required": ["attachment"], + } + output_schema = { + "type": "object", + "properties": {"file_path": {"type": "string"}}, + } + + properties = AgentInternalBatchTransformToolProperties( + tool_type=AgentInternalToolType.BATCH_TRANSFORM, + settings=batch_transform_settings_static_query, + ) + + return AgentInternalToolResourceConfig( + name="batch_transform_static", + description="Transform CSV with Batch Transform (static query)", + input_schema=input_schema, + output_schema=output_schema, + properties=properties, + ) + + @pytest.fixture + def resource_config_dynamic(self, batch_transform_settings_dynamic_query): + """Fixture for resource configuration with dynamic query.""" + input_schema = { + "type": "object", + "properties": {"attachment": {"type": "object"}}, + "required": ["attachment"], + } + output_schema = { + "type": "object", + "properties": {"output": {"type": "string"}}, + } + + properties = AgentInternalBatchTransformToolProperties( + tool_type=AgentInternalToolType.BATCH_TRANSFORM, + settings=batch_transform_settings_dynamic_query, + ) + + return AgentInternalToolResourceConfig( + name="batch_transform_dynamic", + description="Transform CSV with Batch Transform (dynamic query)", + input_schema=input_schema, + output_schema=output_schema, + properties=properties, + ) + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_batch_transform_tool_static_query_index_ready( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_static, + mock_llm, + ): + """Test Batch Transform tool with static query when index is immediately ready.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + mock_index = ContextGroundingIndex( + id=str(uuid.uuid4()), + name="ephemeral-batch-123", + last_ingestion_status="Successful", + ) + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index + ) + + mock_interrupt.return_value = {"file_path": "/path/to/output.csv"} + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_batch_transform_tool(resource_config_static, mock_llm) + + # Verify tool creation + assert tool.name == "batch_transform_static" + assert ( + tool.description == "Transform CSV with Batch Transform (static query)" + ) + + # Test tool execution + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="data.csv", MimeType="text/csv" + ) + + assert tool.coroutine is not None + result = await tool.coroutine(attachment=mock_attachment) + + # Verify result + assert result == {"file_path": "/path/to/output.csv"} + + # Verify ephemeral index was created + mock_uipath.context_grounding.create_ephemeral_index_async.assert_called_once() + call_kwargs = ( + mock_uipath.context_grounding.create_ephemeral_index_async.call_args.kwargs + ) + assert call_kwargs["usage"] == "BatchRAG" + assert mock_attachment.ID in call_kwargs["attachments"] + + # Verify interrupt was called only once (no WaitEphemeralIndex needed) + assert mock_interrupt.call_count == 1 + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_batch_transform_tool_static_query_wait_for_ingestion( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_static, + mock_llm, + ): + """Test Batch Transform tool with static query when index needs to wait for ingestion.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + pending_id = str(uuid.uuid4()) + mock_index_pending = ContextGroundingIndex( + id=pending_id, + name="ephemeral-batch-456", + last_ingestion_status="Queued", + ) + + mock_index_complete = { + "id": mock_index_pending.id, + "name": mock_index_pending.name, + "last_ingestion_status": "Successful", + } + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index_pending + ) + + # First interrupt returns completed index, second returns Batch Transform result + mock_interrupt.side_effect = [ + mock_index_complete, + {"file_path": "/path/to/transformed.csv"}, + ] + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_batch_transform_tool(resource_config_static, mock_llm) + + # Test tool execution + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="data.csv", MimeType="text/csv" + ) + + assert tool.coroutine is not None + result = await tool.coroutine(attachment=mock_attachment) + + # Verify result + assert result == {"file_path": "/path/to/transformed.csv"} + + # Verify interrupt was called twice (WaitEphemeralIndex + CreateBatchTransform) + assert mock_interrupt.call_count == 2 + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_batch_transform_tool_dynamic_query( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_dynamic, + mock_llm, + ): + """Test Batch Transform tool with dynamic query.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + mock_index = ContextGroundingIndex( + id=str(uuid.uuid4()), + name="ephemeral-batch-789", + last_ingestion_status="Successful", + ) + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index + ) + + mock_interrupt.return_value = {"output": "Transformation complete"} + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_batch_transform_tool(resource_config_dynamic, mock_llm) + + # Test tool execution with dynamic query + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="input.csv", MimeType="text/csv" + ) + + assert tool.coroutine is not None + result = await tool.coroutine( + attachment=mock_attachment, query="Extract all names" + ) + + # Verify result + assert result == {"output": "Transformation complete"} + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_batch_transform_tool_default_destination_path( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_static, + mock_llm, + ): + """Test Batch Transform tool defaults to output.csv when destination_path not provided.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + mock_index = ContextGroundingIndex( + id=str(uuid.uuid4()), + name="ephemeral-batch-default", + last_ingestion_status="Successful", + ) + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index + ) + + mock_interrupt.return_value = {"file_path": "output.csv"} + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_batch_transform_tool(resource_config_static, mock_llm) + + # Test tool execution without destination_path + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="data.csv", MimeType="text/csv" + ) + + assert tool.coroutine is not None + result = await tool.coroutine(attachment=mock_attachment) + + # Verify result + assert result == {"file_path": "output.csv"} + + # Verify CreateBatchTransform was called with default destination_path + assert mock_interrupt.call_count == 1 + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_batch_transform_tool_custom_destination_path( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_static, + mock_llm, + ): + """Test Batch Transform tool with custom destination_path.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + mock_index = ContextGroundingIndex( + id=str(uuid.uuid4()), + name="ephemeral-batch-custom", + last_ingestion_status="Successful", + ) + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index + ) + + mock_interrupt.return_value = {"file_path": "/custom/path/result.csv"} + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_batch_transform_tool(resource_config_static, mock_llm) + + # Test tool execution with custom destination_path + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="data.csv", MimeType="text/csv" + ) + + assert tool.coroutine is not None + result = await tool.coroutine( + attachment=mock_attachment, destination_path="/custom/path/result.csv" + ) + + # Verify result + assert result == {"file_path": "/custom/path/result.csv"} + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + async def test_create_batch_transform_tool_missing_attachment( + self, mock_get_wrapper, resource_config_static, mock_llm + ): + """Test tool execution fails when attachment is missing.""" + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + tool = create_batch_transform_tool(resource_config_static, mock_llm) + + assert tool.coroutine is not None + with pytest.raises(ValueError, match="Argument 'attachment' is not available"): + await tool.coroutine() + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + async def test_create_batch_transform_tool_missing_query_dynamic( + self, mock_get_wrapper, resource_config_dynamic, mock_llm + ): + """Test tool execution fails when query is missing (dynamic mode).""" + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + tool = create_batch_transform_tool(resource_config_dynamic, mock_llm) + + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="data.csv", MimeType="text/csv" + ) + + assert tool.coroutine is not None + with pytest.raises( + ValueError, match="Query is required for Batch Transform tool" + ): + await tool.coroutine(attachment=mock_attachment) + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + async def test_create_batch_transform_tool_missing_attachment_id( + self, mock_get_wrapper, resource_config_static, mock_llm + ): + """Test tool execution fails when attachment ID is missing.""" + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + tool = create_batch_transform_tool(resource_config_static, mock_llm) + + class AttachmentWithoutID(BaseModel): + FullName: str + MimeType: str + + mock_attachment = AttachmentWithoutID( + FullName="data.csv", MimeType="text/csv" + ) + + assert tool.coroutine is not None + with pytest.raises(ValueError, match="Attachment ID is required"): + await tool.coroutine(attachment=mock_attachment) diff --git a/tests/agent/tools/internal_tools/test_deeprag_tool.py b/tests/agent/tools/internal_tools/test_deeprag_tool.py new file mode 100644 index 00000000..314b468b --- /dev/null +++ b/tests/agent/tools/internal_tools/test_deeprag_tool.py @@ -0,0 +1,355 @@ +"""Tests for deeprag_tool.py module.""" + +import uuid +from unittest.mock import AsyncMock, Mock, patch + +import pytest +from pydantic import BaseModel, ConfigDict, Field +from uipath.agent.models.agent import ( + AgentContextQuerySetting, + AgentInternalDeepRagSettings, + AgentInternalDeepRagToolProperties, + AgentInternalToolResourceConfig, + AgentInternalToolType, + CitationMode, + DeepRagCitationModeSetting, + DeepRagFileExtension, + DeepRagFileExtensionSetting, +) +from uipath.platform.context_grounding.context_grounding_index import ( + ContextGroundingIndex, +) + +from uipath_langchain.agent.tools.internal_tools.deeprag_tool import ( + create_deeprag_tool, +) + + +class MockAttachment(BaseModel): + """Mock attachment model for testing.""" + + model_config = ConfigDict(populate_by_name=True) + + ID: str = Field(alias="ID") + FullName: str = Field(alias="FullName") + MimeType: str = Field(alias="MimeType") + + +class TestCreateDeepRagTool: + """Test cases for create_deeprag_tool function.""" + + @pytest.fixture + def mock_llm(self): + """Fixture for mock LLM.""" + return AsyncMock() + + @pytest.fixture + def deeprag_settings_static_query(self): + """Fixture for DeepRAG settings with static query.""" + return AgentInternalDeepRagSettings( + context_type="attachment", + query=AgentContextQuerySetting( + value="What are the main points?", variant="static" + ), + folder_path_prefix=None, + citation_mode=DeepRagCitationModeSetting(value=CitationMode.INLINE), + file_extension=DeepRagFileExtensionSetting( + value=DeepRagFileExtension.PDF + ), + ) + + @pytest.fixture + def deeprag_settings_dynamic_query(self): + """Fixture for DeepRAG settings with dynamic query.""" + return AgentInternalDeepRagSettings( + context_type="attachment", + query=AgentContextQuerySetting( + description="Enter your query", variant="dynamic" + ), + folder_path_prefix=None, + citation_mode=DeepRagCitationModeSetting(value=CitationMode.SKIP), + file_extension=DeepRagFileExtensionSetting( + value=DeepRagFileExtension.TXT + ), + ) + + @pytest.fixture + def resource_config_static(self, deeprag_settings_static_query): + """Fixture for resource configuration with static query.""" + input_schema = { + "type": "object", + "properties": {"attachment": {"type": "object"}}, + "required": ["attachment"], + } + output_schema = {"type": "object", "properties": {"text": {"type": "string"}}} + + properties = AgentInternalDeepRagToolProperties( + tool_type=AgentInternalToolType.DEEP_RAG, + settings=deeprag_settings_static_query, + ) + + return AgentInternalToolResourceConfig( + name="deeprag_static", + description="Analyze document with DeepRAG (static query)", + input_schema=input_schema, + output_schema=output_schema, + properties=properties, + ) + + @pytest.fixture + def resource_config_dynamic(self, deeprag_settings_dynamic_query): + """Fixture for resource configuration with dynamic query.""" + input_schema = { + "type": "object", + "properties": {"attachment": {"type": "object"}}, + "required": ["attachment"], + } + output_schema = {"type": "object", "properties": {"content": {"type": "string"}}} + + properties = AgentInternalDeepRagToolProperties( + tool_type=AgentInternalToolType.DEEP_RAG, + settings=deeprag_settings_dynamic_query, + ) + + return AgentInternalToolResourceConfig( + name="deeprag_dynamic", + description="Analyze document with DeepRAG (dynamic query)", + input_schema=input_schema, + output_schema=output_schema, + properties=properties, + ) + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_deeprag_tool_static_query_index_ready( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_static, + mock_llm, + ): + """Test DeepRAG tool with static query when index is immediately ready.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + mock_index = ContextGroundingIndex( + id=str(uuid.uuid4()), + name="ephemeral-index-123", + last_ingestion_status="Successful", + ) + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index + ) + + mock_interrupt.return_value = {"text": "Deep RAG analysis result"} + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_deeprag_tool(resource_config_static, mock_llm) + + # Verify tool creation + assert tool.name == "deeprag_static" + assert tool.description == "Analyze document with DeepRAG (static query)" + + # Test tool execution + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="test.pdf", MimeType="application/pdf" + ) + + assert tool.coroutine is not None + result = await tool.coroutine(attachment=mock_attachment) + + # Verify result + assert result == {"text": "Deep RAG analysis result"} + + # Verify ephemeral index was created + mock_uipath.context_grounding.create_ephemeral_index_async.assert_called_once() + call_kwargs = ( + mock_uipath.context_grounding.create_ephemeral_index_async.call_args.kwargs + ) + assert call_kwargs["usage"] == "DeepRAG" + assert mock_attachment.ID in call_kwargs["attachments"] + + # Verify interrupt was called only once (no WaitEphemeralIndex needed) + assert mock_interrupt.call_count == 1 + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_deeprag_tool_static_query_wait_for_ingestion( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_static, + mock_llm, + ): + """Test DeepRAG tool with static query when index needs to wait for ingestion.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + pending_id = str(uuid.uuid4()) + mock_index_pending = ContextGroundingIndex( + id=pending_id, + name="ephemeral-index-456", + last_ingestion_status="In Progress", # Space is important! + ) + + mock_index_complete = { + "id": mock_index_pending.id, + "name": mock_index_pending.name, + "last_ingestion_status": "Successful", + } + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index_pending + ) + + # First interrupt returns completed index, second returns DeepRAG result + mock_interrupt.side_effect = [ + mock_index_complete, + {"text": "Deep RAG analysis after waiting"}, + ] + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_deeprag_tool(resource_config_static, mock_llm) + + # Test tool execution + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="test.pdf", MimeType="application/pdf" + ) + + assert tool.coroutine is not None + result = await tool.coroutine(attachment=mock_attachment) + + # Verify result + assert result == {"text": "Deep RAG analysis after waiting"} + + # Verify interrupt was called twice (WaitEphemeralIndex + CreateDeepRag) + assert mock_interrupt.call_count == 2 + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath") + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.interrupt") + @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", lambda **kwargs: lambda f: f) + async def test_create_deeprag_tool_dynamic_query( + self, + mock_interrupt, + mock_uipath_class, + mock_get_wrapper, + resource_config_dynamic, + mock_llm, + ): + """Test DeepRAG tool with dynamic query.""" + # Setup mocks + mock_uipath = AsyncMock() + mock_uipath_class.return_value = mock_uipath + + mock_index = ContextGroundingIndex( + id=str(uuid.uuid4()), + name="ephemeral-index-789", + last_ingestion_status="Successful", + ) + + mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock( + return_value=mock_index + ) + + mock_interrupt.return_value = {"content": "Dynamic query result"} + + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + # Create tool + tool = create_deeprag_tool(resource_config_dynamic, mock_llm) + + # Test tool execution with dynamic query + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="test.txt", MimeType="text/plain" + ) + + assert tool.coroutine is not None + result = await tool.coroutine( + attachment=mock_attachment, query="What is the summary?" + ) + + # Verify result + assert result == {"content": "Dynamic query result"} + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + async def test_create_deeprag_tool_missing_attachment( + self, mock_get_wrapper, resource_config_static, mock_llm + ): + """Test tool execution fails when attachment is missing.""" + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + tool = create_deeprag_tool(resource_config_static, mock_llm) + + assert tool.coroutine is not None + with pytest.raises(ValueError, match="Argument 'attachment' is not available"): + await tool.coroutine() + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + async def test_create_deeprag_tool_missing_query_dynamic( + self, mock_get_wrapper, resource_config_dynamic, mock_llm + ): + """Test tool execution fails when query is missing (dynamic mode).""" + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + tool = create_deeprag_tool(resource_config_dynamic, mock_llm) + + mock_attachment = MockAttachment( + ID=str(uuid.uuid4()), FullName="test.txt", MimeType="text/plain" + ) + + assert tool.coroutine is not None + with pytest.raises(ValueError, match="Query is required for DeepRAG tool"): + await tool.coroutine(attachment=mock_attachment) + + @patch( + "uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper" + ) + async def test_create_deeprag_tool_missing_attachment_id( + self, mock_get_wrapper, resource_config_static, mock_llm + ): + """Test tool execution fails when attachment ID is missing.""" + mock_wrapper = Mock() + mock_get_wrapper.return_value = mock_wrapper + + tool = create_deeprag_tool(resource_config_static, mock_llm) + + class AttachmentWithoutID(BaseModel): + FullName: str + MimeType: str + + mock_attachment = AttachmentWithoutID( + FullName="test.pdf", MimeType="application/pdf" + ) + + assert tool.coroutine is not None + with pytest.raises(ValueError, match="Attachment ID is required"): + await tool.coroutine(attachment=mock_attachment) diff --git a/tests/agent/tools/test_tool_factory.py b/tests/agent/tools/test_tool_factory.py index 38135247..80860176 100644 --- a/tests/agent/tools/test_tool_factory.py +++ b/tests/agent/tools/test_tool_factory.py @@ -14,7 +14,7 @@ AgentEscalationResourceConfig, AgentIntegrationToolProperties, AgentIntegrationToolResourceConfig, - AgentInternalToolProperties, + AgentInternalAnalyzeFilesToolProperties, AgentInternalToolResourceConfig, AgentInternalToolType, AgentIxpExtractionResourceConfig, @@ -154,7 +154,7 @@ def internal_resource() -> AgentInternalToolResourceConfig: }, }, output_schema=EMPTY_SCHEMA, - properties=AgentInternalToolProperties( + properties=AgentInternalAnalyzeFilesToolProperties( tool_type=AgentInternalToolType.ANALYZE_FILES, ), ) diff --git a/uv.lock b/uv.lock index 55b18969..26756350 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.14'", @@ -3255,7 +3255,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.6.22" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "applicationinsights" }, @@ -3276,28 +3276,28 @@ dependencies = [ { name = "uipath-core" }, { name = "uipath-runtime" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/f4/9f648a464584d868e08352d0f1578e71acd5c53b9030f80e89fc85082809/uipath-2.6.22.tar.gz", hash = "sha256:ac14999d53310a877406843ff5a91074ef5cf2877be8ce618b62771fb2c85f1c", size = 3927225, upload-time = "2026-01-29T07:51:59.366Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/8e/e4aeb707153eeaeff707f5b753c1b72c93a211a9d11aba9d133e0ed91c25/uipath-2.7.0.tar.gz", hash = "sha256:4e2a43ee4676733989c5b77abf5d89b8c26de2236ec5a944b02864f5ccfa3379", size = 3934767, upload-time = "2026-02-03T21:15:28.068Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/af/913a0e2117244603496131665554cd8b9096b94314593bd7df09cbe8ff20/uipath-2.6.22-py3-none-any.whl", hash = "sha256:f45b16ee04f4c3ddf7b23978fb0d6d6606421f93868a8a3d73caf017f3f13c03", size = 461230, upload-time = "2026-01-29T07:51:57.862Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d0/16f2628d692dfb1e53176be9aeed459102f2b111fd4ce44ce3d167d40702/uipath-2.7.0-py3-none-any.whl", hash = "sha256:ac555d509a2e83cfef56735284231fa54e76583ea5f1a0e69376f3f2f361f011", size = 466642, upload-time = "2026-02-03T21:15:25.598Z" }, ] [[package]] name = "uipath-core" -version = "0.2.2" +version = "0.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/53/ff/bdb43cc852b6067bb052e0cd57bf084027c2ab19b306cc49ec64dcc19c3f/uipath_core-0.2.2.tar.gz", hash = "sha256:cced1a18f7e2ef5842384e7481544d1b8292d8c65da938932de1eb616d3b0295", size = 107502, upload-time = "2026-01-28T12:34:56.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/b9/36aa58515a024f672666e85b66ac1a4ab10343cb308c25b978a9b7c6c5f9/uipath_core-0.2.3.tar.gz", hash = "sha256:8c85281e4d93efc89080dc6764e58ca1fbf914d67cd6c51f5bcb95fd562d0b56", size = 107516, upload-time = "2026-02-02T09:48:20.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/0c/f60854afc587e6d3f2f4fd2bcc6797bfc30de4e6e9b0d44f2a7485ee4683/uipath_core-0.2.2-py3-none-any.whl", hash = "sha256:700e3b9735f456774cfe5da99d88756cc3e87b40dfd95b6e2e5f3d8a525f74b0", size = 34305, upload-time = "2026-01-28T12:34:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/c4/05/2ca2be071109fa35f3fdb6235ce0315ee9f598e21f7640e878971492d389/uipath_core-0.2.3-py3-none-any.whl", hash = "sha256:3a14b9c930dd1137e19fe58d9042c855796ae5a20216d092adf4feab457fd0f5", size = 34311, upload-time = "2026-02-02T09:48:18.809Z" }, ] [[package]] name = "uipath-langchain" -version = "0.5.21" +version = "0.5.22" source = { editable = "." } dependencies = [ { name = "httpx" }, @@ -3362,7 +3362,7 @@ requires-dist = [ { name = "openinference-instrumentation-langchain", specifier = ">=0.1.56" }, { name = "pydantic-settings", specifier = ">=2.6.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "uipath", specifier = ">=2.6.22,<2.7.0" }, + { name = "uipath", specifier = ">=2.7.0,<2.8.0" }, { name = "uipath-runtime", specifier = ">=0.6.0,<0.7.0" }, ] provides-extras = ["vertex", "bedrock"] From 35727443a32d3d6ec3c2f70c1980968a5ede8b20 Mon Sep 17 00:00:00 2001 From: Caleb Martin Date: Tue, 3 Feb 2026 13:53:30 -0800 Subject: [PATCH 2/2] fix: lint --- .../test_batch_transform_tool.py | 33 ++++++++++++------- .../tools/internal_tools/test_deeprag_tool.py | 28 ++++++++++------ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/tests/agent/tools/internal_tools/test_batch_transform_tool.py b/tests/agent/tools/internal_tools/test_batch_transform_tool.py index d986952a..343b9e38 100644 --- a/tests/agent/tools/internal_tools/test_batch_transform_tool.py +++ b/tests/agent/tools/internal_tools/test_batch_transform_tool.py @@ -148,7 +148,10 @@ def resource_config_dynamic(self, batch_transform_settings_dynamic_query): ) @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_batch_transform_tool_static_query_index_ready( self, mock_interrupt, @@ -182,9 +185,7 @@ async def test_create_batch_transform_tool_static_query_index_ready( # Verify tool creation assert tool.name == "batch_transform_static" - assert ( - tool.description == "Transform CSV with Batch Transform (static query)" - ) + assert tool.description == "Transform CSV with Batch Transform (static query)" # Test tool execution mock_attachment = MockAttachment( @@ -213,7 +214,10 @@ async def test_create_batch_transform_tool_static_query_index_ready( ) @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_batch_transform_tool_static_query_wait_for_ingestion( self, mock_interrupt, @@ -275,7 +279,10 @@ async def test_create_batch_transform_tool_static_query_wait_for_ingestion( ) @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_batch_transform_tool_dynamic_query( self, mock_interrupt, @@ -325,7 +332,10 @@ async def test_create_batch_transform_tool_dynamic_query( ) @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_batch_transform_tool_default_destination_path( self, mock_interrupt, @@ -376,7 +386,10 @@ async def test_create_batch_transform_tool_default_destination_path( ) @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.batch_transform_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_batch_transform_tool_custom_destination_path( self, mock_interrupt, @@ -475,9 +488,7 @@ class AttachmentWithoutID(BaseModel): FullName: str MimeType: str - mock_attachment = AttachmentWithoutID( - FullName="data.csv", MimeType="text/csv" - ) + mock_attachment = AttachmentWithoutID(FullName="data.csv", MimeType="text/csv") assert tool.coroutine is not None with pytest.raises(ValueError, match="Attachment ID is required"): diff --git a/tests/agent/tools/internal_tools/test_deeprag_tool.py b/tests/agent/tools/internal_tools/test_deeprag_tool.py index 314b468b..3f60ee4a 100644 --- a/tests/agent/tools/internal_tools/test_deeprag_tool.py +++ b/tests/agent/tools/internal_tools/test_deeprag_tool.py @@ -53,9 +53,7 @@ def deeprag_settings_static_query(self): ), folder_path_prefix=None, citation_mode=DeepRagCitationModeSetting(value=CitationMode.INLINE), - file_extension=DeepRagFileExtensionSetting( - value=DeepRagFileExtension.PDF - ), + file_extension=DeepRagFileExtensionSetting(value=DeepRagFileExtension.PDF), ) @pytest.fixture @@ -68,9 +66,7 @@ def deeprag_settings_dynamic_query(self): ), folder_path_prefix=None, citation_mode=DeepRagCitationModeSetting(value=CitationMode.SKIP), - file_extension=DeepRagFileExtensionSetting( - value=DeepRagFileExtension.TXT - ), + file_extension=DeepRagFileExtensionSetting(value=DeepRagFileExtension.TXT), ) @pytest.fixture @@ -104,7 +100,10 @@ def resource_config_dynamic(self, deeprag_settings_dynamic_query): "properties": {"attachment": {"type": "object"}}, "required": ["attachment"], } - output_schema = {"type": "object", "properties": {"content": {"type": "string"}}} + output_schema = { + "type": "object", + "properties": {"content": {"type": "string"}}, + } properties = AgentInternalDeepRagToolProperties( tool_type=AgentInternalToolType.DEEP_RAG, @@ -124,7 +123,10 @@ def resource_config_dynamic(self, deeprag_settings_dynamic_query): ) @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_deeprag_tool_static_query_index_ready( self, mock_interrupt, @@ -187,7 +189,10 @@ async def test_create_deeprag_tool_static_query_index_ready( ) @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_deeprag_tool_static_query_wait_for_ingestion( self, mock_interrupt, @@ -249,7 +254,10 @@ async def test_create_deeprag_tool_static_query_wait_for_ingestion( ) @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath") @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.interrupt") - @patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", lambda **kwargs: lambda f: f) + @patch( + "uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable", + lambda **kwargs: lambda f: f, + ) async def test_create_deeprag_tool_dynamic_query( self, mock_interrupt,