Skip to content

Commit dddfe7e

Browse files
feat: add ByoValidator for Bring Your Own Guardrail (BYOG) [AL-512] (#1833)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 6de5bed commit dddfe7e

8 files changed

Lines changed: 204 additions & 3 deletions

File tree

packages/uipath-platform/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-platform"
3-
version = "0.2.13"
3+
version = "0.2.14"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/guardrails/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .decorators import (
1818
BlockAction,
1919
BuiltInGuardrailValidator,
20+
ByoValidator,
2021
CustomGuardrailValidator,
2122
CustomValidator,
2223
GuardrailAction,
@@ -43,6 +44,7 @@
4344
register_guardrail_adapter,
4445
)
4546
from .guardrails import (
47+
BYO_VALIDATOR_TYPE,
4648
BuiltInValidatorGuardrail,
4749
EnumListParameterValue,
4850
GuardrailType,
@@ -53,6 +55,7 @@
5355
# Service
5456
"GuardrailsService",
5557
# Guardrail models
58+
"BYO_VALIDATOR_TYPE",
5659
"BuiltInValidatorGuardrail",
5760
"GuardrailType",
5861
"GuardrailValidationResultType",
@@ -67,6 +70,7 @@
6770
"guardrail",
6871
"GuardrailValidatorBase",
6972
"BuiltInGuardrailValidator",
73+
"ByoValidator",
7074
"CustomGuardrailValidator",
7175
"HarmfulContentValidator",
7276
"IntellectualPropertyValidator",

packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ._registry import GuardrailTargetAdapter, register_guardrail_adapter
2020
from .validators import (
2121
BuiltInGuardrailValidator,
22+
ByoValidator,
2223
CustomGuardrailValidator,
2324
CustomValidator,
2425
GuardrailValidatorBase,
@@ -37,6 +38,7 @@
3738
# Validators
3839
"GuardrailValidatorBase",
3940
"BuiltInGuardrailValidator",
41+
"ByoValidator",
4042
"CustomGuardrailValidator",
4143
"HarmfulContentValidator",
4244
"IntellectualPropertyValidator",

packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
CustomGuardrailValidator,
66
GuardrailValidatorBase,
77
)
8+
from .byo import ByoValidator
89
from .custom import CustomValidator, RuleFunction
910
from .harmful_content import HarmfulContentValidator
1011
from .intellectual_property import IntellectualPropertyValidator
@@ -16,6 +17,7 @@
1617
__all__ = [
1718
"GuardrailValidatorBase",
1819
"BuiltInGuardrailValidator",
20+
"ByoValidator",
1921
"CustomGuardrailValidator",
2022
"HarmfulContentValidator",
2123
"IntellectualPropertyValidator",
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Bring Your Own Guardrail (BYOG) validator."""
2+
3+
from typing import Sequence
4+
from uuid import uuid4
5+
6+
from uipath.platform.guardrails.guardrails import (
7+
BYO_VALIDATOR_TYPE,
8+
BuiltInValidatorGuardrail,
9+
ValidatorParameter,
10+
)
11+
12+
from ._base import BuiltInGuardrailValidator
13+
14+
15+
class ByoValidator(BuiltInGuardrailValidator):
16+
"""Validate data through a Bring Your Own Guardrail (BYOG) configuration.
17+
18+
BYOG lets an organization plug its own safety validator (e.g. a customer
19+
Azure Content Safety subscription, a vendor connector, or a custom
20+
Integration Service connector) into UiPath guardrails. An admin first
21+
creates the configuration under ``Admin -> AI Trust Layer -> Guardrails
22+
Configurations``; this validator references it by its validator name and
23+
(recommended) Integration Service connection id.
24+
25+
Supported at all stages — BYO validator capabilities are connector-defined
26+
and cannot be known statically, so no stage restriction is applied here.
27+
Configuring a scope or stage the connector does not support surfaces as a
28+
``PROVIDER_ERROR`` at evaluation time.
29+
30+
Example::
31+
32+
from uipath.platform.guardrails.decorators import (
33+
BlockAction,
34+
ByoValidator,
35+
guardrail,
36+
)
37+
38+
byog_harmful_content = ByoValidator(
39+
"byog-harmful-content",
40+
connection_id="24887687-6ed1-4fe2-9b87-087ffb232682",
41+
)
42+
43+
@guardrail(validator=byog_harmful_content, action=BlockAction())
44+
def summarize(text: str) -> str:
45+
...
46+
47+
Args:
48+
validator_name: The BYOG configuration's validator name
49+
(``byoValidatorName``), as shown in Admin -> AI Trust Layer ->
50+
Guardrails Configurations.
51+
connection_id: Optional Integration Service connection id backing the
52+
BYOG configuration. Strongly recommended: validator names are only
53+
unique per connection, so omitting it lets the server pick the
54+
first configuration matching the name.
55+
parameters: Optional list of validator parameters. BYO parameter
56+
schemas are connector-defined, so values are passed through as-is.
57+
58+
Raises:
59+
ValueError: If *validator_name* is empty or whitespace.
60+
"""
61+
62+
def __init__(
63+
self,
64+
validator_name: str,
65+
*,
66+
connection_id: str | None = None,
67+
parameters: Sequence[ValidatorParameter] | None = None,
68+
) -> None:
69+
"""Initialize ByoValidator with a BYOG configuration reference."""
70+
if not validator_name or not validator_name.strip():
71+
raise ValueError("validator_name must be a non-empty string")
72+
self.validator_name = validator_name
73+
self.connection_id = connection_id
74+
self.parameters = list(parameters or [])
75+
76+
def get_built_in_guardrail(
77+
self,
78+
name: str,
79+
description: str | None,
80+
enabled_for_evals: bool,
81+
) -> BuiltInValidatorGuardrail:
82+
"""Build a BYOG :class:`BuiltInValidatorGuardrail`.
83+
84+
Args:
85+
name: Name for the guardrail.
86+
description: Optional description.
87+
enabled_for_evals: Whether active in evaluation scenarios.
88+
89+
Returns:
90+
Configured :class:`BuiltInValidatorGuardrail` referencing the BYOG
91+
configuration via ``byoValidatorName``/``byoConnectionId``.
92+
"""
93+
return BuiltInValidatorGuardrail(
94+
id=str(uuid4()),
95+
name=name,
96+
description=description
97+
or f"Bring Your Own Guardrail validation '{self.validator_name}'",
98+
enabled_for_evals=enabled_for_evals,
99+
guardrail_type="builtInValidator",
100+
validator_type=BYO_VALIDATOR_TYPE,
101+
validator_parameters=self.parameters,
102+
byo_validator_name=self.validator_name,
103+
byo_connection_id=self.connection_id,
104+
)

packages/uipath-platform/tests/services/test_guardrails_decorators.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from uipath.platform.guardrails.decorators import (
2323
BlockAction,
24+
ByoValidator,
2425
CustomValidator,
2526
GuardrailAction,
2627
GuardrailBlockException,
@@ -1236,3 +1237,91 @@ def joke(topic: str) -> str:
12361237
with pytest.raises(GuardrailBlockException):
12371238
joke("cats")
12381239
mock_uipath.guardrails.evaluate_guardrail.assert_called_once()
1240+
1241+
1242+
# ---------------------------------------------------------------------------
1243+
# ByoValidator — Bring Your Own Guardrail configuration reference
1244+
# ---------------------------------------------------------------------------
1245+
1246+
1247+
class TestByoValidator:
1248+
def test_empty_validator_name_raises(self):
1249+
with pytest.raises(ValueError, match="validator_name"):
1250+
ByoValidator("")
1251+
1252+
def test_whitespace_validator_name_raises(self):
1253+
with pytest.raises(ValueError, match="validator_name"):
1254+
ByoValidator(" ")
1255+
1256+
def test_builds_byo_guardrail_with_name_and_connection(self):
1257+
v = ByoValidator(
1258+
"byog-harmful-content",
1259+
connection_id="24887687-6ed1-4fe2-9b87-087ffb232682",
1260+
)
1261+
g = v.get_built_in_guardrail("G", None, True)
1262+
assert g.validator_type == "byo"
1263+
assert g.byo_validator_name == "byog-harmful-content"
1264+
assert g.byo_connection_id == "24887687-6ed1-4fe2-9b87-087ffb232682"
1265+
1266+
def test_connection_id_defaults_to_none(self):
1267+
v = ByoValidator("byog-harmful-content")
1268+
g = v.get_built_in_guardrail("G", None, True)
1269+
assert g.byo_connection_id is None
1270+
1271+
def test_aliases_serialize_for_the_wire(self):
1272+
v = ByoValidator("byog-pii", connection_id="conn-1")
1273+
g = v.get_built_in_guardrail("G", None, True)
1274+
dumped = g.model_dump(by_alias=True)
1275+
assert dumped["validatorType"] == "byo"
1276+
assert dumped["byoValidatorName"] == "byog-pii"
1277+
assert dumped["byoConnectionId"] == "conn-1"
1278+
assert dumped["$guardrailType"] == "builtInValidator"
1279+
1280+
def test_parameters_pass_through(self):
1281+
from uipath.platform.guardrails.guardrails import NumberParameterValue
1282+
1283+
param = NumberParameterValue(parameter_type="number", id="threshold", value=0.7)
1284+
v = ByoValidator("byog-custom", parameters=[param])
1285+
g = v.get_built_in_guardrail("G", None, True)
1286+
assert g.validator_parameters == [param]
1287+
1288+
def test_parameters_default_empty(self):
1289+
v = ByoValidator("byog-custom")
1290+
g = v.get_built_in_guardrail("G", None, True)
1291+
assert g.validator_parameters == []
1292+
1293+
def test_default_description_includes_validator_name(self):
1294+
v = ByoValidator("byog-harmful-content")
1295+
g = v.get_built_in_guardrail("G", None, True)
1296+
assert g.description is not None
1297+
assert "byog-harmful-content" in g.description
1298+
1299+
def test_no_stage_restriction(self):
1300+
v = ByoValidator("byog-harmful-content")
1301+
# BYO capabilities are connector-defined — all stages allowed
1302+
v.validate_stage(GuardrailExecutionStage.PRE)
1303+
v.validate_stage(GuardrailExecutionStage.POST)
1304+
1305+
def test_selector_is_none(self):
1306+
v = ByoValidator("byog-harmful-content")
1307+
g = v.get_built_in_guardrail("G", None, True)
1308+
assert g.selector is None
1309+
1310+
def test_run_forwards_byo_guardrail_to_service(self):
1311+
v = ByoValidator("byog-harmful-content", connection_id="conn-1")
1312+
mock_uipath = MagicMock()
1313+
mock_uipath.guardrails.evaluate_guardrail.return_value = (
1314+
GuardrailValidationResult(
1315+
result=GuardrailValidationResultType.PASSED, reason=""
1316+
)
1317+
)
1318+
with patch("uipath.platform.UiPath", return_value=mock_uipath):
1319+
result = v.run(
1320+
"G", None, True, "some input", GuardrailExecutionStage.PRE, None, None
1321+
)
1322+
assert result.result == GuardrailValidationResultType.PASSED
1323+
data, g = mock_uipath.guardrails.evaluate_guardrail.call_args[0]
1324+
assert data == "some input"
1325+
assert g.validator_type == "byo"
1326+
assert g.byo_validator_name == "byog-harmful-content"
1327+
assert g.byo_connection_id == "conn-1"

packages/uipath-platform/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/uipath/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)