Unit 04.01: Validating input before it reaches the prompt
Validate the input before the prompt is built, so bad content never reaches the provider or your logs.
Empty, oversized, and containing things it must not
Length bounds and pattern rejection for card numbers and national identifiers.
The code runs four inputs through it.
import re
REJECT = [
(re.compile(r"\b\d{4}[ -]?\d{4}[ -]?\d{4}[ -]?\d{4}\b"), "card number"),
(re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), "national insurance/SSN"),
]
def validate(text, max_chars=4000):
if not text.strip():
return False, "empty"
if len(text) > max_chars:
return False, f"too long: {len(text)} > {max_chars}"
for pattern, name in REJECT:
if pattern.search(text):
return False, f"contains a {name}"
return True, ""
for text in ["How long do I have for a refund?",
"my card is 4111 1111 1111 1111",
"",
"x" * 5000]:
ok, reason = validate(text)
print(f"{'ACCEPT' if ok else 'REJECT'} {text[:38]!r:42} {reason}")
# Validate before building the prompt, not after. A card number rejected here
# never reaches the provider, never reaches your logs, and never reaches the
# trace store.
A card number rejected here never reaches the model, never reaches the provider's logs, and never reaches your trace store. Rejecting it after the call would have failed all three.
The length bound is the cost control: a 5,000-character input is a 5,000-character prompt on every retry.
The mistake this prevents
The mistake is validating after the response, where the checks are about output. Input validation is a different layer with a different purpose - it is what stops content you do not want anywhere near a third party.
Takeaway
Validate input before building the prompt. Content rejected there never reaches the provider, your logs, or your trace store.
