diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index be322ea1e..4699bfeeb 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.35" +version = "0.2.36" description = "HTTP client library for programmatic access to UiPath Platform" readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py index cf3381e0d..f3b87b8dd 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py @@ -119,7 +119,9 @@ def evaluate_guardrail( Args: input_data: The text or structured data to validate. Dictionaries will be converted to a string before validation. guardrail: A guardrail instance used for validation. - attachments: Files attached to the run that the guardrail may inspect. + attachments: Files the guardrail may inspect. ``None`` omits the field, so + the backend checks only the text. An empty list is sent as ``[]``, + meaning files apply but there are none to inspect. termination_mode: ``FAIL_FAST`` stops scanning at the first violation; ``EVALUATE_ALL`` or ``None`` scans everything. @@ -141,7 +143,7 @@ def evaluate_guardrail( "BYO (Bring Your Own) guardrails require byo_validator_name." ) payload["byoValidatorName"] = guardrail.byo_validator_name - if attachments: + if attachments is not None: payload["attachments"] = [a.model_dump(by_alias=True) for a in attachments] if termination_mode is not None: payload["terminationMode"] = termination_mode.value diff --git a/packages/uipath-platform/tests/services/test_guardrails_service.py b/packages/uipath-platform/tests/services/test_guardrails_service.py index ad3a93c0f..d135c5c66 100644 --- a/packages/uipath-platform/tests/services/test_guardrails_service.py +++ b/packages/uipath-platform/tests/services/test_guardrails_service.py @@ -975,7 +975,7 @@ def test_attachments_key_is_absent_when_not_supplied( assert "attachments" not in json.loads(httpx_mock.get_requests()[0].content) - def test_attachments_key_is_absent_when_empty_list( + def test_empty_attachments_list_is_sent_as_empty_list( self, httpx_mock: HTTPXMock, service: GuardrailsService, @@ -983,6 +983,7 @@ def test_attachments_key_is_absent_when_empty_list( org: str, tenant: str, ) -> None: + """An empty list tells the backend files apply but none were found.""" httpx_mock.add_response( url=f"{base_url}{org}{tenant}{_VALIDATE_PATH}", status_code=200, @@ -991,7 +992,8 @@ def test_attachments_key_is_absent_when_empty_list( service.evaluate_guardrail("x", _judge_guardrail(), attachments=[]) - assert "attachments" not in json.loads(httpx_mock.get_requests()[0].content) + body = json.loads(httpx_mock.get_requests()[0].content) + assert body["attachments"] == [] def test_attachment_round_trips_the_wire_shape(self) -> None: """The camelCase body the API emits parses back into the model unchanged.""" @@ -1126,6 +1128,27 @@ def test_folder_header_absent_without_attachments_even_if_configured( request = httpx_mock.get_requests()[0] assert "x-uipath-folderkey" not in request.headers + def test_folder_header_absent_with_empty_attachments_even_if_configured( + self, + httpx_mock: HTTPXMock, + service: GuardrailsService, + base_url: str, + org: str, + tenant: str, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + monkeypatch.setenv("UIPATH_FOLDER_KEY", "folder-key-123") + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}{_VALIDATE_PATH}", + status_code=200, + json={"result": "PASSED", "details": ""}, + ) + + service.evaluate_guardrail("x", _judge_guardrail(), attachments=[]) + + request = httpx_mock.get_requests()[0] + assert "x-uipath-folderkey" not in request.headers + class TestGuardrailAttachmentTiming: """Attachments still get the longer, file-fetching-aware timeout.""" @@ -1170,6 +1193,26 @@ def test_evaluate_guardrail_keeps_default_timeout_without_attachments( timeout = httpx_mock.get_requests()[0].extensions["timeout"] assert timeout["read"] != 60.0 + def test_evaluate_guardrail_keeps_default_timeout_with_empty_attachments( + self, + httpx_mock: HTTPXMock, + service: GuardrailsService, + base_url: str, + org: str, + tenant: str, + ) -> None: + """No file is fetched for an empty list, so it needs no longer timeout.""" + httpx_mock.add_response( + url=f"{base_url}{org}{tenant}{_VALIDATE_PATH}", + status_code=200, + json={"result": "PASSED", "details": ""}, + ) + + service.evaluate_guardrail("x", _judge_guardrail(), attachments=[]) + + timeout = httpx_mock.get_requests()[0].extensions["timeout"] + assert timeout["read"] != 60.0 + class TestGuardrailFlaggedAttachmentIds: """evaluate_guardrail parses the flagged attachment ids from the response.""" diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index b02fd2c40..99070bbf4 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.35" +version = "0.2.36" source = { editable = "." } dependencies = [ { name = "anyio" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 9887fc03a..09541f1cc 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2762,7 +2762,7 @@ wheels = [ [[package]] name = "uipath-platform" -version = "0.2.35" +version = "0.2.36" source = { editable = "../uipath-platform" } dependencies = [ { name = "anyio" },