feat(guardrails): send attached file references with llm-as-judge evaluation - #1082
Draft
apetraru-uipath wants to merge 4 commits into
Draft
feat(guardrails): send attached file references with llm-as-judge evaluation#1082apetraru-uipath wants to merge 4 commits into
apetraru-uipath wants to merge 4 commits into
Conversation
…luation Agent-scope and LLM-scope LLM-as-Judge guardrails on low-code agents now forward the run's job attachments to the guardrails backend, so the judge can evaluate what is *in* a file rather than the serialized metadata that currently lands in the prompt. No new state plumbing was needed: state.inner_state.job_attachments is already populated at every agent- and LLM-scope guardrail node, and the init subgraph wires START -> guarded-init -> guardrail, so the registry is committed before the Agent-scope PRE guardrail runs. New agent/guardrails/attachment_refs.py projects that registry into resolved references. It never raises: the low-code guardrail node re-raises everything it sees, so letting a transient Orchestrator failure escape would kill a production run over an unscanned file. Gated on validator_type == llm_as_judge and the UIPATH_FEATURE_GuardrailAttachmentsEnabled flag, because resolving a SAS url costs an Orchestrator round-trip. Two pre-existing issues in guardrail_nodes.py fixed along the way: - The payload generator ran twice per evaluation — once for observability metadata, once inside _evaluate_builtin_guardrail. It now runs once and the text is passed down. A regression test asserts the single invocation. - evaluate_guardrail is synchronous and was called directly from an async node, blocking the event loop for the whole round-trip. Now offloaded with asyncio.to_thread, which matters more once the backend fetches files inside that call. Only attachment identity (id, file name, mime type) reaches observability metadata — the resolved url is a SAS credential, asserted by a test. Six test mocks of evaluate_guardrail needed the new keyword-only argument. Full suite green; tests/agent/guardrails 299 passed (was 279). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Widens the supported mime set to the binary types the backend can send to a vision-capable judge model as content parts. The runtime still only forwards references — this set exists so an Orchestrator round-trip isn't spent resolving a file the backend would skip anyway. tests/agent/guardrails: 302 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… attachment Three fixes from independent review of the branch: - Tool-scope guardrails no longer receive attachments. _create_guardrail_node had no scope check and the tool node routes through it, so a tool-scope judge would have resolved SAS urls and shipped file contents on every tool call — against the product decision that only Agent and LLM scope inspect files. - A helix 400 on a request that carried attachments now falls back to evaluating the text payload alone instead of terminating the run. The backend rejects the attachment references themselves (unsafe url, unknown host, oversize name), which is not the agent's fault; a 400 without attachments still propagates. - File names are truncated to the API's 260-character ceiling before forwarding, so an over-long name cannot turn into a 400 for the whole request. Also drops the metadata["payload"]["attachments"] entry: nothing consumed it (the Python side creates no evaluation span for built-in guardrails — that span is helix's, which already records attachments), and it was never cleared across LLM turns. tests/agent/guardrails: 305 passed. ruff, ruff format, mypy (touched files) clean. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… guardrail The judge guardrail now carries an optional `appliesTo` parameter (Prompts / Files / Both). The backend gates on it too, but only after the SAS urls are resolved -- reading it here is what actually saves an Orchestrator round-trip per file when the author scoped the guardrail to the prompt. Absent, unrecognized, or unreadable means files stay in scope, matching the backend's default of Both: silently stopping file scanning for a guardrail whose author never asked for that is the worse failure direction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What
Agent-scope and LLM-scope LLM-as-Judge guardrails on low-code agents now forward the run's job attachments to the guardrails backend, so the judge can evaluate what is in a file rather than the serialized metadata that currently lands in the prompt.
No new state plumbing was needed:
state.inner_state.job_attachmentsis already populated at every agent- and LLM-scope guardrail node, and the init subgraph wiresSTART → guarded-init → guardrail, so the registry is committed before the Agent-scope PRE guardrail runs.Review fixes (
d9d57812)_create_guardrail_nodehad no scope check and the tool node routes through it, so a tool-scope judge would have shipped file contents on every tool call. Now Agent and LLM scope only, per the product decision.metadata["payload"]["attachments"]: nothing consumed it (the Python side creates no evaluation span for built-in guardrails; helix's span already records attachments) and it was never cleared across turns.Scope selector (
06a64b0c)The judge guardrail gained an optional
appliesToparameter (Prompts / Files / Both) inUiPath/Agents#6256. The backend gates on it as well, but only after the SAS urls are resolved, so reading it here is what actually saves an Orchestrator round-trip per file on a prompts-only guardrail.Absent, unrecognized, or unreadable keeps files in scope, matching the backend default of Both — silently stopping file scanning for a guardrail whose author never asked for that is the worse direction. The parameter id is matched case-insensitively, as the backend matches it.
Notes for reviewers
attachment_refs.pynever raises. The low-code guardrail node re-raises everything it sees, which terminates the agent run — so a transient Orchestrator failure must never escape. Gated onvalidator_type == "llm_as_judge"andUIPATH_FEATURE_GuardrailAttachmentsEnabled.Two pre-existing bugs fixed along the way, both with regression tests: the payload generator ran twice per evaluation, and the synchronous
evaluate_guardrailblocked the event loop (nowasyncio.to_thread).Out of scope by design: the coded-agent middleware and decorator flavors.
Test plan
tests/agent/guardrails/— 305 passed (baseline 279).tests/cli/55,tests/guardrails/253 (coded flavors, untouched). ruff, ruff format, mypy (touched files) clean.Depends on: UiPath/uipath-python#1895 · pairs with UiPath/Agents#6256
🤖 Generated with Claude Code