Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit d8bb43c

Browse files
Merge pull request #21 from UiPath/fix_guardrails_filtering
fix: filter guardrails based on the source
2 parents db612dc + 6adbbc9 commit d8bb43c

6 files changed

Lines changed: 446 additions & 32 deletions

File tree

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-core"
3-
version = "0.1.4"
3+
version = "0.1.5"
44
description = "UiPath Core abstractions"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath/core/guardrails/_deterministic_guardrails_service.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
evaluate_word_rule,
1111
)
1212
from .guardrails import (
13+
AllFieldsSelector,
14+
ApplyTo,
1315
BooleanRule,
1416
DeterministicGuardrail,
17+
FieldSource,
1518
GuardrailValidationResult,
1619
NumberRule,
20+
SpecificFieldsSelector,
1721
UniversalRule,
1822
WordRule,
1923
)
@@ -27,6 +31,16 @@ def evaluate_pre_deterministic_guardrail(
2731
guardrail: DeterministicGuardrail,
2832
) -> GuardrailValidationResult:
2933
"""Evaluate deterministic guardrail rules against input data (pre-execution)."""
34+
# Check if guardrail contains any output-dependent rules
35+
has_output_rule = self._has_output_dependent_rule(guardrail, [ApplyTo.OUTPUT])
36+
37+
# If guardrail has output-dependent rules, skip evaluation in pre-execution
38+
# Output rules will be evaluated during post-execution
39+
if has_output_rule:
40+
return GuardrailValidationResult(
41+
validation_passed=True,
42+
reason="Guardrail contains output-dependent rules that will be evaluated during post-execution",
43+
)
3044
return self._evaluate_deterministic_guardrail(
3145
input_data=input_data,
3246
output_data={},
@@ -41,12 +55,61 @@ def evaluate_post_deterministic_guardrail(
4155
guardrail: DeterministicGuardrail,
4256
) -> GuardrailValidationResult:
4357
"""Evaluate deterministic guardrail rules against input and output data."""
58+
# Check if guardrail contains any output-dependent rules
59+
has_output_rule = self._has_output_dependent_rule(
60+
guardrail, [ApplyTo.OUTPUT, ApplyTo.INPUT_AND_OUTPUT]
61+
)
62+
63+
# If guardrail has no output-dependent rules, skip post-execution evaluation
64+
# Only input rules exist and they should have been evaluated during pre-execution
65+
if not has_output_rule:
66+
return GuardrailValidationResult(
67+
validation_passed=True,
68+
reason="Guardrail contains only input-dependent rules that were evaluated during pre-execution",
69+
)
70+
4471
return self._evaluate_deterministic_guardrail(
4572
input_data=input_data,
4673
output_data=output_data,
4774
guardrail=guardrail,
4875
)
4976

77+
@staticmethod
78+
def _has_output_dependent_rule(
79+
guardrail: DeterministicGuardrail,
80+
universal_rules_apply_to_values: list[ApplyTo],
81+
) -> bool:
82+
"""Check if at least one rule EXCLUSIVELY requires output data.
83+
84+
Args:
85+
guardrail: The guardrail to check
86+
universal_rules_apply_to_values: List of ApplyTo values to consider as output-dependent for UniversalRules.
87+
88+
Returns:
89+
True if at least one rule exclusively depends on output data, False otherwise.
90+
"""
91+
for rule in guardrail.rules:
92+
# UniversalRule: only return True if it applies to values in universal_rules_apply_to_values
93+
if isinstance(rule, UniversalRule):
94+
if rule.apply_to in universal_rules_apply_to_values:
95+
return True
96+
# Rules with field_selector
97+
elif isinstance(rule, (WordRule, NumberRule, BooleanRule)):
98+
field_selector = rule.field_selector
99+
# AllFieldsSelector applies to both input and output, not exclusively output
100+
# SpecificFieldsSelector: only return True if at least one field has OUTPUT source
101+
if isinstance(field_selector, SpecificFieldsSelector):
102+
if field_selector.fields and any(
103+
field.source == FieldSource.OUTPUT
104+
for field in field_selector.fields
105+
):
106+
return True
107+
elif isinstance(field_selector, AllFieldsSelector):
108+
if FieldSource.OUTPUT in field_selector.sources:
109+
return True
110+
111+
return False
112+
50113
@staticmethod
51114
def _evaluate_deterministic_guardrail(
52115
input_data: dict[str, Any],

src/uipath/core/guardrails/_evaluators.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,23 +120,25 @@ def get_fields_from_selector(
120120
fields: list[tuple[Any, FieldReference]] = []
121121

122122
if isinstance(field_selector, AllFieldsSelector):
123-
# For "all" selector, we need to collect all fields from both input and output
123+
# For "all" selector, we need to collect all fields from the specified sources
124124
# This is a simplified implementation - in practice, you might want to
125125
# recursively collect all nested fields
126-
for key, value in input_data.items():
127-
fields.append(
128-
(
129-
value,
130-
FieldReference(path=key, source=FieldSource.INPUT),
126+
if FieldSource.INPUT in field_selector.sources:
127+
for key, value in input_data.items():
128+
fields.append(
129+
(
130+
value,
131+
FieldReference(path=key, source=FieldSource.INPUT),
132+
)
131133
)
132-
)
133-
for key, value in output_data.items():
134-
fields.append(
135-
(
136-
value,
137-
FieldReference(path=key, source=FieldSource.OUTPUT),
134+
if FieldSource.OUTPUT in field_selector.sources:
135+
for key, value in output_data.items():
136+
fields.append(
137+
(
138+
value,
139+
FieldReference(path=key, source=FieldSource.OUTPUT),
140+
)
138141
)
139-
)
140142
elif isinstance(field_selector, SpecificFieldsSelector):
141143
# For specific fields, extract values based on field references
142144
for field_ref in field_selector.fields:

src/uipath/core/guardrails/guardrails.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class AllFieldsSelector(BaseModel):
5959
"""All fields selector."""
6060

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

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

0 commit comments

Comments
 (0)