Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/guardrails.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,49 @@ async def my_node(state: Input) -> Output:
...
```

### Escalation action (human-in-the-loop)

`EscalateAction` works on the decorator path exactly as it does for middleware — on a violation it suspends the run via `interrupt(CreateEscalation(...))`, creates a review task in a UiPath **Action App**, and resumes on Approve/Reject. It is the **same action class** as the middleware path; just pass it as the `action` of a `@guardrail` on the factory you want to guard. The escalation task's `Component` / `ExecutionStage` are **derived automatically** from the inferred scope of the decorated target — no extra configuration:

```python
from langchain.agents import create_agent
from uipath_langchain.guardrails import (
guardrail,
EscalateAction,
PIIValidator,
GuardrailExecutionStage,
PIIDetectionEntity,
)
from uipath_langchain.guardrails.enums import PIIDetectionEntityType
from uipath.platform.action_center.tasks import TaskRecipient, TaskRecipientType

@guardrail(
validator=PIIValidator(
entities=[PIIDetectionEntity(PIIDetectionEntityType.EMAIL, threshold=0.5)],
),
action=EscalateAction(
app_name="Guardrail Escalation Action App",
app_folder_path="Shared",
# optional: route the review task to a specific recipient
recipient=TaskRecipient(
type=TaskRecipientType.EMAIL, value="reviewer@example.com"
),
),
name="Agent PII escalation",
stage=GuardrailExecutionStage.PRE, # escalate once per run
)
def create_my_agent():
return create_agent(model=llm, tools=[analyze_text], system_prompt="...")

