Repository navigation
feat(guardrails): coded-agent guardrails forward attachment references and accept applies_to [AL-599] - #1145
Draft
apetraru-uipath wants to merge 11 commits into
Draft
apetraru-uipath wants to merge 11 commits into
apetraru-uipath wants to merge 11 commits into
Conversation
… and accept applies_to [AL-599] Built-in guardrail middlewares now send references to the files a run holds with each validate call, as low-code guardrails already do. A registry is rebuilt from the agent state on every hook: conversational attachments, the <uip:attachments> blocks, SDK-tagged file and image blocks, files tool results describe, and the advanced agent's uipath__job_attachments state. Agent and LLM input checks forward the whole registry; output checks only the held files the output names; a tool call the held files its arguments name, plus the files its result returns. A call without files is unchanged. The seven built-in constructors accept applies_to (AppliesTo or "Text", "Files", "Both"); None sends no parameter so the backend default applies. Files without text evaluate the "[no text content]" placeholder. build_file_content_blocks_for tags blocks built from an attachment in extras. attachment_refs.py delegates to uipath.platform.guardrails.attachments. AppliesTo and render_attachments_block are exported from uipath_langchain.guardrails. Raises the uipath-platform floor to 0.2.37. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…idate rejects the attachments [AL-599] A validate call that carried attachment references and got HTTP 400 used to fall through to the generic error handler, which only logs, so the whole guardrail was skipped, text included. _evaluate_guardrail now retries the same input without attachments on a 400 and returns that verdict, as the low-code guardrail node does. A 400 without attachments and any other status still reach the existing handler. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…L-599] @guardrail on a LangChain tool, chat model, StateGraph or compiled graph now passes the evaluator the attachment references each call can see, as the middleware guardrails do. A tool forwards the self-describing files in its arguments and its result. An LLM or agent input check forwards every file the messages hold, plus typed Attachment state keys for an agent; an output check only the held files the response or structured response names, plus the attachments an agent returns in typed state keys. Files without text evaluate the "[no text content]" placeholder, which is never written back. attachments is passed only when there is at least one, so an evaluator with the old four-positional signature keeps working for text-only calls. The evaluator drops attachments for appliesTo=Text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or drops files [AL-599] A decorated chat model, StateGraph or compiled graph that sees files but no text evaluated the "[no text content]" placeholder even when the guardrail was scoped to text, so the validator judged only the constant. The adapter now reads the evaluator's forwards_attachments flag (set by uipath-platform's _make_evaluator; an evaluator without it is assumed to forward files) and collects no attachments when it is False, so a files-only call is skipped and a text call is unchanged. Text scope, an appliesTo=Text validator parameter, custom validators and legacy run() overrides are covered; the backend default (applies_to=None) keeps forwarding, since the judge defaults to Both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the explicit empty list [AL-599] The adapter skips a files-only call when the @guardrail evaluator says it drops files. A contract test now reads that attribute from the evaluator the installed uipath-platform hands the adapter, so a platform release without it fails here by name instead of silently making placeholder calls. The bare-id and unknown-id tests assert the exact request again: under the Both scope the platform sends attachments=[] rather than omitting the field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt sample [AL-599] docs/guardrails.md documents applies_to on the built-in middleware classes and on @guardrail, and adds a Files section for each flavor: what is forwarded (references only, at most 5, gated by the tenant's file-support flags), how a file becomes visible to a guardrail, which files each hook forwards, the held-files rule, the [no text content] placeholder, the HTTP 400 text-only retry, text-only filters, and the per-target discovery of the decorator, including that adapter-wrapped tools guard invoke only. samples/cv-screener-agent ports the Agents e2e CvScreener low-code agent with its prompts, judge rules, examples, thresholds, action reasons and fixtures. Prompt injection is a decorator on the LLM factory; harmful content, the EU judge, the only-CVs judge and protected code are middleware. The only-CVs judge is middleware rather than a decorator on the Analyze Files tool because that tool has only a coroutine and the decorator guards invoke only. PARITY_CHECK=1 adds the other flavor of each guardrail; GUARDRAILS_APPLIES_TO overrides every applies_to for a Text control run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ke [AL-599] Only Tool/StructuredTool without a coroutine route ainvoke through invoke; coroutine tools and BaseTool subclasses bypass the adapter's tool guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The adapter overrode invoke only, so a tool create_agent runs through ainvoke with a coroutine (async def, or coroutine= like the Analyze Files tool) or a BaseTool subclass never reached the guard. ainvoke now applies PRE and POST around the base call; a per-wrap ContextVar token makes the invoke that StructuredTool/Tool.ainvoke routes a sync tool through skip its own check, so each stage is evaluated once, stacked guardrails stay independent and a tool that calls itself is still checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttachments [AL-599] Middleware guardrails: - forward nothing when an appliesTo validator parameter scopes the guardrail to text, not only when applies_to=TEXT; - send attachments=[] when files were requested (Files/Both, by applies_to or parameter) and none was found, mirroring BuiltInGuardrailValidator.run; None and Text keep the bare call, and the 400 retry stays bare; - read the structured response for references when the output has no AIMessage. Decorator adapter: LLM POST with list content and files evaluates the text blocks instead of the placeholder; docstrings name the echoed-input and no-messages limits. BYO docstring states the connector default. Tests type-check under CI's mypy run, pin the appliesTo EnumParameterValue wire shape and cover the agent-scope placeholder without write-back. Sample: Only CVs is the decorator on the Analyze Files tool again, with the middleware as its PARITY_CHECK twin; the attachments block drops "metadata": null. Docs and README follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-agent sample [AL-599] Analyze Files failed with a gateway 400 because the normalized UiPathChat client does not send file content blocks to the provider. The tool now reads the file through UiPathChatOpenAI; the agent's own model never receives file content and stays on UiPathChat. The README also describes the decorator's Analyze Files check correctly: it forwards the mentions in the arguments with the name and type they carry. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ate, docs nit [AL-599] Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
This branch has not been deployed
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 changed?
Coded-agent guardrails read the files a run holds, the way low-code guardrails have since AL-592/593/595. Both coded flavors are covered.
UiPath*Middleware). Every built-in middleware gainsapplies_to(AppliesTo.TEXT/FILES/BOTH, or the strings"Text"/"Files"/"Both";Nonesends no parameter so the backend's per-validator default applies:Bothfor LLM-as-judge,Textfor the others). Hooks derive the run's attachment registry from the message history — HumanMessages (additional_kwargs["attachments"]and<uip:attachments>blocks), ToolMessages, SDK-tagged multimodal blocks and the advanced agent'suipath__job_attachmentsstate — and forward references throughevaluate_guardrail(..., attachments=[{id, fileName, mimeType}]): whole registry at agent/LLM pre-checks, only registry-held mentions in the tool arguments at tool pre-checks, the tool's own result at tool post-checks, and only the files the output references at LLM/agent post-checks (AL-611 rule). A hand-suppliedappliesTo=Textparameter also stops forwarding; an explicit empty list goes out when files were requested but none were found (forward-compatible with uipath-python#1939); a 400 on the attachment list re-evaluates text-only once with a warning, like the low-code node. Text-only payloads are byte-identical to today.@guardrailon LangChain tools, chat models, graphs). Tool wraps forward the self-describing mentions in the arguments/result; LLM wraps use the message-list registry; graph wraps scan the input/output state andstructured_response. Wrapped tools are now guarded onainvoketoo (aContextVartoken prevents double checks for sync tools routed through an executor) — before this, a@guardrailon a coroutine-only tool undercreate_agentnever ran. The adapter reads the platform evaluator'sforwards_attachmentsflag to skip placeholder-only calls for Text-scoped guardrails.agent/guardrails/attachment_refs.pyis a thin shim overuipath.platform.guardrails.attachments; low-code behaviour is unchanged (its 69 tests pass as they were).agent/multimodal/invoke.pytags SDK-built file/image blocks with the attachment id inextrasso the registry can read them back.AppliesToandrender_attachments_blockfromuipath_langchain.guardrails.samples/cv-screener-agent: a coded port of the Agents e2eCvScreenerGuardrailSolutionmixing both flavors (decorator on the LLM factory and on the Analyze Files tool, middleware for the agent-scope checks), with aPARITY_CHECKswitch that doubles every guardrail in the other flavor and aGUARDRAILS_APPLIES_TOcontrol. Fixtures are the e2e files.docs/guardrails.mdgains "Files (attachments)" sections for both flavors (what is forwarded, hook table, how a file becomes visible, security note, Filter is text-only, known gap for raw bytes).uipath-platform>=0.2.37(feat(guardrails): @guardrail forwards attachment references and accepts applies_to [AL-599] uipath-python#1940), package version bumped.uv.lockis regenerated once 0.2.37 is on PyPI (this PR stays draft until then).Trust boundary and degradation (for the security reviewers)
{id, fileName, mimeType}; the Agents backend resolves each id through Orchestrator with the run's own identity, so a forwarded id can never read more than that identity already can. Middleware hooks forward tool-call mentions only when the run already holds the id (agent input, conversation attachments, files a tool returned); the decorator's tool wrap forwards the self-describing mentions in the arguments, which is the same reach the Analyze Files tool already gives the model (resolve_attachments_to_file_infosresolves any id the model passes). A tool result that describes a file with an id, name and type makes that file "held" for later checks; that needs a valid id the run identity may read. Logs carry guardrail names and, at debug level, ids and file names, never content.applies_toset, or anappliesToparameter) but none were found, the call carriesattachments=[]. With today'suipath-platformthat is byte-identical to omitting the field; after feat(guardrails): send an empty attachments list when a step has no files [AL-611] uipath-python#1939 the backend treats it as "files apply, none present" (aFiles-only guardrail passes without a provider call,Bothstill scans the text) — the AL-611 D6 rule low-code already follows.How has this been tested?
applies_tomatrix over all seven classes, explicit empty list, 400 retry, byte-identical no-files run through a realcreate_agent), 64 adapter tests (tool/LLM/graph discovery,ainvokeguard, stacked guardrails, old 4-positional evaluators), 6 multimodal tagging tests; the 12×2 parity scenarios unchanged;ruff,ruff format,mypy .clean.cv_prompt_injection.txtblocked by the decorator prompt-injection check at the LLM pre-check; the same file withGUARDRAILS_APPLIES_TO=Textwas not blocked (control);cv_harmful_content.pdfblocked by the middleware harmful-content check at agent input;recipe_not_a_cv.mdblocked at the Analyze Files pre-check;cv_non_eu_candidate.pdflogged[GUARDRAIL] [Candidate based outside the EU] Failed: Flagged: CV states current residence in Toronto, Canada…and the run continued. Logs captured per run.PARITY_CHECK=1on the non-EU CV doubled the validate calls (6 → 12) with both judge twins flagging; on the recipe the middleware twin blocked first.UiPathChatclient (uipath-langchain-client 1.21.0) does not send LangChainfilecontent blocks to the provider (gateway 400 "Missing required parameter: messages[0].content[1].file"); the sample reads files throughUiPathChatOpenAI, which works.Related (AL-599)
@guardrail(applies_to=…), evaluatorforwards_attachments, zero-argument PRE skip).attachment_refs.py's neighbourhood; rebase whichever merges second.🤖 Generated with Claude Code