Two validators in one Guard
Lesson 5 built one Guard per validator. A real support desk applies several rules to the same reply, and the obvious way to write that quietly throws one of them away.
The bug
from guardrails import Guard
from guardrails_ai.ends_with import EndsWith
from guardrails_ai.valid_length import ValidLength
desk = Guard().use(EndsWith(end=".", on_fail="noop")).use(ValidLength(min=1, max=20, on_fail="noop"))
outcome = desk.validate("we can refund order 8821 today")
print(sorted(s.validator_name for s in outcome.validation_summaries))The reply is thirty characters long and does not end in a full stop, so both rules should have complained. Only one did. EndsWith never ran, and nothing said so: no error, no warning, no entry in the logs from lesson 4.
The reason is in Guard.use's own docstring. Calling it more than once with the same target overwrites what was there before. The Guardrails Custom validators page shows exactly this chained form as the way to combine two validators, which is how the mistake gets copied.
The fix
desk = Guard().use(
EndsWith(end=".", on_fail="noop"),
ValidLength(min=1, max=20, on_fail="noop"),
)
outcome = desk.validate("we can refund order 8821 today")
print(sorted(s.validator_name for s in outcome.validation_summaries))One call, both validators, both complaints. The validators= keyword does the same job when the list is built somewhere else.
rules = [EndsWith(end=".", on_fail="noop"), ValidLength(min=1, max=20, on_fail="noop")]
desk = Guard().use(validators=rules)
print(sorted(s.validator_name for s in desk.validate("we can refund order 8821 today").validation_summaries))The sorting is deliberate. Validators inside one Guard run concurrently, so the order the summaries come back in is not something to depend on.
The overwrite is per target, not per Guard. use takes an on= argument naming what to validate, and two calls with different targets both stick. Lesson 15 attaches a rule to the incoming question and another to the reply on the same Guard, and lesson 18 does it for two fields of one object.
A form that looks right and is not
The Validators concept page shows a Guard built from a validator class plus its arguments, like Guard().use(ToxicLanguage, threshold=0.5, on_fail="exception"). That does not work in 0.11.0.
try:
Guard().use(ValidLength, min=1, max=20, on_fail="noop")
except TypeError as error:
print("TypeError:", error)use takes validator instances, a validators= list and an on= target, and nothing else. Build the validator first, then hand it over.
- Chain three
.use()calls and confirm that only the last one runs. - Give the two validators different
on_failvalues,noopandfix, and see which one changes the output. Lesson 10 explains what you see. - Pass the class positionally,
Guard().use(ValidLength, 1, 20), and read the error. It is a different one, and just as unhelpful.
Little by little, you're building something great.