diff --git a/pyproject.toml b/pyproject.toml index 6182d53a9..460e0a52a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [project] name = "uipath" -version = "2.2.29" +version = "2.2.30" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" dependencies = [ "uipath-runtime>=0.2.5, <0.3.0", - "uipath-core>=0.1.0, <0.2.0", + "uipath-core>=0.1.3, <0.2.0", "click>=8.3.1", "httpx>=0.28.1", "pyjwt>=2.10.1", diff --git a/src/uipath/agent/models/agent.py b/src/uipath/agent/models/agent.py index 0ac16653d..b1e23e341 100644 --- a/src/uipath/agent/models/agent.py +++ b/src/uipath/agent/models/agent.py @@ -6,12 +6,16 @@ from typing import Annotated, Any, Dict, List, Literal, Optional, Union from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from uipath.core.guardrails import ( + BaseGuardrail, + FieldReference, + FieldSelector, + UniversalRule, +) from uipath.platform.connections import Connection from uipath.platform.guardrails import ( BuiltInValidatorGuardrail, - CustomGuardrail, - FieldReference, ) @@ -469,9 +473,84 @@ class AgentBuiltInValidatorGuardrail(BuiltInValidatorGuardrail): ) -class AgentCustomGuardrail(CustomGuardrail): +class AgentWordOperator(str, Enum): + """Word operator enumeration.""" + + CONTAINS = "contains" + DOES_NOT_CONTAIN = "doesNotContain" + DOES_NOT_END_WITH = "doesNotEndWith" + DOES_NOT_EQUAL = "doesNotEqual" + DOES_NOT_START_WITH = "doesNotStartWith" + ENDS_WITH = "endsWith" + EQUALS = "equals" + IS_EMPTY = "isEmpty" + IS_NOT_EMPTY = "isNotEmpty" + MATCHES_REGEX = "matchesRegex" + STARTS_WITH = "startsWith" + + +class AgentWordRule(BaseModel): + """Word rule model.""" + + rule_type: Literal["word"] = Field(alias="$ruleType") + field_selector: FieldSelector = Field(alias="fieldSelector") + operator: AgentWordOperator + value: str | None = None + + model_config = ConfigDict(populate_by_name=True, extra="allow") + + +class AgentNumberOperator(str, Enum): + """Number operator enumeration.""" + + DOES_NOT_EQUAL = "doesNotEqual" + EQUALS = "equals" + GREATER_THAN = "greaterThan" + GREATER_THAN_OR_EQUAL = "greaterThanOrEqual" + LESS_THAN = "lessThan" + LESS_THAN_OR_EQUAL = "lessThanOrEqual" + + +class AgentNumberRule(BaseModel): + """Number rule model.""" + + rule_type: Literal["number"] = Field(alias="$ruleType") + field_selector: FieldSelector = Field(alias="fieldSelector") + operator: AgentNumberOperator + value: float + + model_config = ConfigDict(populate_by_name=True, extra="allow") + + +class AgentBooleanOperator(str, Enum): + """Boolean operator enumeration.""" + + EQUALS = "equals" + + +class AgentBooleanRule(BaseModel): + """Boolean rule model.""" + + rule_type: Literal["boolean"] = Field(alias="$ruleType") + field_selector: FieldSelector = Field(alias="fieldSelector") + operator: AgentBooleanOperator + value: bool + + model_config = ConfigDict(populate_by_name=True, extra="allow") + + +AgentRule = Annotated[ + AgentWordRule | AgentNumberRule | AgentBooleanRule | UniversalRule, + Field(discriminator="rule_type"), +] + + +class AgentCustomGuardrail(BaseGuardrail): """Agent custom guardrail with action capabilities.""" + guardrail_type: Literal["custom"] = Field(alias="$guardrailType") + rules: list[AgentRule] + action: GuardrailAction = Field( ..., description="Action to take when guardrail is triggered" ) diff --git a/src/uipath/platform/guardrails/__init__.py b/src/uipath/platform/guardrails/__init__.py index 9016e7f46..47c38f749 100644 --- a/src/uipath/platform/guardrails/__init__.py +++ b/src/uipath/platform/guardrails/__init__.py @@ -3,62 +3,30 @@ This module contains models related to UiPath Guardrails service. """ +# 2.3.0 remove +from uipath.core.guardrails import ( + BaseGuardrail, + DeterministicGuardrail, + GuardrailScope, + GuardrailValidationResult, +) + from ._guardrails_service import GuardrailsService from .guardrails import ( - AllFieldsSelector, - ApplyTo, - BaseGuardrail, - BooleanOperator, - BooleanRule, BuiltInValidatorGuardrail, - CustomGuardrail, EnumListParameterValue, - FieldReference, - FieldSelector, - FieldSource, - Guardrail, - GuardrailScope, - GuardrailSelector, GuardrailType, MapEnumParameterValue, - NumberOperator, - NumberParameterValue, - NumberRule, - Rule, - SelectorType, - SpecificFieldsSelector, - UniversalRule, - ValidatorParameter, - WordOperator, - WordRule, ) __all__ = [ "GuardrailsService", - "FieldSource", - "ApplyTo", - "FieldReference", - "SelectorType", - "AllFieldsSelector", - "SpecificFieldsSelector", - "FieldSelector", - "BaseGuardrail", - "GuardrailType", - "Guardrail", "BuiltInValidatorGuardrail", - "CustomGuardrail", - "WordOperator", - "WordRule", - "NumberOperator", - "NumberRule", - "BooleanOperator", - "BooleanRule", - "UniversalRule", - "Rule", - "ValidatorParameter", + "GuardrailType", + "BaseGuardrail", + "GuardrailScope", + "DeterministicGuardrail", + "GuardrailValidationResult", "EnumListParameterValue", "MapEnumParameterValue", - "NumberParameterValue", - "GuardrailScope", - "GuardrailSelector", ] diff --git a/src/uipath/platform/guardrails/_guardrails_service.py b/src/uipath/platform/guardrails/_guardrails_service.py index 3e0a1a21f..4fc8c3a56 100644 --- a/src/uipath/platform/guardrails/_guardrails_service.py +++ b/src/uipath/platform/guardrails/_guardrails_service.py @@ -1,9 +1,11 @@ from typing import Any +from uipath.core.guardrails import GuardrailValidationResult + from ..._utils import Endpoint, RequestSpec from ...tracing import traced from ..common import BaseService, UiPathApiConfig, UiPathExecutionContext -from .guardrails import BuiltInValidatorGuardrail, Guardrail, GuardrailValidationResult +from .guardrails import BuiltInValidatorGuardrail class GuardrailsService(BaseService): @@ -29,43 +31,34 @@ def __init__( def evaluate_guardrail( self, input_data: str | dict[str, Any], - guardrail: Guardrail, + guardrail: BuiltInValidatorGuardrail, ) -> GuardrailValidationResult: """Validate input text using the provided 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. Must be an instance of ``BuiltInValidatorGuardrail``. Custom guardrails are not supported. + guardrail: A guardrail instance used for validation. Returns: BuiltInGuardrailValidationResult: The outcome of the guardrail evaluation, containing whether validation passed and the reason. - - Raises: - NotImplementedError: If a non-built-in guardrail is provided. """ - if isinstance(guardrail, BuiltInValidatorGuardrail): - parameters = [ - param.model_dump(by_alias=True) - for param in guardrail.validator_parameters - ] - payload = { - "validator": guardrail.validator_type, - "input": input_data if isinstance(input_data, str) else str(input_data), - "parameters": parameters, - } - spec = RequestSpec( - method="POST", - endpoint=Endpoint("/agentsruntime_/api/execution/guardrails/validate"), - json=payload, - ) - response = self.request( - spec.method, - url=spec.endpoint, - json=spec.json, - headers=spec.headers, - ) - return GuardrailValidationResult.model_validate(response.json()) - else: - raise NotImplementedError( - "Custom guardrail validation is not yet supported by the API." - ) + parameters = [ + param.model_dump(by_alias=True) for param in guardrail.validator_parameters + ] + payload = { + "validator": guardrail.validator_type, + "input": input_data if isinstance(input_data, str) else str(input_data), + "parameters": parameters, + } + spec = RequestSpec( + method="POST", + endpoint=Endpoint("/agentsruntime_/api/execution/guardrails/validate"), + json=payload, + ) + response = self.request( + spec.method, + url=spec.endpoint, + json=spec.json, + headers=spec.headers, + ) + return GuardrailValidationResult.model_validate(response.json()) diff --git a/src/uipath/platform/guardrails/guardrails.py b/src/uipath/platform/guardrails/guardrails.py index 9116cf85b..cfc1e295f 100644 --- a/src/uipath/platform/guardrails/guardrails.py +++ b/src/uipath/platform/guardrails/guardrails.py @@ -1,164 +1,10 @@ """Guardrails models for UiPath Platform.""" from enum import Enum -from typing import Annotated, Dict, List, Literal, Optional, Union +from typing import Annotated, Literal from pydantic import BaseModel, ConfigDict, Field - - -class GuardrailValidationResult(BaseModel): - """Result returned from validating input with a given guardrail. - - Attributes: - validation_passed: Indicates whether the input data passed the guardrail validation. - reason: Textual explanation describing why the validation passed or failed. - """ - - model_config = ConfigDict(populate_by_name=True) - - validation_passed: bool = Field( - alias="validation_passed", description="Whether the input passed validation." - ) - reason: str = Field( - alias="reason", description="Explanation for the validation result." - ) - - -class FieldSource(str, Enum): - """Field source enumeration.""" - - INPUT = "input" - OUTPUT = "output" - - -class ApplyTo(str, Enum): - """Apply to enumeration.""" - - INPUT = "input" - INPUT_AND_OUTPUT = "inputAndOutput" - OUTPUT = "output" - - -class FieldReference(BaseModel): - """Field reference model.""" - - path: str - source: FieldSource - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -class SelectorType(str, Enum): - """Selector type enumeration.""" - - ALL = "all" - SPECIFIC = "specific" - - -class AllFieldsSelector(BaseModel): - """All fields selector.""" - - selector_type: Literal["all"] = Field(alias="$selectorType") - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -class SpecificFieldsSelector(BaseModel): - """Specific fields selector.""" - - selector_type: Literal["specific"] = Field(alias="$selectorType") - fields: List[FieldReference] - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -FieldSelector = Annotated[ - Union[AllFieldsSelector, SpecificFieldsSelector], - Field(discriminator="selector_type"), -] - - -class RuleType(str, Enum): - """Rule type enumeration.""" - - BOOLEAN = "boolean" - NUMBER = "number" - UNIVERSAL = "always" - WORD = "word" - - -class WordOperator(str, Enum): - """Word operator enumeration.""" - - CONTAINS = "contains" - DOES_NOT_CONTAIN = "doesNotContain" - DOES_NOT_END_WITH = "doesNotEndWith" - DOES_NOT_EQUAL = "doesNotEqual" - DOES_NOT_START_WITH = "doesNotStartWith" - ENDS_WITH = "endsWith" - EQUALS = "equals" - IS_EMPTY = "isEmpty" - IS_NOT_EMPTY = "isNotEmpty" - STARTS_WITH = "startsWith" - - -class WordRule(BaseModel): - """Word rule model.""" - - rule_type: Literal["word"] = Field(alias="$ruleType") - field_selector: FieldSelector = Field(alias="fieldSelector") - operator: WordOperator - value: Optional[str] = None - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -class UniversalRule(BaseModel): - """Universal rule model.""" - - rule_type: Literal["always"] = Field(alias="$ruleType") - apply_to: ApplyTo = Field(alias="applyTo") - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -class NumberOperator(str, Enum): - """Number operator enumeration.""" - - DOES_NOT_EQUAL = "doesNotEqual" - EQUALS = "equals" - GREATER_THAN = "greaterThan" - GREATER_THAN_OR_EQUAL = "greaterThanOrEqual" - LESS_THAN = "lessThan" - LESS_THAN_OR_EQUAL = "lessThanOrEqual" - - -class NumberRule(BaseModel): - """Number rule model.""" - - rule_type: Literal["number"] = Field(alias="$ruleType") - field_selector: FieldSelector = Field(alias="fieldSelector") - operator: NumberOperator - value: float - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -class BooleanOperator(str, Enum): - """Boolean operator enumeration.""" - - EQUALS = "equals" - - -class BooleanRule(BaseModel): - """Boolean rule model.""" - - rule_type: Literal["boolean"] = Field(alias="$ruleType") - field_selector: FieldSelector = Field(alias="fieldSelector") - operator: BooleanOperator - value: bool - - model_config = ConfigDict(populate_by_name=True, extra="allow") +from uipath.core.guardrails import BaseGuardrail class EnumListParameterValue(BaseModel): @@ -166,7 +12,7 @@ class EnumListParameterValue(BaseModel): parameter_type: Literal["enum-list"] = Field(alias="$parameterType") id: str - value: List[str] + value: list[str] model_config = ConfigDict(populate_by_name=True, extra="allow") @@ -176,7 +22,7 @@ class MapEnumParameterValue(BaseModel): parameter_type: Literal["map-enum"] = Field(alias="$parameterType") id: str - value: Dict[str, float] + value: dict[str, float] model_config = ConfigDict(populate_by_name=True, extra="allow") @@ -192,73 +38,23 @@ class NumberParameterValue(BaseModel): ValidatorParameter = Annotated[ - Union[EnumListParameterValue, MapEnumParameterValue, NumberParameterValue], + EnumListParameterValue | MapEnumParameterValue | NumberParameterValue, Field(discriminator="parameter_type"), ] -Rule = Annotated[ - Union[WordRule, NumberRule, BooleanRule, UniversalRule], - Field(discriminator="rule_type"), -] - - -class GuardrailScope(str, Enum): - """Guardrail scope enumeration.""" - - AGENT = "Agent" - LLM = "Llm" - TOOL = "Tool" - - -class GuardrailSelector(BaseModel): - """Guardrail selector model.""" - - scopes: List[GuardrailScope] = Field(default=[GuardrailScope.TOOL]) - match_names: Optional[List[str]] = Field(None, alias="matchNames") - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -class BaseGuardrail(BaseModel): - """Base guardrail model.""" - - id: str - name: str - description: Optional[str] = None - enabled_for_evals: bool = Field(True, alias="enabledForEvals") - selector: GuardrailSelector - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - -class CustomGuardrail(BaseGuardrail): - """Custom guardrail model.""" - - guardrail_type: Literal["custom"] = Field(alias="$guardrailType") - rules: List[Rule] - - model_config = ConfigDict(populate_by_name=True, extra="allow") - - class BuiltInValidatorGuardrail(BaseGuardrail): """Built-in validator guardrail model.""" guardrail_type: Literal["builtInValidator"] = Field(alias="$guardrailType") validator_type: str = Field(alias="validatorType") - validator_parameters: List[ValidatorParameter] = Field( + validator_parameters: list[ValidatorParameter] = Field( default_factory=list, alias="validatorParameters" ) model_config = ConfigDict(populate_by_name=True, extra="allow") -Guardrail = Annotated[ - Union[CustomGuardrail, BuiltInValidatorGuardrail], - Field(discriminator="guardrail_type"), -] - - class GuardrailType(str, Enum): """Guardrail type enumeration.""" diff --git a/tests/agent/models/test_agent.py b/tests/agent/models/test_agent.py index b5a9289d4..4b5358852 100644 --- a/tests/agent/models/test_agent.py +++ b/tests/agent/models/test_agent.py @@ -20,11 +20,11 @@ AgentUnknownGuardrail, AgentUnknownResourceConfig, AgentUnknownToolResourceConfig, + AgentWordRule, ) from uipath.platform.guardrails import ( EnumListParameterValue, MapEnumParameterValue, - WordRule, ) @@ -687,7 +687,7 @@ def test_agent_config_loads_guardrails(self): # Check rule rule = agent_custom_guardrail.rules[0] - assert isinstance(rule, WordRule), ( + assert isinstance(rule, AgentWordRule), ( "Rule should be WordRule based on $ruleType='word'" ) assert rule.rule_type == "word" diff --git a/tests/sdk/services/test_guardrails_service.py b/tests/sdk/services/test_guardrails_service.py index 3c4e4feb1..54524ea93 100644 --- a/tests/sdk/services/test_guardrails_service.py +++ b/tests/sdk/services/test_guardrails_service.py @@ -1,15 +1,17 @@ import pytest from pytest_httpx import HTTPXMock +from uipath.core.guardrails import ( + GuardrailScope, + GuardrailSelector, +) from uipath.platform import UiPathApiConfig, UiPathExecutionContext from uipath.platform.guardrails import ( BuiltInValidatorGuardrail, EnumListParameterValue, - GuardrailScope, - GuardrailSelector, + GuardrailsService, MapEnumParameterValue, ) -from uipath.platform.guardrails._guardrails_service import GuardrailsService @pytest.fixture diff --git a/uv.lock b/uv.lock index 5f2e2deca..dd5f91feb 100644 --- a/uv.lock +++ b/uv.lock @@ -2477,7 +2477,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.2.29" +version = "2.2.30" source = { editable = "." } dependencies = [ { name = "click" }, @@ -2541,7 +2541,7 @@ requires-dist = [ { name = "rich", specifier = ">=14.2.0" }, { name = "tenacity", specifier = ">=9.0.0" }, { name = "truststore", specifier = ">=0.10.1" }, - { name = "uipath-core", specifier = ">=0.1.0,<0.2.0" }, + { name = "uipath-core", specifier = ">=0.1.3,<0.2.0" }, { name = "uipath-runtime", specifier = ">=0.2.5,<0.3.0" }, ] @@ -2574,16 +2574,16 @@ dev = [ [[package]] name = "uipath-core" -version = "0.1.1" +version = "0.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-instrumentation" }, { name = "opentelemetry-sdk" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/74/0aa4d000bb545936a23e5afaeb6a6ec7040973dafcc595c33e458c867a05/uipath_core-0.1.1.tar.gz", hash = "sha256:c02f742619b8491a5e31138cb9955dd1b4b97c06fd3b8e797bc14cb3754abce6", size = 88414, upload-time = "2025-12-09T12:47:00.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/94/b548c087afe9853f979b7827815cd4cc34d7a1584fd82fb05881b13415d4/uipath_core-0.1.3.tar.gz", hash = "sha256:8d25e5e372137fc8b59b0a904fd412ec249a6d30b49512cb9e7ece04aca3bca8", size = 94942, upload-time = "2025-12-12T07:43:35.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/7c/2a3e77cbaaf36be3193c4d7578fff5fdb6855a18e34aa9a5de33f6ccd0a2/uipath_core-0.1.1-py3-none-any.whl", hash = "sha256:9ea17343604c4cfc4427d637ecc82752fb39865c8df9ab255b446a5cabff1d0d", size = 23954, upload-time = "2025-12-09T12:46:58.772Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5a/e63602e2160e0fc7092a2cd55fc62beaefcdc79e313ef67913ef1032394c/uipath_core-0.1.3-py3-none-any.whl", hash = "sha256:6596b4a10d8236b5ddf6102f2a772eb09b0ef2457bd1a0da878fb7f718b86f45", size = 29483, upload-time = "2025-12-12T07:43:34.077Z" }, ] [[package]]