AI EngineeringZero to ProductionHome·About·Contact
AWS AI Automation · Chapter W6

Bedrock Guardrails

Guardrails are safety as a configurable layer: content filters, PII redaction, denied topics, and grounding checks that run on input and output, independent of the model.

⏱️ ~1.5 hours🧪 2 labs🎯 Intermediate→Advanced
⚙️ To run this for realThe code here is complete and correct as written. To actually execute it you'll need:
  • AWS credentials (aws configure) + Bedrock model access enabled in your region + pip install boto3
  • AWS credentials (aws configure) + pip install boto3
Reading and learning works without any of this — run when you're ready. IDs/ARNs in examples are placeholders; swap in your own.

Learning objectives

  • Create a Bedrock Guardrail with content filters, denied topics, and PII handling.
  • Apply a guardrail to a Converse call and read the intervention result.
  • Use contextual grounding checks to catch hallucinations in RAG answers.
  • Decide what belongs in a guardrail vs. in your application code.
▶ Runnable companionEvery code block in this lesson is also a standalone file under code/aws6-guardrails/ in the course, with a README on how to run it. Read here, run there — no need to copy-paste.

Safety as a configurable layer essential

A Guardrail is a policy object you attach to model calls. It filters harmful content, blocks topics you deny, redacts PII, and can check that a RAG answer is grounded in the retrieved context. It runs on both the input and the output, independent of the model.

Create a guardrail essential

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab W6.1
create_guardrail.pyimport boto3
bedrock = boto3.client("bedrock", region_name="us-east-1")

g = bedrock.create_guardrail(
    name="support-guardrail",
    blockedInputMessaging="I can't help with that request.",
    blockedOutputsMessaging="I can't provide that response.",
    contentPolicyConfig={"filtersConfig": [
        {"type": "HATE", "inputStrength": "HIGH", "outputStrength": "HIGH"},
        {"type": "VIOLENCE", "inputStrength": "HIGH", "outputStrength": "HIGH"},
    ]},
    sensitiveInformationPolicyConfig={"piiEntitiesConfig": [
        {"type": "EMAIL", "action": "ANONYMIZE"},
        {"type": "CREDIT_DEBIT_CARD_NUMBER", "action": "BLOCK"},
    ]},
    topicPolicyConfig={"topicsConfig": [
        {"name": "legal-advice", "definition": "Providing legal advice.",
         "type": "DENY", "examples": ["Should I sue my employer?"]},
    ]},
)
print("guardrail id:", g["guardrailId"], "version:", g["version"])
▶ How this works

This script builds a safety policy once and registers it with Bedrock. A guardrail is a rule-set object that AWS stores for you; later you attach it to model calls by ID. Nothing about the model changes — the guardrail is a separate layer that inspects text going in and coming out. (Running it needs AWS credentials and Bedrock access, but the structure is the whole lesson.)

  1. boto3.client("bedrock", region_name="us-east-1") opens a connection to the Bedrock control plane (the management API where you create/configure guardrails) in the US East region.
  2. create_guardrail(name=...) is the one call that makes the policy. The two ...Messaging lines set the polite fallback text the caller sees when input or output is blocked, instead of the real content.
  3. Content filterscontentPolicyConfig turns on category filters. Here HATE and VIOLENCE are set to HIGH strength on both inputStrength (what the user sends) and outputStrength (what the model replies). Higher strength = blocks more aggressively.
  4. PII handlingsensitiveInformationPolicyConfig lists personal data types. EMAIL uses ANONYMIZE (the email is redacted, replaced with a placeholder), while CREDIT_DEBIT_CARD_NUMBER uses BLOCK (the whole request is refused).
  5. Denied topicstopicPolicyConfig defines subjects to refuse. The legal-advice topic has a plain-English definition and an examples list; Bedrock uses these to recognize the topic even when worded differently.
  6. The final print shows the two values AWS hands back: a guardrailId and a version — you need both to attach it later.

What the output means: You'd see something like guardrail id: gr-abc123 version: 1. Save that ID — it's the handle you pass to every model call you want protected.

Try this: Add a line for {"type": "SEXUAL", "inputStrength": "HIGH", "outputStrength": "HIGH"} to the filters list. Adding safety categories is just adding dictionary entries — no model retraining involved.

Apply it to a call intermediate

Pass guardrailConfig to converse. If the guardrail intervenes, stopReason is guardrail_intervened and you get the safe fallback message instead of raw model output.

