diff --git a/packages/uipath-platform/pyproject.toml b/packages/uipath-platform/pyproject.toml index a62398e5b..a0d332607 100644 --- a/packages/uipath-platform/pyproject.toml +++ b/packages/uipath-platform/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-platform" -version = "0.1.64" +version = "0.1.65" 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/decorators/__init__.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py index e8d692164..fbb09a16d 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/__init__.py @@ -24,6 +24,7 @@ GuardrailValidatorBase, HarmfulContentValidator, IntellectualPropertyValidator, + LLMJudgeValidator, PIIValidator, PromptInjectionValidator, RuleFunction, @@ -39,6 +40,7 @@ "CustomGuardrailValidator", "HarmfulContentValidator", "IntellectualPropertyValidator", + "LLMJudgeValidator", "PIIValidator", "PromptInjectionValidator", "UserPromptAttacksValidator", 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 bbcf29039..0a332eb11 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 @@ -8,6 +8,7 @@ from .custom import CustomValidator, RuleFunction from .harmful_content import HarmfulContentValidator from .intellectual_property import IntellectualPropertyValidator +from .llm_judge import LLMJudgeValidator from .pii import PIIValidator from .prompt_injection import PromptInjectionValidator from .user_prompt_attacks import UserPromptAttacksValidator @@ -18,6 +19,7 @@ "CustomGuardrailValidator", "HarmfulContentValidator", "IntellectualPropertyValidator", + "LLMJudgeValidator", "PIIValidator", "PromptInjectionValidator", "UserPromptAttacksValidator", diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/llm_judge.py b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/llm_judge.py new file mode 100644 index 000000000..a5caeb08d --- /dev/null +++ b/packages/uipath-platform/src/uipath/platform/guardrails/decorators/validators/llm_judge.py @@ -0,0 +1,87 @@ +"""LLM-as-judge guardrail validator.""" + +from uuid import uuid4 + +from uipath.platform.guardrails.guardrails import ( + BuiltInValidatorGuardrail, + NumberParameterValue, + StringParameterValue, +) + +from ._base import BuiltInGuardrailValidator + + +class LLMJudgeValidator(BuiltInGuardrailValidator): + """Validate data with an LLM acting as judge against free-form criteria. + + Delegates to the UiPath LLM-as-judge guardrail backend. Supported at all + stages — provide judging criteria written from the perspective of the + data being evaluated (input at PRE, output at POST). + + Args: + criteria: Natural-language description of what the judge should check + for. The judge passes when the data satisfies the criteria. + model: LLM model identifier to use for judging. Defaults to ``"gpt-4o-mini"``. + threshold: Score threshold in [0.0, 1.0] above which the judge + considers the data compliant. Defaults to ``0.5``. + + Raises: + ValueError: If *criteria* is empty or *threshold* is outside [0.0, 1.0]. + """ + + def __init__( + self, + criteria: str, + model: str = "gpt-4o-mini", + threshold: float = 0.5, + ) -> None: + """Initialize LLMJudgeValidator with criteria, model, and threshold.""" + if not criteria or not criteria.strip(): + raise ValueError("criteria must be a non-empty string") + if not 0.0 <= threshold <= 1.0: + raise ValueError(f"threshold must be between 0.0 and 1.0, got {threshold}") + self.criteria = criteria + self.model = model + self.threshold = threshold + + def get_built_in_guardrail( + self, + name: str, + description: str | None, + enabled_for_evals: bool, + ) -> BuiltInValidatorGuardrail: + """Build an LLM-as-judge :class:`BuiltInValidatorGuardrail`. + + Args: + name: Name for the guardrail. + description: Optional description. + enabled_for_evals: Whether active in evaluation scenarios. + + Returns: + Configured :class:`BuiltInValidatorGuardrail` for LLM-as-judge. + """ + return BuiltInValidatorGuardrail( + id=str(uuid4()), + name=name, + description=description or f"LLM-as-judge ({self.model}): {self.criteria}", + enabled_for_evals=enabled_for_evals, + guardrail_type="builtInValidator", + validator_type="llm_judge", + validator_parameters=[ + StringParameterValue( + parameter_type="string", + id="criteria", + value=self.criteria, + ), + StringParameterValue( + parameter_type="string", + id="model", + value=self.model, + ), + NumberParameterValue( + parameter_type="number", + id="threshold", + value=self.threshold, + ), + ], + ) diff --git a/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py b/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py index cfc1e295f..0bb95ad5f 100644 --- a/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py +++ b/packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py @@ -37,8 +37,21 @@ class NumberParameterValue(BaseModel): model_config = ConfigDict(populate_by_name=True, extra="allow") +class StringParameterValue(BaseModel): + """String parameter value.""" + + parameter_type: Literal["string"] = Field(alias="$parameterType") + id: str + value: str + + model_config = ConfigDict(populate_by_name=True, extra="allow") + + ValidatorParameter = Annotated[ - EnumListParameterValue | MapEnumParameterValue | NumberParameterValue, + EnumListParameterValue + | MapEnumParameterValue + | NumberParameterValue + | StringParameterValue, Field(discriminator="parameter_type"), ] diff --git a/packages/uipath-platform/tests/services/test_guardrails_decorators.py b/packages/uipath-platform/tests/services/test_guardrails_decorators.py index e578cba84..e96ac7689 100644 --- a/packages/uipath-platform/tests/services/test_guardrails_decorators.py +++ b/packages/uipath-platform/tests/services/test_guardrails_decorators.py @@ -26,6 +26,7 @@ GuardrailBlockException, GuardrailExclude, GuardrailExecutionStage, + LLMJudgeValidator, LogAction, LoggingSeverityLevel, PIIDetectionEntity, @@ -310,6 +311,68 @@ def test_selector_is_none(self): assert g.selector is None +# --------------------------------------------------------------------------- +# 5b. LLMJudgeValidator — criteria/model/threshold validation, all stages +# --------------------------------------------------------------------------- + + +class TestLLMJudgeValidator: + def test_empty_criteria_raises(self): + with pytest.raises(ValueError, match="criteria"): + LLMJudgeValidator(criteria="") + + def test_whitespace_only_criteria_raises(self): + with pytest.raises(ValueError, match="criteria"): + LLMJudgeValidator(criteria=" ") + + def test_threshold_below_zero_raises(self): + with pytest.raises(ValueError, match="threshold"): + LLMJudgeValidator(criteria="be concise", threshold=-0.1) + + def test_threshold_above_one_raises(self): + with pytest.raises(ValueError, match="threshold"): + LLMJudgeValidator(criteria="be concise", threshold=1.5) + + def test_no_scope_restriction(self): + v = LLMJudgeValidator(criteria="be polite") + v.validate_stage(GuardrailExecutionStage.PRE) + v.validate_stage(GuardrailExecutionStage.POST) + + def test_builds_llm_judge_guardrail_with_parameters(self): + v = LLMJudgeValidator( + criteria="The output must be a valid JSON object.", + model="gpt-4o", + threshold=0.8, + ) + g = v.get_built_in_guardrail("Judge", None, True) + assert g.validator_type == "llm_judge" + param_by_id = {p.id: p for p in g.validator_parameters} + assert ( + param_by_id["criteria"].value == "The output must be a valid JSON object." + ) + assert param_by_id["model"].value == "gpt-4o" + assert param_by_id["threshold"].value == 0.8 + + def test_default_model_and_threshold(self): + v = LLMJudgeValidator(criteria="be polite") + g = v.get_built_in_guardrail("Judge", None, True) + param_by_id = {p.id: p for p in g.validator_parameters} + assert param_by_id["model"].value == "gpt-4o-mini" + assert param_by_id["threshold"].value == 0.5 + + def test_default_description_includes_model_and_criteria(self): + v = LLMJudgeValidator(criteria="be polite", model="gpt-4o") + g = v.get_built_in_guardrail("Judge", None, True) + assert g.description is not None + assert "gpt-4o" in g.description + assert "be polite" in g.description + + def test_selector_is_none(self): + v = LLMJudgeValidator(criteria="be polite") + g = v.get_built_in_guardrail("Judge", None, True) + assert g.selector is None + + # --------------------------------------------------------------------------- # 6. CustomValidator — rule routing and error handling # --------------------------------------------------------------------------- diff --git a/packages/uipath-platform/uv.lock b/packages/uipath-platform/uv.lock index 3d9ac6c79..689662d4d 100644 --- a/packages/uipath-platform/uv.lock +++ b/packages/uipath-platform/uv.lock @@ -1095,7 +1095,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.64" +version = "0.1.65" source = { editable = "." } dependencies = [ { name = "httpx" }, diff --git a/packages/uipath/uv.lock b/packages/uipath/uv.lock index 62ecc13a0..1012cd6be 100644 --- a/packages/uipath/uv.lock +++ b/packages/uipath/uv.lock @@ -2691,7 +2691,7 @@ dev = [ [[package]] name = "uipath-platform" -version = "0.1.64" +version = "0.1.65" source = { editable = "../uipath-platform" } dependencies = [ { name = "httpx" },