Skip to content

Commit a80e1ef

Browse files
valentinabojanValentina Bojan
andauthored
feat(guardrails): add missing rule description for UniversalRule (#519)
Co-authored-by: Valentina Bojan <valentina.bojan@uipath.com>
1 parent d712378 commit a80e1ef

5 files changed

Lines changed: 56 additions & 4 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-langchain"
3-
version = "0.5.24"
3+
version = "0.5.25"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_langchain/agent/guardrails/actions/escalate_action.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,13 @@ async def _node(
191191
reviewed_outputs = escalation_result.data.get("ReviewedOutputs")
192192
reason = escalation_result.data.get("Reason")
193193
if reviewed_inputs:
194-
metadata["escalation_data"]["reviewed_inputs"] = reviewed_inputs
194+
metadata["escalation_data"]["reviewed_inputs"] = (
195+
_parse_reviewed_data(reviewed_inputs)
196+
)
195197
if reviewed_outputs:
196-
metadata["escalation_data"]["reviewed_outputs"] = reviewed_outputs
198+
metadata["escalation_data"]["reviewed_outputs"] = (
199+
_parse_reviewed_data(reviewed_outputs)
200+
)
197201
if reason:
198202
metadata["escalation_data"]["reason"] = reason
199203

@@ -226,6 +230,23 @@ async def _node(
226230
return node_name, _node
227231

228232

233+
def _parse_reviewed_data(value: Any) -> Any:
234+
if not value:
235+
return value
236+
237+
if isinstance(value, str):
238+
try:
239+
# Try to parse as JSON first (handles JSON strings like "abcd")
240+
parsed = json.loads(value)
241+
return parsed
242+
except json.JSONDecodeError:
243+
# If not valid JSON, return as-is (plain string)
244+
return value
245+
246+
# Already parsed (dict/list), return as-is
247+
return value
248+
249+
229250
def _validate_message_count(
230251
state: AgentGuardrailsGraphState,
231252
execution_stage: ExecutionStage,

src/uipath_langchain/agent/guardrails/guardrails_factory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,9 @@ def _convert_agent_rule_to_deterministic(
368368
"""
369369
if isinstance(agent_rule, UniversalRule):
370370
# UniversalRule is already compatible
371+
agent_rule.rule_description = (
372+
f"Always enforce the guardrail on {agent_rule.apply_to.value}"
373+
)
371374
return agent_rule
372375

373376
if isinstance(agent_rule, AgentWordRule):

tests/agent/guardrails/test_guardrails_factory.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@
3232
)
3333
from uipath.core.guardrails import (
3434
AllFieldsSelector,
35+
ApplyTo,
3536
BooleanRule,
3637
DeterministicGuardrail,
3738
FieldSource,
3839
GuardrailSelector,
3940
NumberRule,
41+
UniversalRule,
4042
WordRule,
4143
)
4244

@@ -818,6 +820,32 @@ def test_convert_boolean_rule(self) -> None:
818820
assert result.detects_violation(True) is True
819821
assert result.detects_violation(False) is False
820822

823+
def test_convert_universal_rule_sets_rule_description(self) -> None:
824+
"""UniversalRule should have rule_description set based on apply_to value."""
825+
universal_rule = UniversalRule(
826+
rule_type="always",
827+
apply_to=ApplyTo.INPUT_AND_OUTPUT,
828+
)
829+
# Create a minimal guardrail for testing
830+
guardrail = AgentCustomGuardrail.model_validate(
831+
{
832+
"$guardrailType": "custom",
833+
"id": "test-id",
834+
"name": "test-guardrail",
835+
"description": "Test",
836+
"enabledForEvals": True,
837+
"selector": {"$selectorType": "all"},
838+
"rules": [],
839+
"action": {"$actionType": "block", "reason": "test"},
840+
}
841+
)
842+
result = _convert_agent_rule_to_deterministic(universal_rule, guardrail, [])
843+
844+
assert isinstance(result, UniversalRule)
845+
assert (
846+
result.rule_description == "Always enforce the guardrail on inputAndOutput"
847+
)
848+
821849
def test_unsupported_rule_type_raises_value_error(self) -> None:
822850
# Create a mock rule that's not a supported type
823851
invalid_rule = cast(AgentWordRule, types.SimpleNamespace())

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)