uipath-core 0.1.3__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {uipath_core-0.1.3 → uipath_core-0.1.4}/PKG-INFO +1 -1
- {uipath_core-0.1.3 → uipath_core-0.1.4}/pyproject.toml +1 -1
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/guardrails/_evaluators.py +93 -17
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/guardrails/guardrails.py +13 -4
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/guardrails/test_deterministic_guardrails_service.py +86 -32
- {uipath_core-0.1.3 → uipath_core-0.1.4}/uv.lock +1 -1
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.cursorrules +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.editorconfig +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.gitattributes +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.github/workflows/cd.yml +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.github/workflows/ci.yml +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.github/workflows/commitlint.yml +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.github/workflows/lint.yml +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.github/workflows/publish-dev.yml +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.github/workflows/test.yml +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.gitignore +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.pre-commit-config.yaml +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.python-version +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.vscode/extensions.json +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.vscode/launch.json +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/.vscode/settings.json +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/CONTRIBUTING.md +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/LICENSE +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/README.md +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/justfile +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/__init__.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/__init__.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/async_stream.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/citation.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/content.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/conversation.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/error.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/event.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/exchange.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/interrupt.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/message.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/meta.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/chat/tool.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/errors/__init__.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/errors/errors.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/guardrails/__init__.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/guardrails/_deterministic_guardrails_service.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/py.typed +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/tracing/__init__.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/tracing/_utils.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/tracing/decorators.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/tracing/exporters.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/tracing/processors.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/tracing/span_utils.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/src/uipath/core/tracing/trace_manager.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/__init__.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/conftest.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/tracing/test_external_integration.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/tracing/test_serialization.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/tracing/test_span_nesting.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/tracing/test_span_registry.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/tracing/test_trace_manager.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/tracing/test_traced.py +0 -0
- {uipath_core-0.1.3 → uipath_core-0.1.4}/tests/tracing/test_tracing_utils.py +0 -0
|
@@ -4,8 +4,9 @@ This module provides functions for evaluating different types of guardrail rules
|
|
|
4
4
|
against input and output data.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import inspect
|
|
7
8
|
from enum import IntEnum
|
|
8
|
-
from typing import Any
|
|
9
|
+
from typing import Any, Callable
|
|
9
10
|
|
|
10
11
|
from .guardrails import (
|
|
11
12
|
AllFieldsSelector,
|
|
@@ -163,7 +164,7 @@ def format_guardrail_error_message(
|
|
|
163
164
|
) -> str:
|
|
164
165
|
"""Format a guardrail error message following the standard pattern."""
|
|
165
166
|
source = "Input" if field_ref.source == FieldSource.INPUT else "Output"
|
|
166
|
-
message = f"{source} data didn't match the guardrail condition: [{field_ref.path}] {operator}"
|
|
167
|
+
message = f"{source} data didn't match the guardrail condition: [{field_ref.path}] comparing function [{operator}]"
|
|
167
168
|
if expected_value and expected_value.strip():
|
|
168
169
|
message += f" [{expected_value.strip()}]"
|
|
169
170
|
return message
|
|
@@ -187,16 +188,18 @@ def evaluate_word_rule(
|
|
|
187
188
|
field_str = field_value
|
|
188
189
|
|
|
189
190
|
# Use the custom function to evaluate the rule
|
|
191
|
+
# If detects_violation returns True, it means the rule was violated (validation fails)
|
|
190
192
|
try:
|
|
191
|
-
|
|
193
|
+
violation_detected = rule.detects_violation(field_str)
|
|
192
194
|
except Exception:
|
|
193
195
|
# If function raises an exception, treat as failure
|
|
194
|
-
|
|
196
|
+
violation_detected = True
|
|
195
197
|
|
|
196
|
-
if
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
if violation_detected:
|
|
199
|
+
operator = (
|
|
200
|
+
_humanize_guardrail_func(rule.detects_violation) or "violation check"
|
|
199
201
|
)
|
|
202
|
+
reason = format_guardrail_error_message(field_ref, operator, None)
|
|
200
203
|
return False, reason
|
|
201
204
|
|
|
202
205
|
return True, "All word rule validations passed"
|
|
@@ -221,16 +224,18 @@ def evaluate_number_rule(
|
|
|
221
224
|
field_num = float(field_value)
|
|
222
225
|
|
|
223
226
|
# Use the custom function to evaluate the rule
|
|
227
|
+
# If detects_violation returns True, it means the rule was violated (validation fails)
|
|
224
228
|
try:
|
|
225
|
-
|
|
229
|
+
violation_detected = rule.detects_violation(field_num)
|
|
226
230
|
except Exception:
|
|
227
231
|
# If function raises an exception, treat as failure
|
|
228
|
-
|
|
232
|
+
violation_detected = True
|
|
229
233
|
|
|
230
|
-
if
|
|
231
|
-
|
|
232
|
-
|
|
234
|
+
if violation_detected:
|
|
235
|
+
operator = (
|
|
236
|
+
_humanize_guardrail_func(rule.detects_violation) or "violation check"
|
|
233
237
|
)
|
|
238
|
+
reason = format_guardrail_error_message(field_ref, operator, None)
|
|
234
239
|
return False, reason
|
|
235
240
|
|
|
236
241
|
return True, "All number rule validations passed"
|
|
@@ -256,16 +261,18 @@ def evaluate_boolean_rule(
|
|
|
256
261
|
field_bool = field_value
|
|
257
262
|
|
|
258
263
|
# Use the custom function to evaluate the rule
|
|
264
|
+
# If detects_violation returns True, it means the rule was violated (validation fails)
|
|
259
265
|
try:
|
|
260
|
-
|
|
266
|
+
violation_detected = rule.detects_violation(field_bool)
|
|
261
267
|
except Exception:
|
|
262
268
|
# If function raises an exception, treat as failure
|
|
263
|
-
|
|
269
|
+
violation_detected = True
|
|
264
270
|
|
|
265
|
-
if
|
|
266
|
-
|
|
267
|
-
|
|
271
|
+
if violation_detected:
|
|
272
|
+
operator = (
|
|
273
|
+
_humanize_guardrail_func(rule.detects_violation) or "violation check"
|
|
268
274
|
)
|
|
275
|
+
reason = format_guardrail_error_message(field_ref, operator, None)
|
|
269
276
|
return False, reason
|
|
270
277
|
|
|
271
278
|
return True, "All boolean rule validations passed"
|
|
@@ -307,3 +314,72 @@ def evaluate_universal_rule(
|
|
|
307
314
|
return False, "Universal rule validation triggered (input and output)"
|
|
308
315
|
else:
|
|
309
316
|
return False, f"Unknown apply_to value: {rule.apply_to}"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _humanize_guardrail_func(func: Callable[..., Any] | str | None) -> str | None:
|
|
320
|
+
"""Build a user-friendly description of a guardrail predicate.
|
|
321
|
+
|
|
322
|
+
Deterministic guardrails store Python callables (often lambdas) to evaluate
|
|
323
|
+
conditions. For diagnostics, it's useful to include a readable hint about the
|
|
324
|
+
predicate that failed.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
func: A Python callable used as a predicate, or a pre-rendered string
|
|
328
|
+
description (for example, ``"s:str -> bool: contains 'test'"``).
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
A human-readable description, or ``None`` if one cannot be produced.
|
|
332
|
+
"""
|
|
333
|
+
if func is None:
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
if isinstance(func, str):
|
|
337
|
+
rendered = func.strip()
|
|
338
|
+
return rendered or None
|
|
339
|
+
|
|
340
|
+
name = getattr(func, "__name__", None)
|
|
341
|
+
if name and name != "<lambda>":
|
|
342
|
+
return name
|
|
343
|
+
|
|
344
|
+
# Best-effort extraction for lambdas / callables.
|
|
345
|
+
try:
|
|
346
|
+
sig = str(inspect.signature(func))
|
|
347
|
+
except (TypeError, ValueError):
|
|
348
|
+
sig = ""
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
source_lines = inspect.getsourcelines(func)
|
|
352
|
+
source = "".join(source_lines[0]).strip()
|
|
353
|
+
# Collapse whitespace to keep the message compact.
|
|
354
|
+
source = " ".join(source.split())
|
|
355
|
+
|
|
356
|
+
# Remove "detects_violation=lambda" prefix if present
|
|
357
|
+
# Pattern: "detects_violation=lambda s: condition" -> "condition"
|
|
358
|
+
if "detects_violation=lambda" in source:
|
|
359
|
+
# Find the lambda part
|
|
360
|
+
lambda_start = source.find("detects_violation=lambda")
|
|
361
|
+
if lambda_start != -1:
|
|
362
|
+
# Get everything after "detects_violation=lambda"
|
|
363
|
+
lambda_part = source[
|
|
364
|
+
lambda_start + len("detects_violation=lambda") :
|
|
365
|
+
].strip()
|
|
366
|
+
# Find the colon that separates param from body
|
|
367
|
+
colon_idx = lambda_part.find(":")
|
|
368
|
+
if colon_idx != -1:
|
|
369
|
+
# Extract just the body (condition)
|
|
370
|
+
body = lambda_part[colon_idx + 1 :].strip()
|
|
371
|
+
# Remove trailing comma if present
|
|
372
|
+
body = body.rstrip(",").strip()
|
|
373
|
+
source = body
|
|
374
|
+
except (OSError, TypeError):
|
|
375
|
+
source = ""
|
|
376
|
+
|
|
377
|
+
if source and sig:
|
|
378
|
+
return f"{sig}: {source}"
|
|
379
|
+
if source:
|
|
380
|
+
return source
|
|
381
|
+
if sig:
|
|
382
|
+
return sig
|
|
383
|
+
|
|
384
|
+
rendered = repr(func).strip()
|
|
385
|
+
return rendered or None
|
|
@@ -92,7 +92,10 @@ class WordRule(BaseModel):
|
|
|
92
92
|
|
|
93
93
|
rule_type: Literal["word"] = Field(alias="$ruleType")
|
|
94
94
|
field_selector: FieldSelector = Field(alias="fieldSelector")
|
|
95
|
-
|
|
95
|
+
detects_violation: Callable[[str], bool] = Field(
|
|
96
|
+
exclude=True,
|
|
97
|
+
description="Function that returns True if the string violates the rule (validation should fail).",
|
|
98
|
+
)
|
|
96
99
|
|
|
97
100
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
98
101
|
|
|
@@ -111,7 +114,10 @@ class NumberRule(BaseModel):
|
|
|
111
114
|
|
|
112
115
|
rule_type: Literal["number"] = Field(alias="$ruleType")
|
|
113
116
|
field_selector: FieldSelector = Field(alias="fieldSelector")
|
|
114
|
-
|
|
117
|
+
detects_violation: Callable[[float], bool] = Field(
|
|
118
|
+
exclude=True,
|
|
119
|
+
description="Function that returns True if the number violates the rule (validation should fail).",
|
|
120
|
+
)
|
|
115
121
|
|
|
116
122
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
117
123
|
|
|
@@ -121,7 +127,10 @@ class BooleanRule(BaseModel):
|
|
|
121
127
|
|
|
122
128
|
rule_type: Literal["boolean"] = Field(alias="$ruleType")
|
|
123
129
|
field_selector: FieldSelector = Field(alias="fieldSelector")
|
|
124
|
-
|
|
130
|
+
detects_violation: Callable[[bool], bool] = Field(
|
|
131
|
+
exclude=True,
|
|
132
|
+
description="Function that returns True if the boolean violates the rule (validation should fail).",
|
|
133
|
+
)
|
|
125
134
|
|
|
126
135
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
127
136
|
|
|
@@ -164,7 +173,7 @@ class BaseGuardrail(BaseModel):
|
|
|
164
173
|
class DeterministicGuardrail(BaseGuardrail):
|
|
165
174
|
"""Deterministic guardrail model."""
|
|
166
175
|
|
|
167
|
-
guardrail_type: Literal["custom"] = Field(alias="
|
|
176
|
+
guardrail_type: Literal["custom"] = Field(alias="$guardrailType")
|
|
168
177
|
rules: list[Rule]
|
|
169
178
|
|
|
170
179
|
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
{uipath_core-0.1.3 → uipath_core-0.1.4}/tests/guardrails/test_deterministic_guardrails_service.py
RENAMED
|
@@ -50,7 +50,7 @@ class TestDeterministicGuardrailsService:
|
|
|
50
50
|
selector_type="specific",
|
|
51
51
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
52
52
|
),
|
|
53
|
-
|
|
53
|
+
detects_violation=lambda n: n < 21.0,
|
|
54
54
|
),
|
|
55
55
|
BooleanRule(
|
|
56
56
|
rule_type="boolean",
|
|
@@ -60,7 +60,7 @@ class TestDeterministicGuardrailsService:
|
|
|
60
60
|
FieldReference(path="isActive", source=FieldSource.INPUT)
|
|
61
61
|
],
|
|
62
62
|
),
|
|
63
|
-
|
|
63
|
+
detects_violation=lambda b: b is not True,
|
|
64
64
|
),
|
|
65
65
|
],
|
|
66
66
|
)
|
|
@@ -80,7 +80,7 @@ class TestDeterministicGuardrailsService:
|
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
assert result.validation_passed is True
|
|
83
|
-
assert "All deterministic guardrail rules passed"
|
|
83
|
+
assert result.reason == "All deterministic guardrail rules passed"
|
|
84
84
|
|
|
85
85
|
def test_evaluate_post_deterministic_guardrail_validation_failed_age(
|
|
86
86
|
self,
|
|
@@ -103,7 +103,7 @@ class TestDeterministicGuardrailsService:
|
|
|
103
103
|
selector_type="specific",
|
|
104
104
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
105
105
|
),
|
|
106
|
-
|
|
106
|
+
detects_violation=lambda n: n < 21.0,
|
|
107
107
|
),
|
|
108
108
|
BooleanRule(
|
|
109
109
|
rule_type="boolean",
|
|
@@ -113,7 +113,7 @@ class TestDeterministicGuardrailsService:
|
|
|
113
113
|
FieldReference(path="isActive", source=FieldSource.INPUT)
|
|
114
114
|
],
|
|
115
115
|
),
|
|
116
|
-
|
|
116
|
+
detects_violation=lambda b: b is not True,
|
|
117
117
|
),
|
|
118
118
|
],
|
|
119
119
|
)
|
|
@@ -133,7 +133,10 @@ class TestDeterministicGuardrailsService:
|
|
|
133
133
|
)
|
|
134
134
|
|
|
135
135
|
assert result.validation_passed is False
|
|
136
|
-
assert
|
|
136
|
+
assert (
|
|
137
|
+
result.reason
|
|
138
|
+
== "Input data didn't match the guardrail condition: [age] comparing function [(n): n < 21.0]"
|
|
139
|
+
)
|
|
137
140
|
|
|
138
141
|
def test_evaluate_post_deterministic_guardrail_validation_failed_is_active(
|
|
139
142
|
self,
|
|
@@ -156,7 +159,7 @@ class TestDeterministicGuardrailsService:
|
|
|
156
159
|
selector_type="specific",
|
|
157
160
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
158
161
|
),
|
|
159
|
-
|
|
162
|
+
detects_violation=lambda n: n < 21.0,
|
|
160
163
|
),
|
|
161
164
|
BooleanRule(
|
|
162
165
|
rule_type="boolean",
|
|
@@ -166,7 +169,7 @@ class TestDeterministicGuardrailsService:
|
|
|
166
169
|
FieldReference(path="isActive", source=FieldSource.INPUT)
|
|
167
170
|
],
|
|
168
171
|
),
|
|
169
|
-
|
|
172
|
+
detects_violation=lambda b: b is not True,
|
|
170
173
|
),
|
|
171
174
|
],
|
|
172
175
|
)
|
|
@@ -186,8 +189,10 @@ class TestDeterministicGuardrailsService:
|
|
|
186
189
|
)
|
|
187
190
|
|
|
188
191
|
assert result.validation_passed is False
|
|
189
|
-
assert
|
|
190
|
-
|
|
192
|
+
assert (
|
|
193
|
+
result.reason
|
|
194
|
+
== "Input data didn't match the guardrail condition: [isActive] comparing function [(b): b is not True]"
|
|
195
|
+
)
|
|
191
196
|
|
|
192
197
|
def test_evaluate_post_deterministic_guardrail_matches_regex_positive(
|
|
193
198
|
self,
|
|
@@ -212,7 +217,7 @@ class TestDeterministicGuardrailsService:
|
|
|
212
217
|
FieldReference(path="userName", source=FieldSource.INPUT)
|
|
213
218
|
],
|
|
214
219
|
),
|
|
215
|
-
|
|
220
|
+
detects_violation=lambda s: not bool(re.search(".*te.*3.*", s)),
|
|
216
221
|
),
|
|
217
222
|
],
|
|
218
223
|
)
|
|
@@ -230,7 +235,7 @@ class TestDeterministicGuardrailsService:
|
|
|
230
235
|
)
|
|
231
236
|
|
|
232
237
|
assert result.validation_passed is True
|
|
233
|
-
assert "All deterministic guardrail rules passed"
|
|
238
|
+
assert result.reason == "All deterministic guardrail rules passed"
|
|
234
239
|
|
|
235
240
|
def test_evaluate_post_deterministic_guardrail_matches_regex_negative(
|
|
236
241
|
self,
|
|
@@ -255,7 +260,7 @@ class TestDeterministicGuardrailsService:
|
|
|
255
260
|
FieldReference(path="userName", source=FieldSource.INPUT)
|
|
256
261
|
],
|
|
257
262
|
),
|
|
258
|
-
|
|
263
|
+
detects_violation=lambda s: not bool(re.search(".*te.*3.*", s)),
|
|
259
264
|
),
|
|
260
265
|
],
|
|
261
266
|
)
|
|
@@ -273,7 +278,10 @@ class TestDeterministicGuardrailsService:
|
|
|
273
278
|
)
|
|
274
279
|
|
|
275
280
|
assert result.validation_passed is False
|
|
276
|
-
assert
|
|
281
|
+
assert (
|
|
282
|
+
result.reason
|
|
283
|
+
== 'Input data didn\'t match the guardrail condition: [userName] comparing function [(s): not bool(re.search(".*te.*3.*", s))]'
|
|
284
|
+
)
|
|
277
285
|
|
|
278
286
|
def test_evaluate_post_deterministic_guardrail_word_func_positive(
|
|
279
287
|
self,
|
|
@@ -298,7 +306,7 @@ class TestDeterministicGuardrailsService:
|
|
|
298
306
|
FieldReference(path="userName", source=FieldSource.INPUT)
|
|
299
307
|
],
|
|
300
308
|
),
|
|
301
|
-
|
|
309
|
+
detects_violation=lambda s: len(s) <= 5,
|
|
302
310
|
),
|
|
303
311
|
],
|
|
304
312
|
)
|
|
@@ -316,7 +324,7 @@ class TestDeterministicGuardrailsService:
|
|
|
316
324
|
)
|
|
317
325
|
|
|
318
326
|
assert result.validation_passed is True
|
|
319
|
-
assert "All deterministic guardrail rules passed"
|
|
327
|
+
assert result.reason == "All deterministic guardrail rules passed"
|
|
320
328
|
|
|
321
329
|
def test_evaluate_post_deterministic_guardrail_word_func_negative(
|
|
322
330
|
self,
|
|
@@ -341,7 +349,7 @@ class TestDeterministicGuardrailsService:
|
|
|
341
349
|
FieldReference(path="userName", source=FieldSource.INPUT)
|
|
342
350
|
],
|
|
343
351
|
),
|
|
344
|
-
|
|
352
|
+
detects_violation=lambda s: len(s) <= 5,
|
|
345
353
|
),
|
|
346
354
|
],
|
|
347
355
|
)
|
|
@@ -360,6 +368,52 @@ class TestDeterministicGuardrailsService:
|
|
|
360
368
|
|
|
361
369
|
assert result.validation_passed is False
|
|
362
370
|
|
|
371
|
+
def test_evaluate_post_deterministic_guardrail_word_contains_substring_detects_violation(
|
|
372
|
+
self,
|
|
373
|
+
service: DeterministicGuardrailsService,
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Test deterministic guardrail validation fails when string contains forbidden substring."""
|
|
376
|
+
deterministic_guardrail = DeterministicGuardrail(
|
|
377
|
+
id="test-deterministic-id",
|
|
378
|
+
name="Word Contains Guardrail",
|
|
379
|
+
description="Test word contains guardrail",
|
|
380
|
+
enabled_for_evals=True,
|
|
381
|
+
guardrail_type="custom",
|
|
382
|
+
selector=GuardrailSelector(
|
|
383
|
+
scopes=[GuardrailScope.TOOL], match_names=["test"]
|
|
384
|
+
),
|
|
385
|
+
rules=[
|
|
386
|
+
WordRule(
|
|
387
|
+
rule_type="word",
|
|
388
|
+
field_selector=SpecificFieldsSelector(
|
|
389
|
+
selector_type="specific",
|
|
390
|
+
fields=[
|
|
391
|
+
FieldReference(path="userName", source=FieldSource.INPUT)
|
|
392
|
+
],
|
|
393
|
+
),
|
|
394
|
+
detects_violation=lambda s: "dre" in s,
|
|
395
|
+
),
|
|
396
|
+
],
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Input data with userName that contains "dre" - should fail
|
|
400
|
+
input_data = {
|
|
401
|
+
"userName": "andrei",
|
|
402
|
+
}
|
|
403
|
+
output_data: dict[str, Any] = {}
|
|
404
|
+
|
|
405
|
+
result = service.evaluate_post_deterministic_guardrail(
|
|
406
|
+
input_data=input_data,
|
|
407
|
+
output_data=output_data,
|
|
408
|
+
guardrail=deterministic_guardrail,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
assert result.validation_passed is False
|
|
412
|
+
assert (
|
|
413
|
+
result.reason
|
|
414
|
+
== 'Input data didn\'t match the guardrail condition: [userName] comparing function [(s): "dre" in s]'
|
|
415
|
+
)
|
|
416
|
+
|
|
363
417
|
def test_evaluate_post_deterministic_guardrail_number_func_positive(
|
|
364
418
|
self,
|
|
365
419
|
service: DeterministicGuardrailsService,
|
|
@@ -381,7 +435,7 @@ class TestDeterministicGuardrailsService:
|
|
|
381
435
|
selector_type="specific",
|
|
382
436
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
383
437
|
),
|
|
384
|
-
|
|
438
|
+
detects_violation=lambda n: n < 18 or n > 65,
|
|
385
439
|
),
|
|
386
440
|
],
|
|
387
441
|
)
|
|
@@ -399,7 +453,7 @@ class TestDeterministicGuardrailsService:
|
|
|
399
453
|
)
|
|
400
454
|
|
|
401
455
|
assert result.validation_passed is True
|
|
402
|
-
assert "All deterministic guardrail rules passed"
|
|
456
|
+
assert result.reason == "All deterministic guardrail rules passed"
|
|
403
457
|
|
|
404
458
|
def test_evaluate_post_deterministic_guardrail_number_func_negative(
|
|
405
459
|
self,
|
|
@@ -422,7 +476,7 @@ class TestDeterministicGuardrailsService:
|
|
|
422
476
|
selector_type="specific",
|
|
423
477
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
424
478
|
),
|
|
425
|
-
|
|
479
|
+
detects_violation=lambda n: n < 18 or n > 65,
|
|
426
480
|
),
|
|
427
481
|
],
|
|
428
482
|
)
|
|
@@ -678,7 +732,7 @@ class TestDeterministicGuardrailsService:
|
|
|
678
732
|
NumberRule(
|
|
679
733
|
rule_type="number",
|
|
680
734
|
field_selector=AllFieldsSelector(selector_type="all"),
|
|
681
|
-
|
|
735
|
+
detects_violation=lambda n: n != 25.0,
|
|
682
736
|
),
|
|
683
737
|
],
|
|
684
738
|
)
|
|
@@ -720,7 +774,7 @@ class TestDeterministicGuardrailsService:
|
|
|
720
774
|
NumberRule(
|
|
721
775
|
rule_type="number",
|
|
722
776
|
field_selector=AllFieldsSelector(selector_type="all"),
|
|
723
|
-
|
|
777
|
+
detects_violation=lambda n: n != 200.0,
|
|
724
778
|
),
|
|
725
779
|
],
|
|
726
780
|
)
|
|
@@ -898,7 +952,7 @@ class TestDeterministicGuardrailsService:
|
|
|
898
952
|
selector_type="specific",
|
|
899
953
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
900
954
|
),
|
|
901
|
-
|
|
955
|
+
detects_violation=lambda n: n < 21.0,
|
|
902
956
|
),
|
|
903
957
|
BooleanRule(
|
|
904
958
|
rule_type="boolean",
|
|
@@ -908,7 +962,7 @@ class TestDeterministicGuardrailsService:
|
|
|
908
962
|
FieldReference(path="isActive", source=FieldSource.INPUT)
|
|
909
963
|
],
|
|
910
964
|
),
|
|
911
|
-
|
|
965
|
+
detects_violation=lambda b: b is not True,
|
|
912
966
|
),
|
|
913
967
|
],
|
|
914
968
|
)
|
|
@@ -931,7 +985,7 @@ class TestDeterministicGuardrailsService:
|
|
|
931
985
|
selector_type="specific",
|
|
932
986
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
933
987
|
),
|
|
934
|
-
|
|
988
|
+
detects_violation=lambda n: n < 21.0,
|
|
935
989
|
),
|
|
936
990
|
NumberRule(
|
|
937
991
|
rule_type="number",
|
|
@@ -941,7 +995,7 @@ class TestDeterministicGuardrailsService:
|
|
|
941
995
|
FieldReference(path="status", source=FieldSource.OUTPUT)
|
|
942
996
|
],
|
|
943
997
|
),
|
|
944
|
-
|
|
998
|
+
detects_violation=lambda n: n != 200.0,
|
|
945
999
|
),
|
|
946
1000
|
],
|
|
947
1001
|
)
|
|
@@ -964,7 +1018,7 @@ class TestDeterministicGuardrailsService:
|
|
|
964
1018
|
selector_type="specific",
|
|
965
1019
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
966
1020
|
),
|
|
967
|
-
|
|
1021
|
+
detects_violation=lambda n: n < 21.0,
|
|
968
1022
|
),
|
|
969
1023
|
BooleanRule(
|
|
970
1024
|
rule_type="boolean",
|
|
@@ -974,7 +1028,7 @@ class TestDeterministicGuardrailsService:
|
|
|
974
1028
|
FieldReference(path="isActive", source=FieldSource.INPUT)
|
|
975
1029
|
],
|
|
976
1030
|
),
|
|
977
|
-
|
|
1031
|
+
detects_violation=lambda b: b is not True,
|
|
978
1032
|
),
|
|
979
1033
|
NumberRule(
|
|
980
1034
|
rule_type="number",
|
|
@@ -984,7 +1038,7 @@ class TestDeterministicGuardrailsService:
|
|
|
984
1038
|
FieldReference(path="status", source=FieldSource.OUTPUT)
|
|
985
1039
|
],
|
|
986
1040
|
),
|
|
987
|
-
|
|
1041
|
+
detects_violation=lambda n: n != 200.0,
|
|
988
1042
|
),
|
|
989
1043
|
],
|
|
990
1044
|
)
|
|
@@ -1009,7 +1063,7 @@ class TestDeterministicGuardrailsService:
|
|
|
1009
1063
|
selector_type="specific",
|
|
1010
1064
|
fields=[FieldReference(path="age", source=FieldSource.INPUT)],
|
|
1011
1065
|
),
|
|
1012
|
-
|
|
1066
|
+
detects_violation=lambda n: n < 21.0,
|
|
1013
1067
|
),
|
|
1014
1068
|
BooleanRule(
|
|
1015
1069
|
rule_type="boolean",
|
|
@@ -1019,7 +1073,7 @@ class TestDeterministicGuardrailsService:
|
|
|
1019
1073
|
FieldReference(path="isActive", source=FieldSource.INPUT)
|
|
1020
1074
|
],
|
|
1021
1075
|
),
|
|
1022
|
-
|
|
1076
|
+
detects_violation=lambda b: b is not True,
|
|
1023
1077
|
),
|
|
1024
1078
|
NumberRule(
|
|
1025
1079
|
rule_type="number",
|
|
@@ -1029,7 +1083,7 @@ class TestDeterministicGuardrailsService:
|
|
|
1029
1083
|
FieldReference(path="status", source=FieldSource.OUTPUT)
|
|
1030
1084
|
],
|
|
1031
1085
|
),
|
|
1032
|
-
|
|
1086
|
+
detects_violation=lambda n: n != 200.0,
|
|
1033
1087
|
),
|
|
1034
1088
|
],
|
|
1035
1089
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|