Lab W6.2
apply_guardrail.pyimport boto3
brt = boto3.client("bedrock-runtime", region_name="us-east-1")

resp = brt.converse(
    modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
    messages=[{"role": "user", "content": [{"text": "My card is 4111 1111 1111 1111, is it valid?"}]}],
    guardrailConfig={"guardrailIdentifier": "GR123", "guardrailVersion": "1"},
)
print("stop reason:", resp["stopReason"])
print(resp["output"]["message"]["content"][0]["text"])
stop reason: guardrail_intervened
I can't provide that response.
▶ How this works

Now we use the guardrail on a normal chat call. The user message deliberately contains a credit-card number — which the policy above set to BLOCK — so we can watch the guardrail step in and stop the response.

  1. boto3.client("bedrock-runtime", ...) connects to the runtime API (the one that actually runs models), which is different from the bedrock control-plane client used to create the guardrail.
  2. brt.converse(modelId=..., messages=[...]) is the standard Bedrock chat call. The messages list holds the conversation; here one user turn asks whether a card number is valid.
  3. The key line: guardrailConfig={"guardrailIdentifier": "GR123", "guardrailVersion": "1"} attaches the policy by the ID and version you got in Lab W6.1. That single argument is what turns safety on for this call.
  4. resp["stopReason"] tells you why the reply ended. Normally it's something like end_turn; when a guardrail acts it becomes guardrail_intervened.
  5. The last print digs into the nested response to pull the text out: outputmessage → first content block → its text.

