Lab 02 — Bedrock Guardrails: a Content-Safety Policy Engine
Phase 24 · Lab 02 · Phase README · Warmup
The problem
Bedrock Guardrails answers a different question than Bedrock AgentCore's Cedar Policy (Phase 20, Lab 02) does, and mixing them up is a fast way to sound junior in an interview:
- Cedar/IAM (access control) decides whether a principal may take an action on a resource — "may this agent call the refund tool?" It is an authorization decision.
- Guardrails (content safety) decides whether a piece of text is safe to pass through — "does this sentence contain hate speech, PII, a denied topic, or an ungrounded claim?" It is a content decision, applied to the prompt going IN and the generation coming OUT.
An agent needs both. Guardrails is what you build here: five policies — denied topics, content
filters (six categories, each with a configurable severity threshold), word filters, PII
detection (block-or-mask), and contextual grounding — applied at two points in the request
lifecycle: the input guardrail (before the model runs at all) and the output guardrail
(after generation, before the caller sees it). Every policy that actually intervenes produces a
trace entry, mirroring the real Guardrails assessments shape.
What you build
| Piece | What it does | The lesson |
|---|---|---|
DeniedTopic / _score_content / word-filter scan | topic, content, and word policies | multiple independent filters, each with its own detection logic |
PII_PATTERNS + block-or-mask | regex PII detection (email, phone, SSN, credit card) | masking preserves utility; blocking destroys it — the tradeoff is per-entity-type, configurable |
groundedness_score / relevance_score | deterministic token-overlap grounding check | a generation can be fluent AND wrong; grounding catches that content filters can't |
GuardrailsEngine.apply_input / apply_output | the two guardrail application points | denied input never reaches the model; ungrounded output never reaches the caller |
invoke_with_guardrails | the full pipeline: input guardrail → model → output guardrail | proves the model is skipped entirely on an input block |
TraceEntry / GuardrailAction | which specific rule fired, and why | the real Guardrails intervention trace, in miniature |
File map
| File | Role |
|---|---|
lab.py | your implementation (fill the # TODOs) |
solution.py | reference implementation + worked example (python solution.py) |
test_lab.py | 26 tests: each policy in isolation, thresholds, mask-vs-block, grounding, full pipeline, trace shape |
requirements.txt | pytest only |
Run it
pytest test_lab.py -v # your lab.py (red until you implement)
LAB_MODULE=solution pytest test_lab.py -v # the reference (green)
python solution.py # worked example
Success criteria
-
A benign input/output passes through untouched:
action=NONE,blocked=False, no trace. -
A denied-topic input is blocked before the model runs —
model_called=False, proven by a call counter on the injectedmodel_fnstaying at zero. -
A PII-bearing output is masked, not blocked, when the entity type's action is
MASK; it IS blocked when the action isBLOCK— configurable per entity type. -
An ungrounded generated answer is blocked by the contextual grounding check, and the
trace shows
contextualGroundingPolicy. - Content-filter severity thresholds are configurable per category and visibly change the block/no-block outcome for the identical input text.
-
Every intervention produces a
TraceEntrywithpolicy,name,action, anddetail. -
All 26 tests pass under both
labandsolution.
How this maps to the real stack
- Denied topics, content filters, word filters, and PII (sensitive information) filters are
the real Bedrock Guardrails policy names. Content filters really do cover hate, insults, sexual,
violence, misconduct, and prompt-attack categories, each independently scored at
NONE/LOW/MEDIUM/HIGH severity with a configurable block threshold per category — exactly the
content_thresholdsdict here. Real severity scoring is model-based (a classifier), not keyword-matched; the lexicon here is the honest, deterministic stand-in that keeps the threshold mechanics visible and testable. - PII filters in real Guardrails detect a long list of built-in entity types (this lab uses
the real entity-type names
EMAIL,PHONE,US_SOCIAL_SECURITY_NUMBER,CREDIT_DEBIT_CARD_NUMBERamong others) and, per type, either block the response or mask/anonymize just the matched span — the same block-vs-mask choicepii_actionsmodels. - Contextual grounding checks are a real, distinct Guardrails policy (groundedness + relevance scoring against supplied reference source documents) specifically aimed at catching hallucination that content filters cannot — a fluent, non-toxic, completely made-up answer sails through every other filter and only grounding catches it. Real scoring uses an NLI-style model; our token-overlap ratio is the deterministic, inspectable equivalent of the same idea (does the claim have support in the source?).
- Where each policy applies: real Guardrails lets you configure most policies (topics,
content, words, PII) on the input, the output, or both; contextual grounding is output-only by
construction (there's nothing to ground a prompt against).
apply_input/apply_outputmodel that split — both call the same_scan, but onlyapply_outputruns grounding.invoke_with_guardrails's ordering (input guardrail → model → output guardrail) is the real request lifecycle, and the "model never runs on a blocked input" behavior is the actual reason Guardrails is cheaper AND safer than a post-hoc content filter: you never pay for or risk a generation you were going to throw away. GuardrailAction(NONE/GUARDRAIL_INTERVENED) and the assessment/trace shape (policy name, what fired, why) mirror the realApplyGuardrail/Converseresponse'samazon-bedrock-guardrailActionfield andassessmentsarray closely enough to reason about incident response: "which policy fired, on which text, and what did it do about it."- The Cedar/Guardrails distinction is the header lesson of this lab: Guardrails is a
content-safety layer over TEXT; Cedar (Phase 20, Lab 02) is an access-control layer over
ACTIONS. A production agent runs both — Guardrails on every prompt/generation, Cedar on every
tool call — and they fail independently: a Guardrails intervention never authorizes a tool call,
and a Cedar
permitnever excuses toxic or hallucinated text.
Limits of the miniature. Real content-filter and denied-topic classification is model-based (a trained classifier scores arbitrary text, not a fixed lexicon); ours is deterministic keyword matching specifically so the threshold semantics are testable without a model in the loop. Real PII detection also covers dozens more entity types (names, addresses, bank routing numbers, passport numbers, and region-specific IDs) with locale-aware patterns; ours covers four illustrative regex-shaped ones. Real contextual grounding uses a purpose-built scoring model; ours is bag-of-words token overlap, the same honest stand-in this track uses everywhere retrieval relevance shows up (Phase 05).
Extensions (your own machine)
- Swap the keyword lexicon for a real toxicity/classifier model and confirm the
_scanpipeline and threshold mechanics are unchanged — proving severity scoring is a pluggable signal. - Add contextual grounding using the Phase 05 hashing-embedding cosine instead of raw token overlap, and compare which ungrounded answers each method catches.
- Add a guardrail version/config registry (like real Bedrock Guardrail versions) so you can A/B two threshold configurations against the same transcript and diff the interventions.
- Wire
invoke_with_guardrailsin front of Lab 01'sBedrockRuntime.converseso a single call goes through both the capacity model AND the guardrail pipeline, closest to a real request path.
Interview / resume signal
"Built a miniature Bedrock Guardrails engine: denied topics, six-category content filters with per-category configurable severity thresholds, word filters, regex PII detection with a block-or-mask policy per entity type, and a token-overlap contextual grounding check — wired as both an input guardrail (proving the model is never invoked on blocked input) and an output guardrail (proving PII is masked while hallucinated, ungrounded answers are blocked), each producing an intervention trace naming the exact rule that fired."