Skip to content
Draft
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
7 changes: 7 additions & 0 deletions src/uipath_langchain/agent/tools/process_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def create_process_tool(
# invoke_async accepts only one of folder_path/folder_key, so resolve one.
folder_path = get_execution_folder_path() or resource.properties.folder_path
folder_key = get_execution_folder_key() if not folder_path else None
# getattr because the attribute is genuinely optional at runtime: BaseResourceProperties sets
# extra="allow", so against a uipath release predating the declared field the value is present
# only when the stored JSON carried it. None is also the meaningful value -- it is how
# Orchestrator is told to use the release's configured entry point.
entry_point_path = getattr(resource.properties, "entry_point_path", None)

input_model: Any = create_model(resource.input_schema)
output_model: Any = create_output_model(resource.output_schema, resource.name)
Expand Down Expand Up @@ -95,6 +100,7 @@ async def start_job():
parent_span_id=parent_span_id,
parent_operation_id=parent_operation_id,
run_as_me=True if run_as_me else None,
entry_point_path=entry_point_path,
)
except EnrichedException as e:
raise_for_enriched(
Expand Down Expand Up @@ -145,6 +151,7 @@ async def start_job():
"display_name": process_name,
"folder_path": folder_path,
"folder_key": folder_key,
"entry_point_path": entry_point_path,
"args_schema": input_model,
"output_schema": output_model,
"_span_context": _span_context,
Expand Down
39 changes: 39 additions & 0 deletions tests/agent/tools/test_process_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,45 @@ async def test_invoke_calls_processes_invoke_async(
parent_span_id=None,
parent_operation_id=None,
run_as_me=None,
entry_point_path=None,
)

@pytest.mark.asyncio
@patch("uipath_langchain._utils.durable_interrupt.decorator.interrupt")
@patch("uipath_langchain.agent.tools.process_tool.UiPath")
async def test_invoke_forwards_the_configured_entry_point(
self, mock_uipath_class, mock_interrupt, process_resource
):
"""A stored entry point reaches StartJobs; without it Orchestrator runs the release default."""
process_resource.properties.entry_point_path = "Workflows/Main.xaml"

mock_job = MagicMock(spec=Job)
mock_job.key = "job-key-123"
mock_job.folder_key = "folder-key-123"

mock_client = MagicMock()
mock_client.processes.invoke_async = AsyncMock(return_value=mock_job)
mock_client.jobs.extract_output_async = AsyncMock(return_value=None)
mock_uipath_class.return_value = mock_client

resumed = MagicMock(spec=Job)
resumed.state = "successful"
mock_interrupt.return_value = resumed

tool = create_process_tool(process_resource)
await tool.ainvoke({})

_, kwargs = mock_client.processes.invoke_async.call_args
assert kwargs["entry_point_path"] == "Workflows/Main.xaml"

def test_metadata_carries_the_entry_point(self, process_resource):
process_resource.properties.entry_point_path = "Workflows/Main.xaml"

tool = create_process_tool(process_resource)

assert tool.metadata is not None
assert tool.metadata["entry_point_path"] == "Workflows/Main.xaml"

@pytest.mark.asyncio
@patch("uipath_langchain._utils.durable_interrupt.decorator.interrupt")
@patch("uipath_langchain.agent.tools.process_tool.UiPath")
Expand Down Expand Up @@ -635,6 +672,7 @@ async def test_flow_tool_invokes_processes_invoke_async(
parent_span_id=None,
parent_operation_id=None,
run_as_me=None,
entry_point_path=None,
)

@pytest.mark.asyncio
Expand Down Expand Up @@ -717,6 +755,7 @@ async def test_function_tool_invokes_processes_invoke_async(
parent_span_id=None,
parent_operation_id=None,
run_as_me=None,
entry_point_path=None,
)

@pytest.mark.asyncio
Expand Down
Loading