-
Notifications
You must be signed in to change notification settings - Fork 29
feat: allow guardrail evaluation to carry attachment references #1895
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
78d5aa9
63da69e
49b5032
e831652
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,35 @@ | |
| from ..common._job_context import header_job_key | ||
| from ..common._models import Endpoint, RequestSpec | ||
| from ..errors import EnrichedException | ||
| from .guardrails import BYO_VALIDATOR_TYPE, BuiltInValidatorGuardrail | ||
| from .guardrails import ( | ||
| BYO_VALIDATOR_TYPE, | ||
| BuiltInValidatorGuardrail, | ||
| GuardrailAttachment, | ||
| ) | ||
|
|
||
| #: Timeout for a validate call carrying attachments. The backend fetches and decodes each | ||
| #: file inside the request, which the default 30s client timeout does not allow for. | ||
| _ATTACHMENT_VALIDATE_TIMEOUT_SECONDS = 60.0 | ||
|
|
||
|
|
||
| def _redact_attachment_urls(inputs: dict[str, Any]) -> dict[str, Any]: | ||
| """Strip SAS urls from the traced inputs of ``evaluate_guardrail``. | ||
|
|
||
| ``@traced`` records a function's arguments on the span by default. An attachment | ||
| ``url`` is a short-lived SAS credential and must never reach telemetry, so replace | ||
| it and keep the rest (guardrail, input, attachment identity) intact. | ||
| """ | ||
| attachments = inputs.get("attachments") | ||
| if not isinstance(attachments, list): | ||
| return inputs | ||
| redacted = [] | ||
| for attachment in attachments: | ||
| if isinstance(attachment, dict) and "url" in attachment: | ||
| redacted.append({**attachment, "url": "<redacted>"}) | ||
| else: | ||
| redacted.append(attachment) | ||
| return {**inputs, "attachments": redacted} | ||
|
|
||
|
|
||
| # x-uipath-traceparent-id header format: {version}-{trace_id}-{span_id}[-{trace_flags}] | ||
| # Based on W3C traceparent but allows 16- or 32-hex span IDs. | ||
|
|
@@ -97,17 +125,25 @@ def _parse_result(result_str: str) -> GuardrailValidationResultType: | |
| # Fallback to validation_failed if unknown | ||
| return GuardrailValidationResultType.VALIDATION_FAILED | ||
|
|
||
| @traced("evaluate_guardrail", run_type="uipath") | ||
| @traced( | ||
| "evaluate_guardrail", run_type="uipath", input_processor=_redact_attachment_urls | ||
| ) | ||
| def evaluate_guardrail( | ||
| self, | ||
| input_data: str | dict[str, Any], | ||
| guardrail: BuiltInValidatorGuardrail, | ||
| *, | ||
| attachments: list[GuardrailAttachment] | None = None, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in e831652: an 🤖 Generated with Claude Code |
||
| ) -> GuardrailValidationResult: | ||
| """Validate input text using the provided guardrail. | ||
|
|
||
| Args: | ||
| input_data: The text or structured data to validate. Dictionaries will be converted to a string before validation. | ||
| guardrail: A guardrail instance used for validation. | ||
| attachments: Files attached to the run that the guardrail may inspect, so a | ||
| validator can evaluate a file's contents rather than only its metadata. | ||
| Which validators can use them, and which file types are readable, is | ||
| decided server-side. Omitted from the request body when empty. | ||
|
|
||
| Returns: | ||
| GuardrailValidationResult: The outcome of the guardrail evaluation. | ||
|
|
@@ -127,6 +163,8 @@ def evaluate_guardrail( | |
| "BYO (Bring Your Own) guardrails require byo_validator_name." | ||
| ) | ||
| payload["byoValidatorName"] = guardrail.byo_validator_name | ||
| if attachments: | ||
| payload["attachments"] = [a.model_dump(by_alias=True) for a in attachments] | ||
| spec = RequestSpec( | ||
| method="POST", | ||
| endpoint=Endpoint("/agentsruntime_/api/execution/guardrails/validate"), | ||
|
|
@@ -147,13 +185,22 @@ def evaluate_guardrail( | |
| **source_headers, | ||
| **header_job_key(), | ||
| } | ||
| # The default client timeout is 30s (common/_http_config.py). A validate call | ||
| # carrying attachments waits for the backend to fetch and decode each one, so give | ||
| # it more room. RequestSpec.timeout exists but is never forwarded, so pass it here. | ||
| request_kwargs: dict[str, Any] = { | ||
| "json": spec.json, | ||
| "headers": request_headers, | ||
| } | ||
| if attachments: | ||
| request_kwargs["timeout"] = _ATTACHMENT_VALIDATE_TIMEOUT_SECONDS | ||
|
Comment on lines
+195
to
+196
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added in e831652: one test asserts the attachment-bearing call forwards 🤖 Generated with Claude Code |
||
|
|
||
| span_id = None | ||
| try: | ||
| response = self.request( | ||
| spec.method, | ||
| url=spec.endpoint, | ||
| json=spec.json, | ||
| headers=request_headers, | ||
| **request_kwargs, | ||
| ) | ||
| span_id = self._extract_span_id_from_traceparent( | ||
| response.headers.get("x-uipath-traceparent-id") | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 49b5032 — all three lockfiles relocked; CI's
uv lock --checkis green.🤖 Generated with Claude Code