Input guards
Every check so far ran on the reply. Lesson 6 said the overwrite in use is per target rather than per Guard, and the second target is the question the customer sent.
Support desks see things they should not. Somebody pastes a password into the chat box, and now it is in your prompt, your provider's logs and your own transcript store. The cheapest place to stop that is before the model is called at all.
from typing import Dict
from guardrails import Guard, register_validator
from guardrails.validators import FailResult, PassResult, ValidationResult, ValidatorThe same four imports as lesson 8. An input validator is an ordinary validator; only where it is attached changes.
@register_validator(name="shop/no-secrets", data_type="string")
class NoSecrets(Validator):
def _validate(self, value: str, metadata: Dict) -> ValidationResult:
for word in ("password", "card number", "cvv"):
if word in value.lower():
return FailResult(error_message=f"The message contains a {word}.")
return PassResult()
print(NoSecrets.rail_alias)from pretend_guardrails import PretendModel
model = PretendModel()
desk = Guard().use(NoSecrets(on_fail="exception"), on="messages")
try:
desk(model, messages=[{"role": "user", "content": "my password is hunter2, where is 8821?"}])
except Exception as error:
print(type(error).__name__, "|", error)
print("model calls:", len(model.prompts))Zero model calls. The Guard stopped before it spent anything, which is the argument for input validation that survives contact with a finance department. The Use on_fail actions guide makes the same point: exceptions suit input validation particularly well, because there is no output yet to repair.
on="messages" is the target. Guardrails joins the message contents into one string and runs the validators on it, so a rule written for a reply works unchanged on a question.
Both ends of the same Guard
model = PretendModel()
desk = (Guard()
.use(NoSecrets(on_fail="exception"), on="messages")
.use(ValidLength(min=1, max=40, on_fail="noop")))
answer = desk(model, messages=[{"role": "user", "content": "where is order 8821?"}])
print(answer.validated_output)
print(sorted(s.validator_name for s in answer.validation_summaries))Two use calls, two targets, and both survive. This is the exception to lesson 6: chaining only destroys what came before when the target is the same.
Notice which validator appears in the summaries. validation_summaries reports the last iteration, which is the output check, so an input rule that fired will not be in that list. The input failure is in the history and in the exception.
- Give
NoSecretsanon_fail="fix"and afix_valuethat blanks the secret, then see what the model is asked. - Send a clean question through the two-target Guard and confirm both rules ran by reading
desk.history.last.validator_logs. - Add a length limit on
messagesso a customer cannot paste a whole email thread.
Every expert started right here.