agent = create_my_agent()
```

On resume: **Approve** continues, substituting the reviewer's edit if any — read from `ReviewedInputs` for a PRE (input) escalation and `ReviewedOutputs` for a POST (output) one, otherwise keeping the original; **Reject** raises `GuardrailBlockException` and terminates the run. The `app_name` / `app_folder_path` / `assignee` / `recipient` / `title` parameters and the auto-derived payload fields behave identically to the [middleware escalation action](#escalation-action-human-in-the-loop) above — refer to it for the full parameter list.

> 💡 **Scope inference for the payload context.** `Component` / `ExecutionStage` are derived automatically for the adapter-handled LangChain targets — `@tool`, `BaseChatModel` factories, and `create_agent()` factories. On a plain LangGraph node or plain Python function (handled by the core `@guardrail`, which doesn't publish the LangChain runtime context) the escalation still suspends, but those two fields are not populated.

> 💡 **Escalate once per run.** As with middleware, AGENT/LLM scope validates both *before* and *after* by default. Set `stage=GuardrailExecutionStage.PRE` (or `POST`) so only a single checkpoint is registered.

---

## Choosing between middleware and decorator
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-langchain"
version = "0.11.16"
version = "0.11.17"
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
41 changes: 41 additions & 0 deletions samples/joke-agent-decorator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ This sample uses a single unified `@guardrail` decorator with three components:
| `@guardrail(validator=CustomValidator(...))` | `analyze_joke_syntax` tool | Custom (length check) | `BlockAction` — blocks jokes > 1000 chars |
| `@guardrail(validator=CustomValidator(...))` | `analyze_joke_syntax` tool | Custom (always true) | `CustomFilterAction` — always-on output transform (POST) |
| `@guardrail(validator=PIIValidator(...EMAIL, PHONE...))` | `analyze_joke_syntax` tool | PII (Email, Phone) | `LogAction(WARNING)` — logs email/phone |
| `@guardrail(validator=PIIValidator(...EMAIL...))` | `create_joke_agent` factory | PII (Email) | `EscalateAction` — suspends for human review (HITL) |
| `@guardrail(validator=PIIValidator(...PERSON...))` | `create_joke_agent` factory | PII (Person) | `BlockAction` — blocks person names |
| `@guardrail(validator=PIIValidator(...PERSON...))` | `joke_node` graph node | PII (Person) | `BlockAction` — blocks person names in node input |
| `@guardrail(validator=CustomValidator(...))` | `format_joke_for_display` function | Custom (word check) | `CustomFilterAction` — replaces "donkey" in display output |
Expand Down Expand Up @@ -123,6 +124,15 @@ def analyze_joke_syntax(joke: str) -> str:
### Agent-level guardrail

```python
@guardrail(
validator=pii_email,
action=EscalateAction(
app_name=ESCALATION_APP_NAME,
app_folder_path=ESCALATION_APP_FOLDER,
),
name="Agent PII Escalation",
stage=GuardrailExecutionStage.PRE,
)
@guardrail(
validator=PIIValidator(
entities=[PIIDetectionEntity(PIIDetectionEntityType.PERSON, threshold=0.5)],
Expand All @@ -140,6 +150,19 @@ def create_joke_agent():
agent = create_joke_agent()
```

### Agent-level escalation (human-in-the-loop)

`EscalateAction` turns a guardrail violation into a human review step. On an EMAIL
PII violation in the agent input it suspends the run via the documented HITL
`interrupt(CreateEscalation(...))` primitive, creating a task in the **Guardrail
Escalation Action App**. A human approves (optionally editing the input) or
rejects; on resume the run continues with the reviewed input, or terminates on a
reject. The app name/folder default to the same deployment as the middleware
`joke-agent` and can be overridden with the `GUARDRAIL_ESCALATION_APP_NAME` /
`GUARDRAIL_ESCALATION_APP_FOLDER` environment variables. This is the
decorator-style counterpart of the middleware sample's AGENT-scope escalation —
no SDK change beyond the `@guardrail` action.

### Graph node guardrail

```python
Expand Down Expand Up @@ -239,6 +262,23 @@ uv run uipath run agent '{"topic": "donkey, test@example.com"}'

Both the agent-scope and LLM-scope PII guardrails log a `WARNING` when the email is detected. The tool-scope PII guardrail logs when the email reaches the tool input.

**Scenario 4 — agent-scope escalation (HITL):** supply a topic containing an email address:

```bash
uv run uipath run agent '{"topic": "a joke about a@b.com"}'
```

The `Agent PII Escalation` guardrail detects the email and suspends the run,
creating a review task in the Guardrail Escalation Action App. After a human
approves (or rejects) the task in Action Center, resume the run:

```bash
uv run uipath run agent --resume
```

On approve the agent continues with the reviewed input; on reject the run
terminates with a guardrail-violation error.

## Differences from the Middleware Approach (`joke-agent`)

| Aspect | Middleware (`joke-agent`) | Decorator (`joke-agent-decorator`) |
Expand All @@ -256,4 +296,5 @@ Both the agent-scope and LLM-scope PII guardrails log a `WARNING` when the email
- `"banana"` — normal run, all guardrails pass
- `"donkey"` — triggers the word filter on `analyze_joke_syntax`
- `"donkey, test@example.com"` — triggers word filter + PII guardrails at all scopes
- `"a joke about a@b.com"` — triggers the agent-scope escalation (suspends for human review)
- `"computer"`, `"coffee"`, `"pizza"`, `"weather"`
26 changes: 26 additions & 0 deletions samples/joke-agent-decorator/bindings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"version": "2.0",
"resources": [
{
"resource": "app",
"key": "Guardrail.Escalation.Action.App.2.Shared",
"value": {
"name": {
"defaultValue": "Guardrail.Escalation.Action.App.2",
"isExpression": false,
"displayName": "App Name"
},
"folderPath": {
"defaultValue": "Shared",
"isExpression": false,
"displayName": "App Folder Path"
}
},
"metadata": {
"ActivityName": "create_async",
"BindingsVersion": "2.2",
"DisplayLabel": "app_name"
}
}
]
}
19 changes: 19 additions & 0 deletions samples/joke-agent-decorator/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from uipath_langchain.guardrails import (
BlockAction,
CustomValidator,
EscalateAction,
GuardrailAction,
GuardrailExclude,
GuardrailExecutionStage,
Expand Down Expand Up @@ -258,6 +259,24 @@ def analyze_joke_syntax(joke: str) -> str:
# ---------------------------------------------------------------------------


# On an EMAIL PII violation in the agent input this escalates to the Guardrail
# Escalation Action App for human review via the documented HITL
# interrupt(CreateEscalation(...)) — the run suspends until a human approves
# (optionally editing the input) or rejects. PRE only, so it triggers once per
# run. This is the decorator-style counterpart of the middleware joke-agent's
# AGENT-scope PII escalation.
@guardrail(
validator=pii_email,
action=EscalateAction(
# Escalation Action App — declared as a binding in bindings.json (resource
# "app"). Studio/deploy resolves and can override it; locally these literal
# values are used.
app_name="Guardrail.Escalation.Action.App.2",
app_folder_path="Shared",
),
name="Agent PII Escalation",
stage=GuardrailExecutionStage.PRE,
)
@guardrail(
validator=HarmfulContentValidator(
entities=[HarmfulContentEntity(HarmfulContentEntityType.VIOLENCE, threshold=2)],
Expand Down
2 changes: 1 addition & 1 deletion samples/joke-agent-decorator/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = "Joke generating agent that creates family-friendly jokes based on
authors = [{ name = "John Doe", email = "john.doe@myemail.com" }]
requires-python = ">=3.11"
dependencies = [
"uipath-langchain>=0.9.26, <0.11.0",
"uipath-langchain>=0.11.13, <0.12.0",
"uipath>2.7.0",
]

Expand Down
Loading
Loading