diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index b53fb68f9..44f48463f 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.1.21" +version = "0.1.22" 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/__init__.py b/packages/uipath-platform/src/uipath/platform/guardrails/__init__.py index de439e92a..93c60b0a4 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/__init__.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/__init__.py @@ -25,6 +25,11 @@ GuardrailExecutionStage, GuardrailTargetAdapter, GuardrailValidatorBase, + HarmfulContentEntity, + HarmfulContentEntityType, + HarmfulContentValidator, + IntellectualPropertyEntityType, + IntellectualPropertyValidator, LogAction, LoggingSeverityLevel, PIIDetectionEntity, @@ -32,6 +37,7 @@ PIIValidator, PromptInjectionValidator, RuleFunction, + UserPromptAttacksValidator, guardrail, register_guardrail_adapter, ) @@ -61,10 +67,16 @@ "GuardrailValidatorBase", "BuiltInGuardrailValidator", "CustomGuardrailValidator", + "HarmfulContentValidator", + "IntellectualPropertyValidator", "PIIValidator", "PromptInjectionValidator", + "UserPromptAttacksValidator", "CustomValidator", "RuleFunction", + "HarmfulContentEntity", + "HarmfulContentEntityType", + "IntellectualPropertyEntityType", "PIIDetectionEntity", "PIIDetectionEntityType", "GuardrailExecutionStage", diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py index 727925fb1..e8d692164 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py @@ -7,19 +7,27 @@ from ._actions import BlockAction, LogAction, LoggingSeverityLevel from ._core import GuardrailExclude -from ._enums import GuardrailExecutionStage, PIIDetectionEntityType +from ._enums import ( + GuardrailExecutionStage, + HarmfulContentEntityType, + IntellectualPropertyEntityType, + PIIDetectionEntityType, +) from ._exceptions import GuardrailBlockException from ._guardrail import guardrail -from ._models import GuardrailAction, PIIDetectionEntity +from ._models import GuardrailAction, HarmfulContentEntity, PIIDetectionEntity from ._registry import GuardrailTargetAdapter, register_guardrail_adapter from .validators import ( BuiltInGuardrailValidator, CustomGuardrailValidator, CustomValidator, GuardrailValidatorBase, + HarmfulContentValidator, + IntellectualPropertyValidator, PIIValidator, PromptInjectionValidator, RuleFunction, + UserPromptAttacksValidator, ) __all__ = [ @@ -29,11 +37,17 @@ "GuardrailValidatorBase", "BuiltInGuardrailValidator", "CustomGuardrailValidator", + "HarmfulContentValidator", + "IntellectualPropertyValidator", "PIIValidator", "PromptInjectionValidator", + "UserPromptAttacksValidator", "CustomValidator", "RuleFunction", # Models & enums + "HarmfulContentEntity", + "HarmfulContentEntityType", + "IntellectualPropertyEntityType", "PIIDetectionEntity", "PIIDetectionEntityType", "GuardrailExecutionStage", diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_enums.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_enums.py index be7832ddf..49956f62f 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_enums.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_enums.py @@ -42,3 +42,22 @@ class PIIDetectionEntityType(str, Enum): USUK_PASSPORT_NUMBER = "UsukPassportNumber" URL = "URL" IP_ADDRESS = "IPAddress" + + +class HarmfulContentEntityType(str, Enum): + """Harmful content entity types supported by UiPath guardrails. + + These entities correspond to the Azure Content Safety categories. + """ + + HATE = "Hate" + SELF_HARM = "SelfHarm" + SEXUAL = "Sexual" + VIOLENCE = "Violence" + + +class IntellectualPropertyEntityType(str, Enum): + """Intellectual property entity types supported by UiPath guardrails.""" + + TEXT = "Text" + CODE = "Code" diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_models.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_models.py index ac22538e0..8d86fbf39 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_models.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/_models.py @@ -26,6 +26,23 @@ def __post_init__(self) -> None: ) +@dataclass +class HarmfulContentEntity: + """Harmful content entity configuration with severity threshold. + + Args: + name: The entity type name (e.g. ``HarmfulContentEntityType.VIOLENCE``). + threshold: Severity threshold (0 to 6) for detection. Defaults to ``2``. + """ + + name: str + threshold: int = 2 + + def __post_init__(self) -> None: + if not 0 <= self.threshold <= 6: + raise ValueError(f"Threshold must be between 0 and 6, got {self.threshold}") + + class GuardrailAction(ABC): """Interface for defining custom actions when a guardrail violation is detected. diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/__init__.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/__init__.py index 6be170534..bbcf29039 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/__init__.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/__init__.py @@ -6,15 +6,21 @@ GuardrailValidatorBase, ) from .custom import CustomValidator, RuleFunction +from .harmful_content import HarmfulContentValidator +from .intellectual_property import IntellectualPropertyValidator from .pii import PIIValidator from .prompt_injection import PromptInjectionValidator +from .user_prompt_attacks import UserPromptAttacksValidator __all__ = [ "GuardrailValidatorBase", "BuiltInGuardrailValidator", "CustomGuardrailValidator", + "HarmfulContentValidator", + "IntellectualPropertyValidator", "PIIValidator", "PromptInjectionValidator", + "UserPromptAttacksValidator", "CustomValidator", "RuleFunction", ] diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/harmful_content.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/harmful_content.py new file mode 100644 index 000000000..d186341d7 --- /dev/null +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/harmful_content.py @@ -0,0 +1,77 @@ +"""Harmful content detection guardrail validator.""" + +from typing import Any, Sequence +from uuid import uuid4 + +from uipath.platform.guardrails.guardrails import ( + BuiltInValidatorGuardrail, + EnumListParameterValue, + MapEnumParameterValue, +) + +from .._models import HarmfulContentEntity +from ._base import BuiltInGuardrailValidator + + +class HarmfulContentValidator(BuiltInGuardrailValidator): + """Validate data for harmful content using the UiPath API. + + Supported at all stages (PRE, POST, PRE_AND_POST). + + Args: + entities: One or more :class:`~uipath.platform.guardrails.decorators.HarmfulContentEntity` + instances specifying which harmful content categories to detect + and their severity thresholds. + + Raises: + ValueError: If *entities* is empty. + """ + + def __init__(self, entities: Sequence[HarmfulContentEntity]) -> None: + """Initialize HarmfulContentValidator with entities to detect.""" + if not entities: + raise ValueError("entities must be provided and non-empty") + self.entities = list(entities) + + def get_built_in_guardrail( + self, + name: str, + description: str | None, + enabled_for_evals: bool, + ) -> BuiltInValidatorGuardrail: + """Build a harmful content :class:`BuiltInValidatorGuardrail`. + + Args: + name: Name for the guardrail. + description: Optional description. + enabled_for_evals: Whether active in evaluation scenarios. + + Returns: + Configured :class:`BuiltInValidatorGuardrail` for harmful content detection. + """ + entity_names = [entity.name for entity in self.entities] + entity_thresholds: dict[str, Any] = { + entity.name: entity.threshold for entity in self.entities + } + + return BuiltInValidatorGuardrail( + id=str(uuid4()), + name=name, + description=description + or f"Detects harmful content: {', '.join(entity_names)}", + enabled_for_evals=enabled_for_evals, + guardrail_type="builtInValidator", + validator_type="harmful_content", + validator_parameters=[ + EnumListParameterValue( + parameter_type="enum-list", + id="harmfulContentEntities", + value=entity_names, + ), + MapEnumParameterValue( + parameter_type="map-enum", + id="harmfulContentEntityThresholds", + value=entity_thresholds, + ), + ], + ) diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/intellectual_property.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/intellectual_property.py new file mode 100644 index 000000000..8a18e6a37 --- /dev/null +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/intellectual_property.py @@ -0,0 +1,67 @@ +"""Intellectual property detection guardrail validator.""" + +from typing import Sequence +from uuid import uuid4 + +from uipath.platform.guardrails.guardrails import ( + BuiltInValidatorGuardrail, + EnumListParameterValue, +) + +from .._enums import GuardrailExecutionStage +from ._base import BuiltInGuardrailValidator + + +class IntellectualPropertyValidator(BuiltInGuardrailValidator): + """Validate output for intellectual property violations using the UiPath API. + + Restricted to POST stage only — IP detection is an output-only concern. + + Args: + entities: One or more entity type strings (e.g. + ``IntellectualPropertyEntityType.TEXT``). + + Raises: + ValueError: If *entities* is empty. + """ + + supported_stages = [GuardrailExecutionStage.POST] + + def __init__(self, entities: Sequence[str]) -> None: + """Initialize IntellectualPropertyValidator with entities to detect.""" + if not entities: + raise ValueError("entities must be provided and non-empty") + self.entities = list(entities) + + def get_built_in_guardrail( + self, + name: str, + description: str | None, + enabled_for_evals: bool, + ) -> BuiltInValidatorGuardrail: + """Build an intellectual property :class:`BuiltInValidatorGuardrail`. + + Args: + name: Name for the guardrail. + description: Optional description. + enabled_for_evals: Whether active in evaluation scenarios. + + Returns: + Configured :class:`BuiltInValidatorGuardrail` for IP detection. + """ + return BuiltInValidatorGuardrail( + id=str(uuid4()), + name=name, + description=description + or f"Detects intellectual property: {', '.join(self.entities)}", + enabled_for_evals=enabled_for_evals, + guardrail_type="builtInValidator", + validator_type="intellectual_property", + validator_parameters=[ + EnumListParameterValue( + parameter_type="enum-list", + id="ipEntities", + value=self.entities, + ), + ], + ) diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/user_prompt_attacks.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/user_prompt_attacks.py new file mode 100644 index 000000000..7275acc25 --- /dev/null +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/user_prompt_attacks.py @@ -0,0 +1,44 @@ +"""User prompt attacks detection guardrail validator.""" + +from uuid import uuid4 + +from uipath.platform.guardrails.guardrails import BuiltInValidatorGuardrail + +from .._enums import GuardrailExecutionStage +from ._base import BuiltInGuardrailValidator + + +class UserPromptAttacksValidator(BuiltInGuardrailValidator): + """Validate input for user prompt attacks via the UiPath API. + + Restricted to PRE stage only — prompt attacks are an input-only concern. + Takes no parameters. + """ + + supported_stages = [GuardrailExecutionStage.PRE] + + def get_built_in_guardrail( + self, + name: str, + description: str | None, + enabled_for_evals: bool, + ) -> BuiltInValidatorGuardrail: + """Build a user prompt attacks :class:`BuiltInValidatorGuardrail`. + + Args: + name: Name for the guardrail. + description: Optional description. + enabled_for_evals: Whether active in evaluation scenarios. + + Returns: + Configured :class:`BuiltInValidatorGuardrail` for user prompt attacks. + """ + return BuiltInValidatorGuardrail( + id=str(uuid4()), + name=name, + description=description or "Detects user prompt attacks", + enabled_for_evals=enabled_for_evals, + guardrail_type="builtInValidator", + validator_type="user_prompt_attacks", + validator_parameters=[], + ) diff --git a/packages/uipath-platform/tests/services/test_azure_guardrail_validators.py b/packages/uipath-platform/tests/services/test_azure_guardrail_validators.py new file mode 100644 index 000000000..fd6a3f39e --- /dev/null +++ b/packages/uipath-platform/tests/services/test_azure_guardrail_validators.py @@ -0,0 +1,146 @@ +"""Tests for the Azure-provided guardrail validators. + +Covers HarmfulContentValidator, IntellectualPropertyValidator, and +UserPromptAttacksValidator — verifying guardrail construction, parameter +serialization, stage enforcement, and input validation. +""" + +from __future__ import annotations + +import pytest + +from uipath.platform.guardrails.decorators import ( + GuardrailExecutionStage, + HarmfulContentEntity, + HarmfulContentEntityType, + HarmfulContentValidator, + IntellectualPropertyEntityType, + IntellectualPropertyValidator, + UserPromptAttacksValidator, +) + +# --------------------------------------------------------------------------- +# HarmfulContentValidator +# --------------------------------------------------------------------------- + + +class TestHarmfulContentValidator: + """Tests for HarmfulContentValidator.""" + + def test_builds_guardrail(self): + """Verify get_built_in_guardrail returns correct structure.""" + validator = HarmfulContentValidator( + entities=[ + HarmfulContentEntity(HarmfulContentEntityType.VIOLENCE, threshold=3), + HarmfulContentEntity(HarmfulContentEntityType.HATE, threshold=4), + ] + ) + guardrail = validator.get_built_in_guardrail( + name="Test HC", + description="test", + enabled_for_evals=True, + ) + assert guardrail.validator_type == "harmful_content" + assert len(guardrail.validator_parameters) == 2 + + enum_param = guardrail.validator_parameters[0] + assert enum_param.id == "harmfulContentEntities" + assert enum_param.value == ["Violence", "Hate"] + + map_param = guardrail.validator_parameters[1] + assert map_param.id == "harmfulContentEntityThresholds" + assert map_param.value == {"Violence": 3, "Hate": 4} + + def test_empty_entities_raises(self): + """Empty entities should raise ValueError.""" + with pytest.raises(ValueError, match="non-empty"): + HarmfulContentValidator(entities=[]) + + def test_threshold_validation(self): + """Threshold outside 0-6 should raise ValueError.""" + with pytest.raises(ValueError, match="between 0 and 6"): + HarmfulContentEntity(HarmfulContentEntityType.VIOLENCE, threshold=7) + with pytest.raises(ValueError, match="between 0 and 6"): + HarmfulContentEntity(HarmfulContentEntityType.VIOLENCE, threshold=-1) + + def test_all_stages_supported(self): + """supported_stages should be empty (all stages allowed).""" + validator = HarmfulContentValidator( + entities=[HarmfulContentEntity(HarmfulContentEntityType.VIOLENCE)] + ) + assert validator.supported_stages == [] + # Should not raise for any stage + validator.validate_stage(GuardrailExecutionStage.PRE) + validator.validate_stage(GuardrailExecutionStage.POST) + + +# --------------------------------------------------------------------------- +# IntellectualPropertyValidator +# --------------------------------------------------------------------------- + + +class TestIntellectualPropertyValidator: + """Tests for IntellectualPropertyValidator.""" + + def test_builds_guardrail(self): + """Verify get_built_in_guardrail returns correct structure.""" + validator = IntellectualPropertyValidator( + entities=[ + IntellectualPropertyEntityType.TEXT, + IntellectualPropertyEntityType.CODE, + ] + ) + guardrail = validator.get_built_in_guardrail( + name="Test IP", + description=None, + enabled_for_evals=False, + ) + assert guardrail.validator_type == "intellectual_property" + assert len(guardrail.validator_parameters) == 1 + + param = guardrail.validator_parameters[0] + assert param.id == "ipEntities" + assert param.value == ["Text", "Code"] + + def test_empty_entities_raises(self): + """Empty entities should raise ValueError.""" + with pytest.raises(ValueError, match="non-empty"): + IntellectualPropertyValidator(entities=[]) + + def test_post_only(self): + """Should only support POST stage.""" + validator = IntellectualPropertyValidator( + entities=[IntellectualPropertyEntityType.TEXT] + ) + assert validator.supported_stages == [GuardrailExecutionStage.POST] + validator.validate_stage(GuardrailExecutionStage.POST) + with pytest.raises(ValueError, match="does not support stage"): + validator.validate_stage(GuardrailExecutionStage.PRE) + + +# --------------------------------------------------------------------------- +# UserPromptAttacksValidator +# --------------------------------------------------------------------------- + + +class TestUserPromptAttacksValidator: + """Tests for UserPromptAttacksValidator.""" + + def test_builds_guardrail(self): + """Verify get_built_in_guardrail returns correct structure.""" + validator = UserPromptAttacksValidator() + guardrail = validator.get_built_in_guardrail( + name="Test UPA", + description=None, + enabled_for_evals=True, + ) + assert guardrail.validator_type == "user_prompt_attacks" + assert guardrail.validator_parameters == [] + + def test_pre_only(self): + """Should only support PRE stage.""" + validator = UserPromptAttacksValidator() + assert validator.supported_stages == [GuardrailExecutionStage.PRE] + validator.validate_stage(GuardrailExecutionStage.PRE) + with pytest.raises(ValueError, match="does not support stage"): + validator.validate_stage(GuardrailExecutionStage.POST) diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index 648df8ab4..0494f3530 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1088,7 +1088,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.21" +version = "0.1.22" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index b8a817758..679981407 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2682,7 +2682,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.21" +version = "0.1.22" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" },