Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-core"
version = "0.1.4"
version = "0.1.5"
description = "UiPath Core abstractions"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
63 changes: 63 additions & 0 deletions src/uipath/core/guardrails/_deterministic_guardrails_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@
evaluate_word_rule,
)
from .guardrails import (
AllFieldsSelector,
ApplyTo,
BooleanRule,
DeterministicGuardrail,
FieldSource,
GuardrailValidationResult,
NumberRule,
SpecificFieldsSelector,
UniversalRule,
WordRule,
)
Expand All @@ -27,6 +31,16 @@ def evaluate_pre_deterministic_guardrail(
guardrail: DeterministicGuardrail,
) -> GuardrailValidationResult:
"""Evaluate deterministic guardrail rules against input data (pre-execution)."""
# Check if guardrail contains any output-dependent rules
has_output_rule = self._has_output_dependent_rule(guardrail, [ApplyTo.OUTPUT])

# If guardrail has output-dependent rules, skip evaluation in pre-execution
# Output rules will be evaluated during post-execution
if has_output_rule:
return GuardrailValidationResult(
validation_passed=True,
reason="Guardrail contains output-dependent rules that will be evaluated during post-execution",
)
return self._evaluate_deterministic_guardrail(
input_data=input_data,
output_data={},
Expand All @@ -41,12 +55,61 @@ def evaluate_post_deterministic_guardrail(
guardrail: DeterministicGuardrail,
) -> GuardrailValidationResult:
"""Evaluate deterministic guardrail rules against input and output data."""
# Check if guardrail contains any output-dependent rules
has_output_rule = self._has_output_dependent_rule(
guardrail, [ApplyTo.OUTPUT, ApplyTo.INPUT_AND_OUTPUT]
)

# If guardrail has no output-dependent rules, skip post-execution evaluation
# Only input rules exist and they should have been evaluated during pre-execution
if not has_output_rule:
return GuardrailValidationResult(
validation_passed=True,
reason="Guardrail contains only input-dependent rules that were evaluated during pre-execution",
)

return self._evaluate_deterministic_guardrail(
input_data=input_data,
output_data=output_data,
guardrail=guardrail,
)

@staticmethod
def _has_output_dependent_rule(
guardrail: DeterministicGuardrail,
universal_rules_apply_to_values: list[ApplyTo],
) -> bool:
"""Check if at least one rule EXCLUSIVELY requires output data.

Args:
guardrail: The guardrail to check
universal_rules_apply_to_values: List of ApplyTo values to consider as output-dependent for UniversalRules.

Returns:
True if at least one rule exclusively depends on output data, False otherwise.
"""
for rule in guardrail.rules:
# UniversalRule: only return True if it applies to values in universal_rules_apply_to_values
if isinstance(rule, UniversalRule):
if rule.apply_to in universal_rules_apply_to_values:
return True
# Rules with field_selector
elif isinstance(rule, (WordRule, NumberRule, BooleanRule)):
field_selector = rule.field_selector
# AllFieldsSelector applies to both input and output, not exclusively output
# SpecificFieldsSelector: only return True if at least one field has OUTPUT source
if isinstance(field_selector, SpecificFieldsSelector):
if field_selector.fields and any(
field.source == FieldSource.OUTPUT
for field in field_selector.fields
):
return True
elif isinstance(field_selector, AllFieldsSelector):
if FieldSource.OUTPUT in field_selector.sources:
return True

return False

@staticmethod
def _evaluate_deterministic_guardrail(
input_data: dict[str, Any],
Expand Down
28 changes: 15 additions & 13 deletions src/uipath/core/guardrails/_evaluators.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,23 +120,25 @@ def get_fields_from_selector(
fields: list[tuple[Any, FieldReference]] = []

if isinstance(field_selector, AllFieldsSelector):
# For "all" selector, we need to collect all fields from both input and output
# For "all" selector, we need to collect all fields from the specified sources
# This is a simplified implementation - in practice, you might want to
# recursively collect all nested fields
for key, value in input_data.items():
fields.append(
(
value,
FieldReference(path=key, source=FieldSource.INPUT),
if FieldSource.INPUT in field_selector.sources:
for key, value in input_data.items():
fields.append(
(
value,
FieldReference(path=key, source=FieldSource.INPUT),
)
)
)
for key, value in output_data.items():
fields.append(
(
value,
FieldReference(path=key, source=FieldSource.OUTPUT),
if FieldSource.OUTPUT in field_selector.sources:
for key, value in output_data.items():
fields.append(
(
value,
FieldReference(path=key, source=FieldSource.OUTPUT),
)
)
)
elif isinstance(field_selector, SpecificFieldsSelector):
# For specific fields, extract values based on field references
for field_ref in field_selector.fields:
Expand Down
1 change: 1 addition & 0 deletions src/uipath/core/guardrails/guardrails.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class AllFieldsSelector(BaseModel):
"""All fields selector."""

selector_type: Literal["all"] = Field(alias="$selectorType")
sources: list[FieldSource]

model_config = ConfigDict(populate_by_name=True, extra="allow")

Expand Down
Loading