From 29d090d551325e5c6e4fe3fa78d2cb61354085b7 Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Tue, 19 May 2026 17:07:11 +0300 Subject: [PATCH 1/8] docs: add LangChain guardrails documentation Covers the middleware pattern (UiPathPIIDetectionMiddleware, UiPathHarmfulContentMiddleware, UiPathUserPromptAttacksMiddleware, UiPathIntellectualPropertyMiddleware, UiPathDeterministicGuardrailMiddleware, custom hooks) and the decorator pattern extended to LangChain/LangGraph target types (LLM factory, agent factory, LangGraph nodes, @tool). Excludes deprecated PromptInjection middleware. Co-Authored-By: Claude Sonnet 4.6 --- docs/guardrails.md | 390 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 docs/guardrails.md diff --git a/docs/guardrails.md b/docs/guardrails.md new file mode 100644 index 000000000..c669b157b --- /dev/null +++ b/docs/guardrails.md @@ -0,0 +1,390 @@ +# Guardrails + +Guardrails inspect inputs and outputs for policy violations — PII, harmful content, prompt injection, intellectual property, and custom rules — and respond by logging, blocking, or modifying the data. + +The UiPath LangChain SDK exposes two complementary patterns: + +- **Middleware** — a LangChain-native approach. Guardrail classes are passed as a list to `create_agent(..., middleware=[...])`. Each class registers hooks (`before_agent`, `after_agent`, `before_model`, `after_model`, `wrap_tool_call`) automatically for the configured scope. +- **Decorator** — the same `@guardrail` API from the core SDK, extended to understand LangChain/LangGraph objects. Scope is inferred automatically from the decorated target type. + +For the full list of validators, actions, execution stages, and the low-level API, see the [core guardrails documentation](https://uipath.github.io/uipath-python/core/guardrails/). + +--- + +## Middleware pattern + +Middleware is the preferred approach when all guardrail configuration should live in one place, next to the `create_agent()` call. + +### How it works + +Each built-in middleware class is **iterable** — unpacking it with `*` yields one or more `AgentMiddleware` hook objects that LangChain registers internally. Pass them all inside the `middleware=[]` list: + +```python +from langchain.agents import create_agent + +agent = create_agent( + model=llm, + tools=[my_tool], + system_prompt=SYSTEM_PROMPT, + middleware=[ + *UiPathPIIDetectionMiddleware(...), + *UiPathHarmfulContentMiddleware(...), + # ... more middleware + ], +) +``` + +### Imports + +```python +from uipath_langchain.guardrails import ( + BlockAction, + LogAction, + LoggingSeverityLevel, + UiPathDeterministicGuardrailMiddleware, + UiPathHarmfulContentMiddleware, + UiPathIntellectualPropertyMiddleware, + UiPathPIIDetectionMiddleware, + UiPathUserPromptAttacksMiddleware, + PIIDetectionEntity, + HarmfulContentEntity, + GuardrailExecutionStage, +) +from uipath_langchain.guardrails.enums import ( + HarmfulContentEntityType, + IntellectualPropertyEntityType, + PIIDetectionEntityType, +) +from uipath.core.guardrails import GuardrailScope +``` + +### Built-in middleware classes + +| Class | Default scopes | Stage support | +|---|---|---| +| `UiPathPIIDetectionMiddleware` | AGENT + LLM | PRE + POST | +| `UiPathHarmfulContentMiddleware` | AGENT + LLM | PRE + POST | +| `UiPathUserPromptAttacksMiddleware` | LLM | PRE only | +| `UiPathIntellectualPropertyMiddleware` | LLM + AGENT | POST only | +| `UiPathDeterministicGuardrailMiddleware` | TOOL | PRE / POST / PRE_AND_POST | + +All classes share these common parameters: + +- **`name`** (`str`) — display name for this guardrail instance. +- **`action`** — what to do on violation: `LogAction(...)` or `BlockAction(...)`. +- **`scopes`** (`list[GuardrailScope]`) — restrict which hooks are registered. Defaults shown in the table above. Use `GuardrailScope.AGENT`, `GuardrailScope.LLM`, `GuardrailScope.TOOL`. +- **`enabled_for_evals`** (`bool`, default `True`) — set `False` to skip this guardrail when the agent runs in evaluation mode. + +Additional parameters per class: + +- **`UiPathPIIDetectionMiddleware`** / **`UiPathHarmfulContentMiddleware`**: `entities` (list of entity configs), `tools` (restrict TOOL-scope hooks to specific tools). +- **`UiPathUserPromptAttacksMiddleware`**: no extra parameters. +- **`UiPathIntellectualPropertyMiddleware`**: `entities` (list of `IntellectualPropertyEntityType` values). +- **`UiPathDeterministicGuardrailMiddleware`**: `tools` (required — list of tools to guard), `rules` (list of lambda functions), `stage`. + +### Full example + +```python +from langchain.agents import create_agent +from langchain_core.tools import tool +from uipath_langchain.chat import UiPathChat +from uipath_langchain.guardrails import ( + BlockAction, + LogAction, + LoggingSeverityLevel, + UiPathDeterministicGuardrailMiddleware, + UiPathHarmfulContentMiddleware, + UiPathIntellectualPropertyMiddleware, + UiPathPIIDetectionMiddleware, + UiPathUserPromptAttacksMiddleware, + PIIDetectionEntity, + HarmfulContentEntity, + GuardrailExecutionStage, +) +from uipath_langchain.guardrails.enums import ( + HarmfulContentEntityType, + IntellectualPropertyEntityType, + PIIDetectionEntityType, +) +from uipath.core.guardrails import GuardrailScope + + +@tool +def analyze_text(text: str) -> str: + """Count words in the provided text.""" + return f"Word count: {len(text.split())}" + + +llm = UiPathChat(model="gpt-4o-2024-08-06") + +agent = create_agent( + model=llm, + tools=[analyze_text], + system_prompt="You are a helpful assistant.", + middleware=[ + # PII detection on agent input/output and LLM messages + *UiPathPIIDetectionMiddleware( + name="PII detector", + scopes=[GuardrailScope.AGENT, GuardrailScope.LLM], + action=LogAction(severity_level=LoggingSeverityLevel.WARNING), + entities=[ + PIIDetectionEntity(PIIDetectionEntityType.EMAIL, threshold=0.5), + PIIDetectionEntity(PIIDetectionEntityType.CREDIT_CARD_NUMBER, threshold=0.5), + ], + ), + # PII detection restricted to TOOL scope for specific tools + *UiPathPIIDetectionMiddleware( + name="Tool PII detector", + scopes=[GuardrailScope.TOOL], + action=LogAction(severity_level=LoggingSeverityLevel.WARNING), + entities=[ + PIIDetectionEntity(PIIDetectionEntityType.PHONE_NUMBER, threshold=0.5), + ], + tools=[analyze_text], + enabled_for_evals=False, + ), + # Block adversarial user prompts at the LLM level + *UiPathUserPromptAttacksMiddleware( + name="User prompt attacks", + action=BlockAction(), + enabled_for_evals=False, + ), + # Block harmful content in agent + LLM messages + *UiPathHarmfulContentMiddleware( + name="Harmful content", + scopes=[GuardrailScope.AGENT, GuardrailScope.LLM], + action=BlockAction(), + entities=[ + HarmfulContentEntity(HarmfulContentEntityType.VIOLENCE, threshold=2), + ], + ), + # Log IP violations in LLM output (POST only) + *UiPathIntellectualPropertyMiddleware( + name="IP detection", + scopes=[GuardrailScope.LLM], + action=LogAction(severity_level=LoggingSeverityLevel.WARNING), + entities=[IntellectualPropertyEntityType.TEXT], + ), + # Deterministic: block tool input longer than 1000 chars + *UiPathDeterministicGuardrailMiddleware( + tools=[analyze_text], + rules=[lambda data: len(data.get("text", "")) > 1000], + action=BlockAction(detail="Input too long"), + stage=GuardrailExecutionStage.PRE, + name="Length limiter", + ), + ], +) +``` + +### Deterministic middleware + +`UiPathDeterministicGuardrailMiddleware` applies in-process rule functions without any API call. + +- **`rules`** — list of callables. For `PRE` stage: `(input_dict: dict) -> bool`. For `POST` stage: `(input_dict: dict, output_dict: dict) -> bool`. Return `True` to signal a violation. +- **`rules=[]`** (empty list) — always triggers the action, useful for unconditional transforms. +- **`stage`** — `GuardrailExecutionStage.PRE`, `POST`, or `PRE_AND_POST`. + +```python +# Always replace a word in tool output (unconditional transform, empty rules) +*UiPathDeterministicGuardrailMiddleware( + tools=[analyze_text], + rules=[], + action=CustomFilterAction(word_to_filter="count", replacement="total"), + stage=GuardrailExecutionStage.POST, + name="Output transformer", +), +``` + +### Custom middleware hooks + +For logic that doesn't fit a built-in class, implement raw middleware hooks using the `langchain.agents.middleware` decorators and pass them directly to the middleware list. + +Available hook decorators: `before_agent`, `after_agent`, `before_model`, `after_model`, `wrap_tool_call`. + +```python +from langchain.agents.middleware import AgentState, before_agent, after_agent +from langchain_core.messages import AIMessage, HumanMessage +from langgraph.runtime import Runtime + + +@before_agent +async def log_agent_input(state: AgentState, runtime: Runtime) -> None: + for msg in reversed(state.get("messages", [])): + if isinstance(msg, HumanMessage): + print(f"[INPUT] {msg.content}") + break + + +@after_agent +async def log_agent_output(state: AgentState, runtime: Runtime) -> None: + for msg in reversed(state.get("messages", [])): + if isinstance(msg, AIMessage): + print(f"[OUTPUT] {msg.content}") + break + + +LoggingMiddleware = [log_agent_input, log_agent_output] + +agent = create_agent( + model=llm, + tools=[analyze_text], + middleware=[ + *LoggingMiddleware, + *UiPathPIIDetectionMiddleware(...), + ], +) +``` + +### Custom actions + +Both the built-in middleware and `UiPathDeterministicGuardrailMiddleware` accept any `GuardrailAction` subclass as the `action` parameter. This lets you implement content sanitisation, redaction, or any other custom response to a violation: + +```python +import re +from dataclasses import dataclass +from typing import Any +from uipath.core.guardrails import GuardrailValidationResult, GuardrailValidationResultType +from uipath_langchain.guardrails import GuardrailAction + + +@dataclass +class RedactAction(GuardrailAction): + pattern: str + replacement: str = "[REDACTED]" + + def handle_validation_result( + self, + result: GuardrailValidationResult, + data: str | dict[str, Any], + guardrail_name: str, + ) -> str | dict[str, Any] | None: + if result.result != GuardrailValidationResultType.VALIDATION_FAILED: + return None + if isinstance(data, str): + return re.sub(self.pattern, self.replacement, data, flags=re.IGNORECASE) + return None +``` + +--- + +## Decorator pattern + +Importing `uipath_langchain.guardrails` auto-registers a LangChain adapter that extends `@guardrail` to wrap LangChain/LangGraph objects in addition to plain Python callables. All validators, actions, execution stages, stacking, and `GuardrailExclude` work exactly as described in the [core guardrails documentation](https://uipath.github.io/uipath-python/core/guardrails/) — use `uipath_langchain.guardrails` as the import path, which re-exports everything from the core SDK. + +```python +from uipath_langchain.guardrails import guardrail, PIIValidator, BlockAction, ... +``` + +### LangChain target types + +On LangChain objects, scope is **inferred automatically** from the target — no `stage` inference, but no explicit `scopes=` parameter is needed either: + +| Decorated target | Inferred scope | Notes | +|---|---|---| +| `@tool` function | TOOL | `@guardrail` must be placed **above** `@tool` | +| `BaseChatModel` factory | LLM | Decorate the factory function, not the model instance | +| `create_agent()` factory | AGENT | Decorate the factory function, not the returned agent | +| LangGraph node (async fn) | AGENT | — | + +### LLM factory + +```python +from uipath_langchain.chat import UiPathChat +from uipath_langchain.guardrails import guardrail, PIIValidator, BlockAction, GuardrailExecutionStage, PIIDetectionEntity +from uipath_langchain.guardrails.enums import PIIDetectionEntityType + +@guardrail( + validator=PIIValidator( + entities=[PIIDetectionEntity(PIIDetectionEntityType.EMAIL, threshold=0.5)], + ), + action=BlockAction(), + name="LLM PII check", + stage=GuardrailExecutionStage.PRE, +) +def create_llm(): + return UiPathChat(model="gpt-4o-2024-08-06") + +llm = create_llm() +``` + +### Tool + +`@guardrail` must be placed **above** `@tool`: + +```python +from langchain_core.tools import tool +from uipath_langchain.guardrails import guardrail, CustomValidator, BlockAction, GuardrailExecutionStage + +@guardrail( + validator=CustomValidator(lambda args: len(args.get("text", "")) > 1000), + action=BlockAction(detail="Text exceeds 1000 characters"), + stage=GuardrailExecutionStage.PRE, + name="Length limiter", +) +@tool +def analyze_text(text: str) -> str: + """Count words in the provided text.""" + return f"Word count: {len(text.split())}" +``` + +### Agent factory + +```python +from langchain.agents import create_agent +from uipath_langchain.guardrails import guardrail, HarmfulContentValidator, BlockAction, GuardrailExecutionStage, HarmfulContentEntity +from uipath_langchain.guardrails.enums import HarmfulContentEntityType + +@guardrail( + validator=HarmfulContentValidator( + entities=[HarmfulContentEntity(HarmfulContentEntityType.VIOLENCE, threshold=2)], + ), + action=BlockAction(), + name="Block harmful content", + stage=GuardrailExecutionStage.PRE, +) +def create_my_agent(): + return create_agent(model=llm, tools=[analyze_text], system_prompt="...") + +agent = create_my_agent() +``` + +### LangGraph node + +```python +from uipath_langchain.guardrails import guardrail, PIIValidator, BlockAction, GuardrailExecutionStage, PIIDetectionEntity +from uipath_langchain.guardrails.enums import PIIDetectionEntityType + +@guardrail( + validator=PIIValidator( + entities=[PIIDetectionEntity(PIIDetectionEntityType.PERSON, threshold=0.5)], + ), + action=BlockAction(title="Person name in input"), + name="Node PII check", + stage=GuardrailExecutionStage.PRE, +) +async def my_node(state: Input) -> Output: + ... +``` + +--- + +## Choosing between middleware and decorator + +| | Middleware | Decorator | +|---|---|---| +| Configuration location | Centralised in `create_agent()` | Per-target, co-located with the object | +| Scope specification | Explicit `scopes=` parameter | Auto-inferred from the decorated type | +| Works outside `create_agent` | No | Yes — any LangGraph graph or plain function | +| Reusable validator objects | No | Yes — declare once, use in multiple decorators | +| Parameter exclusion | No | `GuardrailExclude()` annotation | +| Custom deterministic rules | `UiPathDeterministicGuardrailMiddleware(rules=[...])` | `CustomValidator(lambda ...)` | + +Use **middleware** when you want all guardrail policy in one place alongside a single `create_agent()` call. Use **decorators** when building custom LangGraph graphs, reusing validators across multiple agents, or guarding code outside the agent context. + +--- + +## Sample agents + +- [`samples/joke-agent`](https://github.com/UiPath/uipath-langchain-python/tree/main/samples/joke-agent) — middleware pattern +- [`samples/joke-agent-decorator`](https://github.com/UiPath/uipath-langchain-python/tree/main/samples/joke-agent-decorator) — decorator pattern From 926b8784dbb112c69df29dec6bbd748875cd97e9 Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Thu, 21 May 2026 16:47:58 +0300 Subject: [PATCH 2/8] docs(guardrails): add reference tables and reflect PR #868 TOOL scope fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add UiPathPromptInjectionMiddleware to middleware table and imports (threshold float 0–1) - Clarify that PII and HarmfulContent support TOOL scope (fixed in PR #868 via _run_tool_guardrail) - Reformat middleware table with Supported scopes / Stage support / Extra parameters columns - Add ## Reference section with complete value tables for GuardrailScope, GuardrailExecutionStage, LoggingSeverityLevel, PIIDetectionEntityType (19 values), HarmfulContentEntityType, and IntellectualPropertyEntityType including threshold constraints Co-Authored-By: Claude Sonnet 4.6 --- docs/guardrails.md | 100 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/docs/guardrails.md b/docs/guardrails.md index c669b157b..7d35f1933 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -45,6 +45,7 @@ from uipath_langchain.guardrails import ( UiPathHarmfulContentMiddleware, UiPathIntellectualPropertyMiddleware, UiPathPIIDetectionMiddleware, + UiPathPromptInjectionMiddleware, UiPathUserPromptAttacksMiddleware, PIIDetectionEntity, HarmfulContentEntity, @@ -60,13 +61,16 @@ from uipath.core.guardrails import GuardrailScope ### Built-in middleware classes -| Class | Default scopes | Stage support | -|---|---|---| -| `UiPathPIIDetectionMiddleware` | AGENT + LLM | PRE + POST | -| `UiPathHarmfulContentMiddleware` | AGENT + LLM | PRE + POST | -| `UiPathUserPromptAttacksMiddleware` | LLM | PRE only | -| `UiPathIntellectualPropertyMiddleware` | LLM + AGENT | POST only | -| `UiPathDeterministicGuardrailMiddleware` | TOOL | PRE / POST / PRE_AND_POST | +| Class | Supported scopes | Stage support | Extra parameters | +|---|---|---|---| +| `UiPathPIIDetectionMiddleware` | AGENT, LLM, TOOL | PRE + POST | `entities`, `tools` | +| `UiPathHarmfulContentMiddleware` | AGENT, LLM, TOOL | PRE + POST | `entities`, `tools` | +| `UiPathPromptInjectionMiddleware` | LLM only | PRE only | `threshold` (float 0–1, default 0.5) | +| `UiPathUserPromptAttacksMiddleware` | LLM only | PRE only | — | +| `UiPathIntellectualPropertyMiddleware` | AGENT, LLM only | POST only | `entities` | +| `UiPathDeterministicGuardrailMiddleware` | TOOL only | PRE / POST / PRE_AND_POST | `tools`, `rules`, `stage` | + +TOOL scope for `UiPathPIIDetectionMiddleware` and `UiPathHarmfulContentMiddleware` requires passing `tools=[...]` to restrict `wrap_tool_call` hooks to specific tools. All classes share these common parameters: @@ -96,6 +100,7 @@ from uipath_langchain.guardrails import ( UiPathHarmfulContentMiddleware, UiPathIntellectualPropertyMiddleware, UiPathPIIDetectionMiddleware, + UiPathPromptInjectionMiddleware, UiPathUserPromptAttacksMiddleware, PIIDetectionEntity, HarmfulContentEntity, @@ -388,3 +393,84 @@ Use **middleware** when you want all guardrail policy in one place alongside a s - [`samples/joke-agent`](https://github.com/UiPath/uipath-langchain-python/tree/main/samples/joke-agent) — middleware pattern - [`samples/joke-agent-decorator`](https://github.com/UiPath/uipath-langchain-python/tree/main/samples/joke-agent-decorator) — decorator pattern + +--- + +## Reference + +### GuardrailScope + +Imported from `uipath.core.guardrails`. + +| Value | Description | +|---|---| +| `AGENT` | Hooks run at agent input/output boundary (`before_agent` / `after_agent`) | +| `LLM` | Hooks run around every LLM call (`before_model` / `after_model`) | +| `TOOL` | Hooks run around every tool call (`wrap_tool_call`) | + +### GuardrailExecutionStage + +Imported from `uipath_langchain.guardrails`. + +| Value | When it fires | +|---|---| +| `PRE` | Before the call (inspect / block inputs) | +| `POST` | After the call (inspect / transform outputs) | +| `PRE_AND_POST` | Both — used only by `UiPathDeterministicGuardrailMiddleware` | + +### LoggingSeverityLevel + +Used in `LogAction(severity_level=...)`. Imported from `uipath_langchain.guardrails`. + +| Value | +|---| +| `DEBUG` | +| `INFO` | +| `WARNING` | +| `ERROR` | + +### PIIDetectionEntityType + +Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `PIIDetectionEntity(entity_type, threshold=0.5)` — `threshold` is a float from `0.0` to `1.0`. + +| Value | +|---| +| `PERSON` | +| `ADDRESS` | +| `DATE` | +| `PHONE_NUMBER` | +| `EUGPS_COORDINATES` | +| `EMAIL` | +| `CREDIT_CARD_NUMBER` | +| `INTERNATIONAL_BANKING_ACCOUNT_NUMBER` | +| `SWIFT_CODE` | +| `ABA_ROUTING_NUMBER` | +| `US_DRIVERS_LICENSE_NUMBER` | +| `UK_DRIVERS_LICENSE_NUMBER` | +| `US_INDIVIDUAL_TAXPAYER_IDENTIFICATION` | +| `UK_UNIQUE_TAXPAYER_NUMBER` | +| `US_BANK_ACCOUNT_NUMBER` | +| `US_SOCIAL_SECURITY_NUMBER` | +| `USUK_PASSPORT_NUMBER` | +| `URL` | +| `IP_ADDRESS` | + +### HarmfulContentEntityType + +Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `HarmfulContentEntity(entity_type, threshold=2)` — `threshold` must be one of `0`, `2`, `4`, or `6` (higher = less sensitive). + +| Value | +|---| +| `HATE` | +| `SELF_HARM` | +| `SEXUAL` | +| `VIOLENCE` | + +### IntellectualPropertyEntityType + +Imported from `uipath_langchain.guardrails.enums`. Pass values directly in `entities=[...]` — no wrapper model class. + +| Value | +|---| +| `TEXT` | +| `CODE` | From 2873fb2897538e93efc7130755d8afdd029182cc Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Thu, 21 May 2026 17:52:47 +0300 Subject: [PATCH 3/8] docs(guardrails): remove UiPathPromptInjectionMiddleware, normalize stage notation to PRE / POST Co-Authored-By: Claude Sonnet 4.6 --- docs/guardrails.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/guardrails.md b/docs/guardrails.md index 7d35f1933..d34e7fa71 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -45,7 +45,6 @@ from uipath_langchain.guardrails import ( UiPathHarmfulContentMiddleware, UiPathIntellectualPropertyMiddleware, UiPathPIIDetectionMiddleware, - UiPathPromptInjectionMiddleware, UiPathUserPromptAttacksMiddleware, PIIDetectionEntity, HarmfulContentEntity, @@ -63,9 +62,8 @@ from uipath.core.guardrails import GuardrailScope | Class | Supported scopes | Stage support | Extra parameters | |---|---|---|---| -| `UiPathPIIDetectionMiddleware` | AGENT, LLM, TOOL | PRE + POST | `entities`, `tools` | -| `UiPathHarmfulContentMiddleware` | AGENT, LLM, TOOL | PRE + POST | `entities`, `tools` | -| `UiPathPromptInjectionMiddleware` | LLM only | PRE only | `threshold` (float 0–1, default 0.5) | +| `UiPathPIIDetectionMiddleware` | AGENT, LLM, TOOL | PRE / POST | `entities`, `tools` | +| `UiPathHarmfulContentMiddleware` | AGENT, LLM, TOOL | PRE / POST | `entities`, `tools` | | `UiPathUserPromptAttacksMiddleware` | LLM only | PRE only | — | | `UiPathIntellectualPropertyMiddleware` | AGENT, LLM only | POST only | `entities` | | `UiPathDeterministicGuardrailMiddleware` | TOOL only | PRE / POST / PRE_AND_POST | `tools`, `rules`, `stage` | @@ -100,7 +98,6 @@ from uipath_langchain.guardrails import ( UiPathHarmfulContentMiddleware, UiPathIntellectualPropertyMiddleware, UiPathPIIDetectionMiddleware, - UiPathPromptInjectionMiddleware, UiPathUserPromptAttacksMiddleware, PIIDetectionEntity, HarmfulContentEntity, From b489486ab00cf490c9586593a3bc3deea067f812 Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Thu, 21 May 2026 18:06:43 +0300 Subject: [PATCH 4/8] docs(guardrails): auto-generate enum value tables from installed package Add scripts/gen_guardrail_enums.py that regenerates the PIIDetectionEntityType, HarmfulContentEntityType, and IntellectualPropertyEntityType value tables in docs/guardrails.md by reading the installed uipath package at runtime. Tables are wrapped with markers so the script can target them precisely. A local pre-commit hook wires the script to run automatically whenever docs/guardrails.md or enums.py is staged, ensuring the tables stay in sync when new entity types are added upstream. Co-Authored-By: Claude Sonnet 4.6 --- .pre-commit-config.yaml | 8 ++++++ docs/guardrails.md | 6 +++++ scripts/gen_guardrail_enums.py | 46 ++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 scripts/gen_guardrail_enums.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d584e5017..9bab67ad9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,3 +5,11 @@ repos: - id: ruff args: [ --fix ] - id: ruff-format +- repo: local + hooks: + - id: gen-guardrail-enums + name: Regenerate guardrail enum tables from code + language: python + entry: python scripts/gen_guardrail_enums.py + files: ^(docs/guardrails\.md|src/uipath_langchain/guardrails/enums\.py)$ + pass_filenames: false diff --git a/docs/guardrails.md b/docs/guardrails.md index d34e7fa71..78b37b61b 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -430,6 +430,7 @@ Used in `LogAction(severity_level=...)`. Imported from `uipath_langchain.guardra Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `PIIDetectionEntity(entity_type, threshold=0.5)` — `threshold` is a float from `0.0` to `1.0`. + | Value | |---| | `PERSON` | @@ -451,23 +452,28 @@ Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `PIIDetect | `USUK_PASSPORT_NUMBER` | | `URL` | | `IP_ADDRESS` | + ### HarmfulContentEntityType Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `HarmfulContentEntity(entity_type, threshold=2)` — `threshold` must be one of `0`, `2`, `4`, or `6` (higher = less sensitive). + | Value | |---| | `HATE` | | `SELF_HARM` | | `SEXUAL` | | `VIOLENCE` | + ### IntellectualPropertyEntityType Imported from `uipath_langchain.guardrails.enums`. Pass values directly in `entities=[...]` — no wrapper model class. + | Value | |---| | `TEXT` | | `CODE` | + diff --git a/scripts/gen_guardrail_enums.py b/scripts/gen_guardrail_enums.py new file mode 100644 index 000000000..e05e9cf35 --- /dev/null +++ b/scripts/gen_guardrail_enums.py @@ -0,0 +1,46 @@ +"""Regenerates enum value tables in docs/guardrails.md from the installed uipath package. + +Run manually: python scripts/gen_guardrail_enums.py +Also runs automatically as a pre-commit hook when docs/guardrails.md or enums.py is staged. +""" + +import pathlib +import re +import sys + +from uipath_langchain.guardrails.enums import ( + HarmfulContentEntityType, + IntellectualPropertyEntityType, + PIIDetectionEntityType, +) + +ENUMS = [PIIDetectionEntityType, HarmfulContentEntityType, IntellectualPropertyEntityType] +DOCS = pathlib.Path(__file__).parent.parent / "docs" / "guardrails.md" + + +def make_table(enum_cls): + rows = "\n".join(f"| `{m.name}` |" for m in enum_cls) + return f"| Value |\n|---|\n{rows}" + + +def regenerate(content, enum_cls): + name = enum_cls.__name__ + table = make_table(enum_cls) + marker = rf"().*?()" + replacement = rf"\1\n{table}\n\2" + new, n = re.subn(marker, replacement, content, flags=re.DOTALL) + if n == 0: + print(f"WARNING: markers for {name} not found in {DOCS}", file=sys.stderr) + return new + + +def main(): + text = DOCS.read_text() + for cls in ENUMS: + text = regenerate(text, cls) + DOCS.write_text(text) + print(f"Regenerated enum tables in {DOCS}") + + +if __name__ == "__main__": + main() From ae69d372bc9fe65aa4a1e8448354e1e7274e48bc Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Fri, 22 May 2026 00:30:56 +0300 Subject: [PATCH 5/8] style: ruff format gen_guardrail_enums.py Co-Authored-By: Claude Sonnet 4.6 --- scripts/gen_guardrail_enums.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/gen_guardrail_enums.py b/scripts/gen_guardrail_enums.py index e05e9cf35..7b7a9a060 100644 --- a/scripts/gen_guardrail_enums.py +++ b/scripts/gen_guardrail_enums.py @@ -14,7 +14,11 @@ PIIDetectionEntityType, ) -ENUMS = [PIIDetectionEntityType, HarmfulContentEntityType, IntellectualPropertyEntityType] +ENUMS = [ + PIIDetectionEntityType, + HarmfulContentEntityType, + IntellectualPropertyEntityType, +] DOCS = pathlib.Path(__file__).parent.parent / "docs" / "guardrails.md" From 99db79214a729116e7d622820cc369a43f222a50 Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Fri, 22 May 2026 12:05:25 +0300 Subject: [PATCH 6/8] docs(guardrails): use mkdocstrings directives for enum reference sections Replace generated markdown tables (and the gen script + pre-commit hook) with ::: directives for PIIDetectionEntityType, HarmfulContentEntityType, and IntellectualPropertyEntityType. The parent uipath-python docs site already uses mkdocstrings[python] and symlinks these docs at build time, so enum members are rendered directly from the installed package with no manual maintenance needed. Co-Authored-By: Claude Sonnet 4.6 --- .pre-commit-config.yaml | 8 ----- docs/guardrails.md | 58 +++++++++++----------------------- scripts/gen_guardrail_enums.py | 50 ----------------------------- 3 files changed, 18 insertions(+), 98 deletions(-) delete mode 100644 scripts/gen_guardrail_enums.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9bab67ad9..d584e5017 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,11 +5,3 @@ repos: - id: ruff args: [ --fix ] - id: ruff-format -- repo: local - hooks: - - id: gen-guardrail-enums - name: Regenerate guardrail enum tables from code - language: python - entry: python scripts/gen_guardrail_enums.py - files: ^(docs/guardrails\.md|src/uipath_langchain/guardrails/enums\.py)$ - pass_filenames: false diff --git a/docs/guardrails.md b/docs/guardrails.md index 78b37b61b..4cc3f3b01 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -428,52 +428,30 @@ Used in `LogAction(severity_level=...)`. Imported from `uipath_langchain.guardra ### PIIDetectionEntityType -Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `PIIDetectionEntity(entity_type, threshold=0.5)` — `threshold` is a float from `0.0` to `1.0`. +Wrap each value in `PIIDetectionEntity(entity_type, threshold=0.5)` — `threshold` is a float from `0.0` to `1.0`. - -| Value | -|---| -| `PERSON` | -| `ADDRESS` | -| `DATE` | -| `PHONE_NUMBER` | -| `EUGPS_COORDINATES` | -| `EMAIL` | -| `CREDIT_CARD_NUMBER` | -| `INTERNATIONAL_BANKING_ACCOUNT_NUMBER` | -| `SWIFT_CODE` | -| `ABA_ROUTING_NUMBER` | -| `US_DRIVERS_LICENSE_NUMBER` | -| `UK_DRIVERS_LICENSE_NUMBER` | -| `US_INDIVIDUAL_TAXPAYER_IDENTIFICATION` | -| `UK_UNIQUE_TAXPAYER_NUMBER` | -| `US_BANK_ACCOUNT_NUMBER` | -| `US_SOCIAL_SECURITY_NUMBER` | -| `USUK_PASSPORT_NUMBER` | -| `URL` | -| `IP_ADDRESS` | - +::: uipath_langchain.guardrails.enums.PIIDetectionEntityType + options: + show_root_heading: false + show_source: false + members_order: source ### HarmfulContentEntityType -Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `HarmfulContentEntity(entity_type, threshold=2)` — `threshold` must be one of `0`, `2`, `4`, or `6` (higher = less sensitive). +Wrap each value in `HarmfulContentEntity(entity_type, threshold=2)` — `threshold` must be one of `0`, `2`, `4`, or `6` (higher = less sensitive). - -| Value | -|---| -| `HATE` | -| `SELF_HARM` | -| `SEXUAL` | -| `VIOLENCE` | - +::: uipath_langchain.guardrails.enums.HarmfulContentEntityType + options: + show_root_heading: false + show_source: false + members_order: source ### IntellectualPropertyEntityType -Imported from `uipath_langchain.guardrails.enums`. Pass values directly in `entities=[...]` — no wrapper model class. +Pass values directly in `entities=[...]` — no wrapper model class. - -| Value | -|---| -| `TEXT` | -| `CODE` | - +::: uipath_langchain.guardrails.enums.IntellectualPropertyEntityType + options: + show_root_heading: false + show_source: false + members_order: source diff --git a/scripts/gen_guardrail_enums.py b/scripts/gen_guardrail_enums.py deleted file mode 100644 index 7b7a9a060..000000000 --- a/scripts/gen_guardrail_enums.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Regenerates enum value tables in docs/guardrails.md from the installed uipath package. - -Run manually: python scripts/gen_guardrail_enums.py -Also runs automatically as a pre-commit hook when docs/guardrails.md or enums.py is staged. -""" - -import pathlib -import re -import sys - -from uipath_langchain.guardrails.enums import ( - HarmfulContentEntityType, - IntellectualPropertyEntityType, - PIIDetectionEntityType, -) - -ENUMS = [ - PIIDetectionEntityType, - HarmfulContentEntityType, - IntellectualPropertyEntityType, -] -DOCS = pathlib.Path(__file__).parent.parent / "docs" / "guardrails.md" - - -def make_table(enum_cls): - rows = "\n".join(f"| `{m.name}` |" for m in enum_cls) - return f"| Value |\n|---|\n{rows}" - - -def regenerate(content, enum_cls): - name = enum_cls.__name__ - table = make_table(enum_cls) - marker = rf"().*?()" - replacement = rf"\1\n{table}\n\2" - new, n = re.subn(marker, replacement, content, flags=re.DOTALL) - if n == 0: - print(f"WARNING: markers for {name} not found in {DOCS}", file=sys.stderr) - return new - - -def main(): - text = DOCS.read_text() - for cls in ENUMS: - text = regenerate(text, cls) - DOCS.write_text(text) - print(f"Regenerated enum tables in {DOCS}") - - -if __name__ == "__main__": - main() From 3456efb6f5a466f12767098c69d8a47756d241af Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Fri, 22 May 2026 13:31:29 +0300 Subject: [PATCH 7/8] docs(guardrails): replace mkdocstrings directives with MkDocs hook for enum tables Replace ::: directives (which rendered verbosely as class-attribute blocks) with placeholders. A MkDocs on_page_markdown hook (docs/hooks/enum_lists.py) expands each placeholder into a | Value | table by importing the enum class at build time, giving the same table format as LoggingSeverityLevel with zero static content to maintain. Co-Authored-By: Claude Sonnet 4.6 --- docs/guardrails.md | 24 ++++++------------------ docs/hooks/enum_lists.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 docs/hooks/enum_lists.py diff --git a/docs/guardrails.md b/docs/guardrails.md index 4cc3f3b01..18fe58045 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -428,30 +428,18 @@ Used in `LogAction(severity_level=...)`. Imported from `uipath_langchain.guardra ### PIIDetectionEntityType -Wrap each value in `PIIDetectionEntity(entity_type, threshold=0.5)` — `threshold` is a float from `0.0` to `1.0`. +Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `PIIDetectionEntity(entity_type, threshold=0.5)` — `threshold` is a float from `0.0` to `1.0`. -::: uipath_langchain.guardrails.enums.PIIDetectionEntityType - options: - show_root_heading: false - show_source: false - members_order: source + ### HarmfulContentEntityType -Wrap each value in `HarmfulContentEntity(entity_type, threshold=2)` — `threshold` must be one of `0`, `2`, `4`, or `6` (higher = less sensitive). +Imported from `uipath_langchain.guardrails.enums`. Wrap each value in `HarmfulContentEntity(entity_type, threshold=2)` — `threshold` must be one of `0`, `2`, `4`, or `6` (higher = less sensitive). -::: uipath_langchain.guardrails.enums.HarmfulContentEntityType - options: - show_root_heading: false - show_source: false - members_order: source + ### IntellectualPropertyEntityType -Pass values directly in `entities=[...]` — no wrapper model class. +Imported from `uipath_langchain.guardrails.enums`. Pass values directly in `entities=[...]` — no wrapper model class. -::: uipath_langchain.guardrails.enums.IntellectualPropertyEntityType - options: - show_root_heading: false - show_source: false - members_order: source + diff --git a/docs/hooks/enum_lists.py b/docs/hooks/enum_lists.py new file mode 100644 index 000000000..d84295d3f --- /dev/null +++ b/docs/hooks/enum_lists.py @@ -0,0 +1,17 @@ +"""MkDocs build hook: expand into a markdown table.""" + +import importlib +import re + +PLACEHOLDER = re.compile(r"") + + +def on_page_markdown(markdown, **kwargs): + def replace(match): + dotted = match.group(1) + module_path, _, cls_name = dotted.rpartition(".") + enum_cls = getattr(importlib.import_module(module_path), cls_name) + rows = "\n".join(f"| `{e.name}` |" for e in enum_cls) + return f"| Value |\n|---|\n{rows}" + + return PLACEHOLDER.sub(replace, markdown) From 9503a9b52401a6cc5e92cdf0fbcfd38e3577e1c9 Mon Sep 17 00:00:00 2001 From: Andrei Petraru Date: Fri, 22 May 2026 15:50:22 +0300 Subject: [PATCH 8/8] docs(guardrails): fix stage support for PII and HarmfulContent middleware Both classes accept a stage parameter (PRE / POST / PRE_AND_POST, default PRE_AND_POST) for TOOL scope, matching the implementation in _base.py and the usage in samples/joke-agent. Update table and parameter description. Co-Authored-By: Claude Sonnet 4.6 --- docs/guardrails.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guardrails.md b/docs/guardrails.md index 18fe58045..c5f733c74 100644 --- a/docs/guardrails.md +++ b/docs/guardrails.md @@ -62,8 +62,8 @@ from uipath.core.guardrails import GuardrailScope | Class | Supported scopes | Stage support | Extra parameters | |---|---|---|---| -| `UiPathPIIDetectionMiddleware` | AGENT, LLM, TOOL | PRE / POST | `entities`, `tools` | -| `UiPathHarmfulContentMiddleware` | AGENT, LLM, TOOL | PRE / POST | `entities`, `tools` | +| `UiPathPIIDetectionMiddleware` | AGENT, LLM, TOOL | PRE / POST / PRE_AND_POST | `entities`, `tools`, `stage` | +| `UiPathHarmfulContentMiddleware` | AGENT, LLM, TOOL | PRE / POST / PRE_AND_POST | `entities`, `tools`, `stage` | | `UiPathUserPromptAttacksMiddleware` | LLM only | PRE only | — | | `UiPathIntellectualPropertyMiddleware` | AGENT, LLM only | POST only | `entities` | | `UiPathDeterministicGuardrailMiddleware` | TOOL only | PRE / POST / PRE_AND_POST | `tools`, `rules`, `stage` | @@ -79,7 +79,7 @@ All classes share these common parameters: Additional parameters per class: -- **`UiPathPIIDetectionMiddleware`** / **`UiPathHarmfulContentMiddleware`**: `entities` (list of entity configs), `tools` (restrict TOOL-scope hooks to specific tools). +- **`UiPathPIIDetectionMiddleware`** / **`UiPathHarmfulContentMiddleware`**: `entities` (list of entity configs), `tools` (restrict TOOL-scope hooks to specific tools), `stage`. - **`UiPathUserPromptAttacksMiddleware`**: no extra parameters. - **`UiPathIntellectualPropertyMiddleware`**: `entities` (list of `IntellectualPropertyEntityType` values). - **`UiPathDeterministicGuardrailMiddleware`**: `tools` (required — list of tools to guard), `rules` (list of lambda functions), `stage`.