What the output means: stop reason: guardrail_intervened confirms the guardrail blocked the call, and the printed text is your safe fallback message (I can't provide that response.) — not anything the model actually said. The card number never reached the model unfiltered.

Try this: Change the message to a harmless question like "What are your support hours?" and you'd expect stop reason: end_turn with a real answer — the guardrail only intervenes when a rule is triggered.

Contextual grounding for RAG advanced

The grounding check scores whether an answer is supported by the source context and relevant to the query. Below a threshold, the guardrail blocks the answer — a managed defense against the hallucination problem you evaluated in Ch 5.

Guardrail vs. codePut broad, reusable policy in the guardrail (PII, banned topics, grounding). Keep business authorization (can THIS user issue THIS refund?) in your code — a guardrail cannot know your permission model.

Exercise W6.1 — Ground your W4 KB

Context: The whole point of guardrails on a RAG stack is to stop hallucination on questions your corpus cannot answer. Attaching a contextual-grounding check to your existing W4 Knowledge Base call is the canonical way to prove the safety layer actually earns its place.

Your task: Attach a guardrail with a contextual-grounding check to your W4 retrieve_and_generate call, then ask a question your docs do not answer and confirm the guardrail blocks a confident-but-unsupported reply.

Requirements:

  • Reuse the W4 Knowledge Base / retrieve_and_generate path
  • Attach a guardrail whose grounding check is active on the call
  • Pose a question with no support anywhere in the indexed docs
  • Confirm the response is blocked/refused rather than an invented answer
  • Contrast with an in-corpus question that still answers normally

💡 Hint: Pick a question that sounds in-domain but is genuinely absent from the docs — that is what exposes weak grounding, not obvious nonsense.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Create a content-filter guardrailBeginner

Context: Guardrails are a safety layer that lives outside the model, configured on the Bedrock control plane and applied to calls independently of the prompt. Content filters are the starting point: they score input and output per harm category at a chosen strength.

Your task: Use bedrock.create_guardrail to block HATE and VIOLENCE at HIGH strength on both input and output, with fallback messaging, and print the returned id and version.

Requirements:

  • Create the guardrail via the bedrock control-plane client
  • Set both blockedInputMessaging and blockedOutputsMessaging
  • contentPolicyConfig.filtersConfig lists HATE and VIOLENCE
  • Each filter sets inputStrength and outputStrength to HIGH
  • Print guardrailId and version; needs AWS creds

💡 Hint: The guardrail is created on the control plane and referenced by id later — it is not part of any single prompt.

Show solution

Guardrails are a separate safety layer configured on the control plane; content filters set input/output strengths per category.

import boto3

bedrock = boto3.client("bedrock", region_name="us-east-1")
resp = bedrock.create_guardrail(
    name="support-guardrail",
    blockedInputMessaging="I can't help with that request.",
    blockedOutputsMessaging="Response withheld by policy.",
    contentPolicyConfig={"filtersConfig": [
        {"type": "HATE",     "inputStrength": "HIGH", "outputStrength": "HIGH"},
        {"type": "VIOLENCE", "inputStrength": "HIGH", "outputStrength": "HIGH"},
    ]},
)
print(resp["guardrailId"], resp["version"])
Exercise 2 · Apply a guardrail to a callIntermediate

Context: A guardrail does nothing until you attach it to a runtime call. When it fires, the response comes back with a distinctive stop reason and the safe fallback text instead of the model's output — and your code must branch on that.

Your task: Pass guardrailConfig to a converse call on the bedrock-runtime client and detect the guardrail_intervened stop reason.

Requirements:

  • Use the bedrock-runtime client and the converse API
  • Supply guardrailConfig with a guardrailIdentifier and guardrailVersion
  • Inspect r["stopReason"] after the call
  • When it equals guardrail_intervened, report the block instead of printing model text
  • Otherwise print the normal completion; needs AWS creds

💡 Hint: Treat stopReason as the control signal — the fallback message is already substituted for you when the guardrail intervenes.

Show solution

When a guardrail fires, stopReason is "guardrail_intervened" and the caller gets the safe fallback text.

import boto3

brt = boto3.client("bedrock-runtime", region_name="us-east-1")
r = brt.converse(
    modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
    messages=[{"role":"user","content":[{"text":"...user text..."}]}],
    guardrailConfig={"guardrailIdentifier": "GR123", "guardrailVersion": "1"},
)
if r["stopReason"] == "guardrail_intervened":
    print("blocked; safe fallback returned")
else:
    print(r["output"]["message"]["content"][0]["text"])
Exercise 3 · Add PII anonymizationAdvanced

Context: Handling personal data safely is often a compliance requirement, not a nicety. Bedrock's PII policy lets you mask some entity types and hard-block others in the same guardrail, so emails can be redacted while card numbers stop the request cold.

Your task: Extend a guardrail with a sensitiveInformationPolicyConfig that ANONYMIZEs EMAIL and BLOCKs CREDIT_DEBIT_CARD_NUMBER.

Requirements:

  • Configure piiEntitiesConfig as a list of entity/action pairs
  • EMAIL uses action ANONYMIZE (masked, not refused)
  • CREDIT_DEBIT_CARD_NUMBER uses action BLOCK (refused)
  • Keep the required blocked-messaging fields set
  • Explain the difference between anonymize and block; needs AWS creds

💡 Hint: ANONYMIZE and BLOCK are two different actions on the same policy — pick per entity based on whether the data may pass through masked or must stop the call.

Show solution

The PII policy lists entity types with an action: ANONYMIZE masks, BLOCK refuses.

import boto3

bedrock = boto3.client("bedrock", region_name="us-east-1")
bedrock.create_guardrail(
    name="pii-guardrail",
    blockedInputMessaging="blocked", blockedOutputsMessaging="blocked",
    sensitiveInformationPolicyConfig={"piiEntitiesConfig": [
        {"type": "EMAIL",                  "action": "ANONYMIZE"},
        {"type": "CREDIT_DEBIT_CARD_NUMBER", "action": "BLOCK"},
    ]},
)
print("pii guardrail created")
Exercise 4 · Deny a topicExpert

Context: Sometimes the risk is a whole subject, not a word: a support bot that must never give financial advice, for instance. A denied-topic policy detects the topic semantically, and its accuracy depends on giving it a crisp definition plus example utterances to anchor detection.

Your task: Add a topicPolicyConfig that denies financial advice, with a name, a definition, type="DENY", and example utterances.

Requirements:

  • topicsConfig holds one topic with type="DENY"
  • Provide a precise definition of what counts as the topic
  • Include a few examples of in-topic questions to anchor detection
  • Keep blockedInputMessaging/blockedOutputsMessaging set
  • Needs AWS creds to create

💡 Hint: Examples are not decoration — the more representative the sample utterances, the better the topic classifier generalizes to paraphrases.

Show solution

A denied topic needs a name, a definition, type="DENY", and example utterances to anchor detection.

import boto3

bedrock = boto3.client("bedrock", region_name="us-east-1")
bedrock.create_guardrail(
    name="topic-guardrail",
    blockedInputMessaging="I can't advise on that.",
    blockedOutputsMessaging="Withheld.",
    topicPolicyConfig={"topicsConfig": [{
        "name": "FinancialAdvice",
        "definition": "Recommendations to buy/sell specific securities.",
        "type": "DENY",
        "examples": ["Should I buy TSLA?", "What stock will go up?"],
    }]},
)
print("topic guardrail created")
Exercise 5 · A policy-config builderProfessional

Context: In a real org the security team, not the app engineer, owns policy. Expressing the guardrail as a small data spec that a builder turns into create_guardrail kwargs lets non-engineers edit it and lets you diff the policy in code review.

Your task: Write build_guardrail_config(pii, denied_topics) that assembles the create_guardrail kwargs from a simple spec, adding each policy section only when its input is non-empty.

Requirements:

  • Always include the base fields (name and blocked-messaging)
  • When pii is given, build sensitiveInformationPolicyConfig.piiEntitiesConfig from (type, action) pairs
  • When denied_topics is given, build topicPolicyConfig.topicsConfig with type="DENY"
  • Omit a section entirely when its spec input is empty
  • Runs offline; verify by printing the resulting config keys

💡 Hint: Build a base dict, then conditionally add each policy block — a comprehension over the spec tuples keeps the entity/topic lists tidy.

Show solution

Expressing policy as data lets non-engineers edit it and lets you diff it in review.

def build_guardrail_config(pii, denied_topics):
    cfg = {"name": "house-guardrail",
           "blockedInputMessaging": "blocked",
           "blockedOutputsMessaging": "blocked"}
    if pii:
        cfg["sensitiveInformationPolicyConfig"] = {"piiEntitiesConfig":
            [{"type": t, "action": a} for t, a in pii]}
    if denied_topics:
        cfg["topicPolicyConfig"] = {"topicsConfig":
            [{"name": n, "definition": d, "type": "DENY", "examples": ex}
             for n, d, ex in denied_topics]}
    return cfg

c = build_guardrail_config(
    pii=[("EMAIL","ANONYMIZE"),("SSN","BLOCK")],
    denied_topics=[("Legal","Legal advice",["Can I sue?"])])
print(list(c.keys()))
Exercise 6 · Contextual grounding gate for a RAG endpointIndustry scenario

Context: A RAG bot's cardinal sin is a confident answer the retrieved context does not support. Contextual grounding scores how well an answer is backed by its context; below a threshold you refuse. In production this decision sits alongside the guardrail stop reason as a single gate.

Your task: Write gate(grounding_score, stop_reason, threshold=0.75) that returns a decision dict: block when the guardrail intervened, refuse when grounding is below threshold, otherwise answer.

Requirements:

  • A guardrail_intervened stop reason short-circuits to a blocked result
  • A grounding_score below threshold returns a refuse action with the reason and score
  • Otherwise return an answer action carrying the score
  • Threshold is a parameter with a sensible default (e.g. 0.75)
  • Demonstrate all three branches; runs offline

💡 Hint: Check the guardrail signal before the score — a hard block outranks a grounding judgement, so order the conditions accordingly.

Show solution

Contextual grounding scores how well the answer is supported by the context; below threshold you refuse, defending against hallucination.

def gate(grounding_score, stop_reason, threshold=0.75):
    if stop_reason == "guardrail_intervened":
        return {"action": "blocked_by_guardrail"}
    if grounding_score < threshold:
        return {"action": "refuse", "reason": "insufficient grounding",
                "score": grounding_score}
    return {"action": "answer", "score": grounding_score}

print(gate(0.91, "end_turn"))   # answer
print(gate(0.40, "end_turn"))   # refuse
print(gate(0.99, "guardrail_intervened"))   # blocked_by_guardrail

✓ Checkpoint — you can move on when you can…

  • Create a guardrail with content, PII, and topic policies.
  • Apply a guardrail to Converse and detect an intervention.
  • Explain what a contextual-grounding check defends against.
  • Draw the line between guardrail policy and application authorization.

Knowledge check check yourself

✓ Knowledge check

A guardrail runs independent of the model on both input and output. What stopReason indicates it acted, and what does the caller receive instead of raw model output?

Show answer
When a guardrail intervenes the stopReason is guardrail_intervened, and the caller gets the safe fallback message (blockedInputMessaging / blockedOutputsMessaging) rather than the model's real text.
✓ Knowledge check

Where do you draw the line between what belongs in a guardrail versus your application code?

Show answer
Put broad, reusable policy in the guardrail: content filters, PII handling, denied topics, and contextual grounding checks. Keep business authorization (can THIS user perform THIS action?) in your code, because a guardrail cannot know your permission model.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in