Skip to content

Commit e2ab6f7

Browse files
valentinabojanValentina Bojanclaude
authored
feat(guardrails): forward byoConnectionId for BYO guardrails (#1817)
Co-authored-by: Valentina Bojan <valentina.bojan@uipath.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c5924ec commit e2ab6f7

6 files changed

Lines changed: 152 additions & 3 deletions

File tree

packages/uipath-platform/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-platform"
3-
version = "0.2.9"
3+
version = "0.2.10"
44
description = "HTTP client library for programmatic access to UiPath Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

packages/uipath-platform/src/uipath/platform/guardrails/_guardrails_service.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ def evaluate_guardrail(
127127
"BYO (Bring Your Own) guardrails require byo_validator_name."
128128
)
129129
payload["byoValidatorName"] = guardrail.byo_validator_name
130+
if guardrail.byo_connection_id:
131+
payload["byoConnectionId"] = guardrail.byo_connection_id
130132
spec = RequestSpec(
131133
method="POST",
132134
endpoint=Endpoint("/agentsruntime_/api/execution/guardrails/validate"),

packages/uipath-platform/src/uipath/platform/guardrails/guardrails.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class BuiltInValidatorGuardrail(BaseGuardrail):
9292
default_factory=list, alias="validatorParameters"
9393
)
9494
byo_validator_name: str | None = Field(default=None, alias="byoValidatorName")
95+
byo_connection_id: str | None = Field(default=None, alias="byoConnectionId")
9596

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

packages/uipath-platform/tests/services/test_guardrails_service.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,109 @@ def capture_request(request):
350350
assert request_payload["byoValidatorName"] == "my_databricks_pii"
351351
assert result.result == GuardrailValidationResultType.PASSED
352352

353+
def test_evaluate_guardrail_byog_forwards_byo_connection_id(
354+
self,
355+
httpx_mock: HTTPXMock,
356+
service: GuardrailsService,
357+
base_url: str,
358+
org: str,
359+
tenant: str,
360+
) -> None:
361+
"""A BYOG guardrail forwards byoConnectionId so the backend can narrow
362+
configuration resolution to the specific connection."""
363+
captured_request = None
364+
365+
def capture_request(request):
366+
nonlocal captured_request
367+
captured_request = request
368+
return httpx.Response(
369+
status_code=200,
370+
json={"result": "PASSED", "details": "Validation passed"},
371+
)
372+
373+
httpx_mock.add_callback(
374+
method="POST",
375+
url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate",
376+
callback=capture_request,
377+
)
378+
379+
byog_guardrail = BuiltInValidatorGuardrail(
380+
id="byog-id",
381+
name="Databricks PII (BYOG)",
382+
enabled_for_evals=True,
383+
selector=GuardrailSelector(scopes=[GuardrailScope.LLM]),
384+
guardrail_type="builtInValidator",
385+
validator_type="byo",
386+
byo_validator_name="my_databricks_pii",
387+
byo_connection_id="byog-conn-1",
388+
validator_parameters=[],
389+
)
390+
391+
service.evaluate_guardrail("some input", byog_guardrail)
392+
393+
assert captured_request is not None
394+
request_payload = json.loads(captured_request.content)
395+
assert request_payload["byoValidatorName"] == "my_databricks_pii"
396+
assert request_payload["byoConnectionId"] == "byog-conn-1"
397+
398+
def test_evaluate_guardrail_byog_omits_connection_id_when_absent(
399+
self,
400+
httpx_mock: HTTPXMock,
401+
service: GuardrailsService,
402+
base_url: str,
403+
org: str,
404+
tenant: str,
405+
) -> None:
406+
"""byoConnectionId is only forwarded when present; a BYOG guardrail without
407+
one resolves by validator name alone."""
408+
captured_request = None
409+
410+
def capture_request(request):
411+
nonlocal captured_request
412+
captured_request = request
413+
return httpx.Response(
414+
status_code=200,
415+
json={"result": "PASSED", "details": "Validation passed"},
416+
)
417+
418+
httpx_mock.add_callback(
419+
method="POST",
420+
url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate",
421+
callback=capture_request,
422+
)
423+
424+
byog_guardrail = BuiltInValidatorGuardrail(
425+
id="byog-id",
426+
name="Databricks PII (BYOG)",
427+
enabled_for_evals=True,
428+
selector=GuardrailSelector(scopes=[GuardrailScope.LLM]),
429+
guardrail_type="builtInValidator",
430+
validator_type="byo",
431+
byo_validator_name="my_databricks_pii",
432+
validator_parameters=[],
433+
)
434+
435+
service.evaluate_guardrail("some input", byog_guardrail)
436+
437+
assert captured_request is not None
438+
request_payload = json.loads(captured_request.content)
439+
assert "byoConnectionId" not in request_payload
440+
441+
def test_evaluate_guardrail_byo_connection_id_from_alias(self) -> None:
442+
"""byoConnectionId parses into the typed field via its camelCase alias."""
443+
guardrail = BuiltInValidatorGuardrail.model_validate(
444+
{
445+
"$guardrailType": "builtInValidator",
446+
"id": "byog-id",
447+
"name": "BYOG",
448+
"validatorType": "byo",
449+
"byoValidatorName": "my_databricks_pii",
450+
"byoConnectionId": "byog-conn-1",
451+
"validatorParameters": [],
452+
}
453+
)
454+
assert guardrail.byo_connection_id == "byog-conn-1"
455+
353456
def test_evaluate_guardrail_byo_without_name_raises(
354457
self,
355458
service: GuardrailsService,
@@ -426,6 +529,49 @@ def capture_request(request):
426529
request_payload = json.loads(captured_request.content)
427530
assert "byoValidatorName" not in request_payload
428531

532+
def test_evaluate_guardrail_non_byo_type_does_not_forward_connection_id(
533+
self,
534+
httpx_mock: HTTPXMock,
535+
service: GuardrailsService,
536+
base_url: str,
537+
org: str,
538+
tenant: str,
539+
) -> None:
540+
"""byoConnectionId is only forwarded for the "byo" sentinel, never leaked
541+
into a non-BYOG validator payload even if the field happens to be set."""
542+
captured_request = None
543+
544+
def capture_request(request):
545+
nonlocal captured_request
546+
captured_request = request
547+
return httpx.Response(
548+
status_code=200,
549+
json={"result": "PASSED", "details": "Validation passed"},
550+
)
551+
552+
httpx_mock.add_callback(
553+
method="POST",
554+
url=f"{base_url}{org}{tenant}/agentsruntime_/api/execution/guardrails/validate",
555+
callback=capture_request,
556+
)
557+
558+
guardrail = BuiltInValidatorGuardrail(
559+
id="test-id",
560+
name="PII detection guardrail",
561+
enabled_for_evals=True,
562+
selector=GuardrailSelector(scopes=[GuardrailScope.LLM]),
563+
guardrail_type="builtInValidator",
564+
validator_type="pii_detection",
565+
byo_connection_id="stray-conn",
566+
validator_parameters=[],
567+
)
568+
569+
service.evaluate_guardrail("some input", guardrail)
570+
571+
assert captured_request is not None
572+
request_payload = json.loads(captured_request.content)
573+
assert "byoConnectionId" not in request_payload
574+
429575
def test_evaluate_guardrail_ootb_omits_byo_validator_name(
430576
self,
431577
httpx_mock: HTTPXMock,

packages/uipath-platform/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.

packages/uipath/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)