Skip to content

feat(guardrails): send attached file references with llm-as-judge evaluation - #1082

Draft
apetraru-uipath wants to merge 4 commits into
mainfrom
feat/guardrail-judge-file-support
Draft

feat(guardrails): send attached file references with llm-as-judge evaluation#1082
apetraru-uipath wants to merge 4 commits into
mainfrom
feat/guardrail-judge-file-support

Conversation

@apetraru-uipath

@apetraru-uipath apetraru-uipath commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Draft until uipath-platform 0.2.29 publishes. This branch imports GuardrailAttachment from it at module top level in guardrail_nodes.py, so against the currently published SDK the whole uipath_langchain.agent package fails to import — CI here is red for exactly that reason. Before marking ready: raise the uipath-platform floor to >=0.2.29, uv sync, commit uv.lock. Everything else is complete and green locally (305 guardrail tests against the local SDK).

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_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.

Review fixes (d9d57812)

  • Tool scope excluded. _create_guardrail_node had 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.
  • A helix 400 on a request that carried attachments falls back to text-only instead of terminating the run — the backend rejected the file references, 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.
  • Dropped 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 appliesTo parameter (Prompts / Files / Both) in UiPath/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.py never 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 on validator_type == "llm_as_judge" and UIPATH_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_guardrail blocked the event loop (now asyncio.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

apetraru-uipath and others added 4 commits September 13, 2026 01:31
…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>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 90%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant