A simple LangGraph agent that generates family-friendly jokes based on a given topic using UiPath's LLM. This sample demonstrates all three guardrail decorator types — PII, Prompt Injection, and Deterministic — applied directly to the LLM, agent, and tool without a middleware stack.
- Python 3.11+
uv venv -p 3.11 .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv syncRun the joke agent:
uv run uipath run agent '{"topic": "banana"}'{
"topic": "banana"
}{
"joke": "Why did the banana go to the doctor? Because it wasn't peeling well!"
}This sample achieves full parity with the middleware-based joke-agent sample using only decorators. The table below shows which scope each guardrail covers:
| Decorator | Target | Scope | Action |
|---|---|---|---|
@prompt_injection_guardrail |
create_llm factory |
LLM | BlockAction — blocks on detection |
@pii_detection_guardrail |
create_llm factory |
LLM | LogAction(WARNING) — logs and continues |
@pii_detection_guardrail |
analyze_joke_syntax tool |
TOOL | LogAction(WARNING) — logs email/phone |
@deterministic_guardrail |
analyze_joke_syntax tool |
TOOL (PRE) | CustomFilterAction — replaces "donkey" with "[censored]" |
@deterministic_guardrail |
analyze_joke_syntax tool |
TOOL (PRE) | BlockAction — blocks jokes > 1000 chars |
@deterministic_guardrail |
analyze_joke_syntax tool |
TOOL (POST) | CustomFilterAction — always-on output transform |
@pii_detection_guardrail |
create_joke_agent factory |
AGENT | LogAction(WARNING) — logs agent-level PII |
Stacked decorators on a factory function. The outermost decorator runs first:
@prompt_injection_guardrail(
threshold=0.5,
action=BlockAction(),
name="LLM Prompt Injection Detection",
enabled_for_evals=False, # default is True
)
@pii_detection_guardrail(
entities=[PIIDetectionEntity(PIIDetectionEntityType.EMAIL, 0.5)],
action=LogAction(severity_level=LoggingSeverityLevel.WARNING),
name="LLM PII Detection",
)
def create_llm():
return UiPathChat(model="gpt-4o-2024-08-06", temperature=0.7)
llm = create_llm()@deterministic_guardrail applies local rule functions — no UiPath API call. Rules receive the tool input dict and return True to signal a violation. @pii_detection_guardrail at TOOL scope evaluates via the UiPath guardrails API.
@deterministic_guardrail(
rules=[lambda args: "donkey" in args.get("joke", "").lower()],
action=CustomFilterAction(word_to_filter="donkey", replacement="[censored]"),
stage=GuardrailExecutionStage.PRE,
name="Joke Content Word Filter",
enabled_for_evals=False, # default is True
)
@deterministic_guardrail(
rules=[lambda args: len(args.get("joke", "")) > 1000],
action=BlockAction(),
stage=GuardrailExecutionStage.PRE,
name="Joke Content Length Limiter",
)
@deterministic_guardrail(
rules=[], # empty rules = always apply (unconditional transform)
action=CustomFilterAction(word_to_filter="words", replacement="words++"),
stage=GuardrailExecutionStage.POST,
name="Joke Content Always Filter",
)
@pii_detection_guardrail(
entities=[
PIIDetectionEntity(PIIDetectionEntityType.EMAIL, 0.5),
PIIDetectionEntity(PIIDetectionEntityType.PHONE_NUMBER, 0.5),
],
action=LogAction(severity_level=LoggingSeverityLevel.WARNING),
name="Tool PII Detection",
)
@tool
def analyze_joke_syntax(joke: str) -> str:
...@pii_detection_guardrail(
entities=[PIIDetectionEntity(PIIDetectionEntityType.EMAIL, 0.5)],
action=LogAction(
severity_level=LoggingSeverityLevel.WARNING,
message="PII detected from agent guardrails decorator",
),
name="Agent PII Detection",
enabled_for_evals=False, # default is True
)
def create_joke_agent():
return create_agent(model=llm, tools=[analyze_joke_syntax], ...)
agent = create_joke_agent()CustomFilterAction (defined locally in graph.py) demonstrates how to implement a custom GuardrailAction. When a violation is detected it replaces the offending word in the tool input dict or string, logs the change, then returns the modified data so execution continues with the sanitised input:
@dataclass
class CustomFilterAction(GuardrailAction):
word_to_filter: str
replacement: str = "***"
def handle_validation_result(self, result, data, guardrail_name):
# filter word from dict/str and return modified data
...- A rule with 1 parameter receives the tool input dict (
PREstage). - A rule with 2 parameters receives
(input_dict, output_dict)(POSTstage). - A rule returns
Trueto signal a violation,Falseto pass. - All rules must detect a violation for the guardrail to trigger. If any rule passes, the guardrail passes.
- Empty
rules=[]always triggers the action (useful for unconditional transforms).
All decorator guardrails accept enabled_for_evals (default True). Set it to False
when you want runtime guardrail behavior but do not want that guardrail enabled for eval scenarios.
To manually verify each guardrail fires, run from this directory:
uv run uipath run agent '{"topic": "donkey"}'Scenario 1 — word filter (PRE): the LLM includes "donkey" in the joke passed to analyze_joke_syntax. CustomFilterAction replaces it with [censored] before the tool executes. Look for [FILTER][Joke Content Word Filter] in stdout.
Scenario 2 — length limiter (PRE): if the generated joke exceeds 1000 characters, BlockAction raises AgentRuntimeError(TERMINATION_GUARDRAIL_VIOLATION) before the tool is called.
Scenario 3 — PII at tool and agent scope: supply a topic containing an email address:
uv run uipath run agent '{"topic": "donkey, test@example.com"}'Both the agent-scope and LLM-scope @pii_detection_guardrail decorators log a WARNING when the email is detected. The tool-scope @pii_detection_guardrail logs when the email reaches the tool input.
| Aspect | Middleware (joke-agent) |
Decorator (joke-agent-decorator) |
|---|---|---|
| Configuration | Middleware class instances passed to create_agent(middleware=[...]) |
@decorator stacked on the target object |
| Scope | Explicit scopes=[...] list |
Inferred automatically from the decorated object |
| Tool guardrails | UiPathDeterministicGuardrailMiddleware(tools=[...]) |
@deterministic_guardrail directly on the @tool |
| Custom loops | Not supported (requires create_agent) |
Works in any custom LangChain loop |
| API calls | Via middleware stack | Direct uipath.guardrails.evaluate_guardrail() |
"banana"— normal run, all guardrails pass"donkey"— triggers the word filter onanalyze_joke_syntax"donkey, test@example.com"— triggers word filter + PII guardrails at all scopes"computer","coffee","pizza","weather"