diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index 42178758a..f5c7522b9 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.2.9" +version = "0.2.10" 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 b73d810e7..fbeb1f027 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py @@ -127,6 +127,8 @@ def evaluate_guardrail( "BYO (Bring Your Own) guardrails require byo_validator_name." ) payload["byoValidatorName"] = guardrail.byo_validator_name + if guardrail.byo_connection_id: + payload["byoConnectionId"] = guardrail.byo_connection_id spec = RequestSpec( method="POST", endpoint=Endpoint("/agentsruntime_/api/execution/guardrails/validate"), diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py b/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py index dace18019..c7a968a61 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py @@ -92,6 +92,7 @@ class BuiltInValidatorGuardrail(BaseGuardrail): default_factory=list, alias="validatorParameters" ) byo_validator_name: str | None = Field(default=None, alias="byoValidatorName") + byo_connection_id: str | None = Field(default=None, alias="byoConnectionId") model_config = ConfigDict(populate_by_name=True, extra="allow") diff --git a/packages/uipath-platform/tests/services/test_guardrails_service.py b/packages/uipath-platform/tests/services/test_guardrails_service.py index 6fd20bbc5..c94bf8be3 100644 --- a/packages/uipath-platform/tests/services/test_guardrails_service.py +++ b/packages/uipath-platform/tests/services/test_guardrails_service.py @@ -350,6 +350,109 @@ def capture_request(request): assert request_payload["byoValidatorName"] == "my_databricks_pii" assert result.result == GuardrailValidationResultType.PASSED + def test_evaluate_guardrail_byog_forwards_byo_connection_id( + self, + httpx_mock: HTTPXMock, + service: GuardrailsService, + base_url: str, + org: str, + tenant: str, + ) -> None: + """A BYOG guardrail forwards byoConnectionId so the backend can narrow + configuration resolution to the specific connection.""" + captured_request = None + + def capture_request(request): + nonlocal captured_request + captured_request = request + return httpx.Response( + status_code=200, + json={"result": "PASSED", "details": "Validation passed"}, + ) + + httpx_mock.add_callback( + method="POST", + url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate", + callback=capture_request, + ) + + byog_guardrail = BuiltInValidatorGuardrail( + id="byog-id", + name="Databricks PII (BYOG)", + enabled_for_evals=True, + selector=GuardrailSelector(scopes=[GuardrailScope.LLM]), + guardrail_type="builtInValidator", + validator_type="byo", + byo_validator_name="my_databricks_pii", + byo_connection_id="byog-conn-1", + validator_parameters=[], + ) + + service.evaluate_guardrail("some input", byog_guardrail) + + assert captured_request is not None + request_payload = json.loads(captured_request.content) + assert request_payload["byoValidatorName"] == "my_databricks_pii" + assert request_payload["byoConnectionId"] == "byog-conn-1" + + def test_evaluate_guardrail_byog_omits_connection_id_when_absent( + self, + httpx_mock: HTTPXMock, + service: GuardrailsService, + base_url: str, + org: str, + tenant: str, + ) -> None: + """byoConnectionId is only forwarded when present; a BYOG guardrail without + one resolves by validator name alone.""" + captured_request = None + + def capture_request(request): + nonlocal captured_request + captured_request = request + return httpx.Response( + status_code=200, + json={"result": "PASSED", "details": "Validation passed"}, + ) + + httpx_mock.add_callback( + method="POST", + url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate", + callback=capture_request, + ) + + byog_guardrail = BuiltInValidatorGuardrail( + id="byog-id", + name="Databricks PII (BYOG)", + enabled_for_evals=True, + selector=GuardrailSelector(scopes=[GuardrailScope.LLM]), + guardrail_type="builtInValidator", + validator_type="byo", + byo_validator_name="my_databricks_pii", + validator_parameters=[], + ) + + service.evaluate_guardrail("some input", byog_guardrail) + + assert captured_request is not None + request_payload = json.loads(captured_request.content) + assert "byoConnectionId" not in request_payload + + def test_evaluate_guardrail_byo_connection_id_from_alias(self) -> None: + """byoConnectionId parses into the typed field via its camelCase alias.""" + guardrail = BuiltInValidatorGuardrail.model_validate( + { + "$guardrailType": "builtInValidator", + "id": "byog-id", + "name": "BYOG", + "validatorType": "byo", + "byoValidatorName": "my_databricks_pii", + "byoConnectionId": "byog-conn-1", + "validatorParameters": [], + } + ) + assert guardrail.byo_connection_id == "byog-conn-1" + def test_evaluate_guardrail_byo_without_name_raises( self, service: GuardrailsService, @@ -426,6 +529,49 @@ def capture_request(request): request_payload = json.loads(captured_request.content) assert "byoValidatorName" not in request_payload + def test_evaluate_guardrail_non_byo_type_does_not_forward_connection_id( + self, + httpx_mock: HTTPXMock, + service: GuardrailsService, + base_url: str, + org: str, + tenant: str, + ) -> None: + """byoConnectionId is only forwarded for the "byo" sentinel, never leaked + into a non-BYOG validator payload even if the field happens to be set.""" + captured_request = None + + def capture_request(request): + nonlocal captured_request + captured_request = request + return httpx.Response( + status_code=200, + json={"result": "PASSED", "details": "Validation passed"}, + ) + + httpx_mock.add_callback( + method="POST", + url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate", + callback=capture_request, + ) + + guardrail = BuiltInValidatorGuardrail( + id="test-id", + name="PII detection guardrail", + enabled_for_evals=True, + selector=GuardrailSelector(scopes=[GuardrailScope.LLM]), + guardrail_type="builtInValidator", + validator_type="pii_detection", + byo_connection_id="stray-conn", + validator_parameters=[], + ) + + service.evaluate_guardrail("some input", guardrail) + + assert captured_request is not None + request_payload = json.loads(captured_request.content) + assert "byoConnectionId" not in request_payload + def test_evaluate_guardrail_ootb_omits_byo_validator_name( self, httpx_mock: HTTPXMock, diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index ea1f85ad3..744c292d3 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.9" +version = "0.2.10" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 1c38c3b33..aea72e7a1 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2741,7 +2741,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.2.9" +version = "0.2.10" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" },