1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
11 small wins to finish your pathNext lesson →
Guardrail spans
@guardrail records a check on input or output as its own span, with spec input or output. The span records the check's input too, including what it removed.
import re
from agentops.sdk.decorators import guardrail
@guardrail(spec="input")
def remove_card_numbers(text):
return re.sub(r"\b\d(?:[ -]?\d){12,15}\b", "[card removed]", text)
@agentops.trace(name="support-ticket")
def handle(ticket):
return remove_card_numbers(ticket)print(handle("My card 4111 1111 1111 1111 was charged twice"))
local_collector.flush()
span = next(s for s in local_collector.SPANS if s["name"] == "remove_card_numbers.guardrail")
for key in ["agentops.guardrail.spec", "agentops.guardrail.input", "agentops.guardrail.output"]:
print(key, "=", span["attributes"][key])python guard.pyThe guardrail replaced the card number, and handle got the clean text. But the guardrail span records its own input like every decorated function, so the card number it removed is in guardrail.input, and was exported. A guardrail span shows that a check ran and what it changed; it does not make the data safe to send.
Redact before anything decorated sees the text, in a plain function, as the project does. Keep @guardrail for checks whose input is safe to record, such as whether a reply mentions a competitor.
Try it yourself
- Move the redaction into an undecorated function and call it first.
- Write an output guardrail,
spec="output", that shortens long replies. - Return a boolean from the guardrail instead of text and print the span.
Slow is fine. Stopping is the only problem.