Lab 2: Data Protection#

In this lab, you will apply several data protection techniques discussed in the lesson. In particular, you will focus on performing data sanitization with Amazon Comprehend and on applying Guardrails for Amazon Bedrock to add safeguards on top of the native protections of Foundation Models.

About This Lab#

Throughout this lab, you will encounter two types of interactive elements:

            ![Activity](../mlu_utils/images/activity.png)

            ![Challenge](../mlu_utils/images/challenge.png)

            No coding is needed for an activity. You try to understand a concept,

answer questions, or run a code cell.

            Challenges are where you test your understanding by implementing something new or taking a short quiz.

Please work through this notebook from top to bottom to avoid errors due to missing code or context.

Table of Contents#

%%capture
# Let's first install all libraries needed
# This cell might take 1-2 minutes to run
!pip install -r ../requirements.txt --quiet
# Remove aiobotocore/s3fs to fix botocore version conflict
!pip uninstall aiobotocore s3fs -y --quiet
# Reinstall correct boto3/botocore versions
!pip install boto3==1.38.16 botocore==1.38.16 --quiet --force-reinstall --no-deps
%%capture
# Import libraries
import time
import boto3
import json
import pandas as pd
import re
pd.set_option('display.max_colwidth', None)
from IPython.display import Markdown, display

1. Sanitize training data using Amazon Comprehend#

Data protection stresses the need to sanitize our data at every step of the pipeline. Simple sanitization techniques can be applied to data that we can programmatically define, e.g. with a regular expression. For complex data, we need to resort to heuristic or statistical methods to detect it.

Run the code below to see how to apply regexes to the detection of simple data, such as phone numbers and email addresses.

text = """If you need to reach our sales team, you can email them at sales@company.com or call 
them directly at (555) 123-4567. For technical support inquiries, please contact support@company.com 
or call our helpline at (555) 987-6543 ext. 2.

You can also follow us on social media by sending a message to socialmedia@company.com or by calling 
our social media hotline at (555) 456-7890. If you're interested in career opportunities with our company, 
feel free to send your resume to careers@company.com or call our HR department at (555) 234-5678.

For general inquiries or feedback, you can email us at info@company.com or call our main line at (555) 888-9999."""

display(Markdown("**Original text**"))
Markdown(text)

Original text

If you need to reach our sales team, you can email them at sales@company.com or call them directly at (555) 123-4567. For technical support inquiries, please contact support@company.com or call our helpline at (555) 987-6543 ext. 2.

You can also follow us on social media by sending a message to socialmedia@company.com or by calling our social media hotline at (555) 456-7890. If you’re interested in career opportunities with our company, feel free to send your resume to careers@company.com or call our HR department at (555) 234-5678.

For general inquiries or feedback, you can email us at info@company.com or call our main line at (555) 888-9999.

# Function to insert highlighting markdown to certain strings
def insert_highlight(text, highlight):
    highlighted = f"<span style='background-color: yellow'>{highlight}</span>"
    text = text.replace(highlight, highlighted)
    return text

# Regex to match email addresses
email_pattern = r"[a-z0-9\.\-+_]+@[a-z0-9\.\-+_]+\.[a-z]+"
email_regex = re.compile(email_pattern)

display(Markdown("Email addresses:"))
print(email_regex.findall(text))

# Regex to match phone numbers
phone_pattern = r"(\d{3}[-\.\s]??\d{3}[-\.\s]??\d{4}|\(\d{3}\)\s*\d{3}[-\.\s]??\d{4}|\d{3}[-\.\s]??\d{4})"
phone_regex = re.compile(phone_pattern)

display(Markdown("Phone numbers:"))
print(phone_regex.findall(text))

# Add highlighting markdown
highlights = email_regex.findall(text) + phone_regex.findall(text)
for highlight in highlights:
    text = insert_highlight(text, highlight)

# Display text with highlighted matched data
print()
display(Markdown("**Text with highlighted detected data**"))
Markdown(text)

Email addresses:

['sales@company.com', 'support@company.com', 'socialmedia@company.com', 'careers@company.com', 'info@company.com']

Phone numbers:

['(555) 123-4567', '(555) 987-6543', '(555) 456-7890', '(555) 234-5678', '(555) 888-9999']

Text with highlighted detected data

If you need to reach our sales team, you can email them at sales@company.com or call them directly at (555) 123-4567. For technical support inquiries, please contact support@company.com or call our helpline at (555) 987-6543 ext. 2.

You can also follow us on social media by sending a message to socialmedia@company.com or by calling our social media hotline at (555) 456-7890. If you’re interested in career opportunities with our company, feel free to send your resume to careers@company.com or call our HR department at (555) 234-5678.

For general inquiries or feedback, you can email us at info@company.com or call our main line at (555) 888-9999.

Amazon Comprehend#

In this first part of the lab, we will explore how to use Amazon Comprehend to sanitize complex training data by automatically detecting and redacting sensitive information.

Amazon Comprehend is a powerful natural language processing service that can identify a variety of personally identifiable information (PII) within unstructured text, such as names, dates of birth, financial data, and more. By leveraging this service, we can systematically remove or obfuscate these privacy-preserving elements from our dataset, helping to provide data protection during responsible AI development.

# Create a boto3 Comprehend client
comprehend = boto3.client("comprehend")

1.1 Detect PII entities#

The following example shows how Amazon Comprehend can detect PII entities from data and assign them a Type. Notice that we can pass a language code to indicate the particular language used as input. For this particular case, English.

example_data = "You can call John Wick right away!"

# Call Comprehend to detect PIIs from data input (in English)
response = comprehend.detect_pii_entities(Text=example_data, LanguageCode="en")
print(json.dumps(response["Entities"], indent=2))
[
  {
    "Score": 0.8735540509223938,
    "Type": "NAME",
    "BeginOffset": 13,
    "EndOffset": 22
  }
]

Challenge

Challenge: Exercise 1#

    Try it yourself!
    Experiment with Amazon Comprehend to detect multiple types of PIIs (see the list of types in the documentation).

        Find an example where Amazon Comprehend is able to detect multiple types of PIIs (e.g., dates, AWS access key, or phone number).
        Find an example where Amazon Comprehend fails to detect a name (hint: you may want to create a fake but realistic name from common English words).
        Find an example where Amazon Comprehend has low confidence in its detection, or misclassifies a result with high confidence (hint: timestamps may be misclassified, or fake credit card numbers may have low confidence.)
###### YOUR CODE HERE ######






###### END OF CODE ######

Tip: Consider using common English words to contruct a fake but realistic name. For misclassified or low-confidence samples, consider using timestamps or fake credit card numbers. Remove the # before the load instruction in the next code cell to display sample solutions. You may need to run the cell twice, in order to actually run the code.

# %load ../mlu_utils/solutions/lab1_ex1_solutions.txt

1.2 Sanitize data#

Now that we have used Amazon Comprehend to identify various types of personally identifiable information (PII) within our training data, the next step is to sanitize this data.

Sanitization refers to the process of removing, obfuscating, or otherwise obscuring sensitive personal information to protect individual privacy. This could involve replacing names with generic identifiers, converting dates of birth to ages, redacting financial details, and so on. By systematically sanitizing our dataset in this way, we can remove or minimize the exposure of PII while still preserving the essential features and characteristics needed to train our machine learning models. This is a crucial step in responsible AI development, ensuring we uphold strong data privacy and security practices.

Let’s first try a (slightly incorrect) sanitization function in which Amazon Comprehend is used to detect PII entities that are replaced with a placeholder.

def sanitize_naive(text):

    # Call Amazon Comprehend to detect PII entities.
    response = comprehend.detect_pii_entities(Text=text, LanguageCode="en")

    # Replace the sensitive data with a placeholder.
    sanitized = text

    for entity in response["Entities"]:
        sanitized = sanitized.replace(
            text[entity["BeginOffset"] : entity["EndOffset"]], "[PLACEHOLDER]"
        )

    return sanitized
example_data = "You can call John Wick right away!"

display(Markdown(f"**Original input:**"))
print(example_data)
print()

display(Markdown("**Sanitized output:**"))
print(sanitize_naive(example_data))
print()

Original input:

You can call John Wick right away!

Sanitized output:

You can call [PLACEHOLDER] right away!

In the example above, the naive sanitization function works as expected. But what if the personally identifiable information has common words between two different types of PIIs? Additionally, when multiple placeholders are added to sentences with several PII entities, it becomes difficult to understand the parsed answer.

See the example below:

example_data = """James and Rose are my friends. Ironically, they live at 31-33 James St, New York! 
You can contact them at 123-456-7890."""

display(Markdown(f"**Original input:**"))
print(example_data)
print()

display(Markdown("**Sanitized output:**"))
print(sanitize_naive(example_data))
print()

Original input:

James and Rose are my friends. Ironically, they live at 31-33 James St, New York! 
You can contact them at 123-456-7890.

Sanitized output:

[PLACEHOLDER] and [PLACEHOLDER] are my friends. Ironically, they live at 31-33 [PLACEHOLDER] St, New York! 
You can contact them at [PLACEHOLDER].

In the example above, we can see two issues:

  • the address did not get properly sanitized, because it contains a word that was previously detected as a PII (James);

  • the same placeholder is used for all types of PIIs, which reduces the semantic meaning of the sentence.

The first issue arises because we used the .replace() function on the Python string in sanitize_naive. Instead, we should use the offsets provided by Comprehend to properly sanitize the data. For the second issue, it would be preferable to sanitize a PII as [TYPE], where TYPE is the type of PII reported by Amazon Comprehend.

A proper sanitization of the sentence above would then be:

[NAME] and [NAME] are my friends. Ironically, they live at [ADDRESS]! You can contact them at [PHONE].

Challenge

Challenge: Exercise 2#

    Try it yourself!
    Write a correct sanitize function that takes text as input and uses Amazon Comprehend to detect PIIs, and then sanitize the text. The sanitization of a PII of type X should be [X]. You can assume that the internals BeginOffset and EndOffset do not intersect and are sorted.
###### YOUR CODE HERE ######

def sanitize(text):
    pass



###### END OF CODE ######

Tip: Use BeginOffset and EndOffset from the Amazon Comprehend response to locate the start and end of the text that needs to be replaced with a placeholder of the type Type. Remove the # before the load instruction in the next code cell to display sample solutions. You may need to run the cell twice, in order to actually run the code.

# %load ../mlu_utils/solutions/lab1_ex2_solutions.txt
example_data = """James and Rose are my friends. Ironically, they live at 31-33 James St, New York! 
You can contact them at 123-456-7890."""

display(Markdown(f"**Original input:**"))
print(example_data)
print()

if "sanitize" not in dir():
    raise ValueError("Please define a `sanitize` function to properly sanitize the text above.")

display(Markdown("**Sanitized output:**"))
print(sanitize(example_data))
print()

Original input:

James and Rose are my friends. Ironically, they live at 31-33 James St, New York! 
You can contact them at 123-456-7890.

Sanitized output:

None

1.3 Let’s sanitize a real training dataset#

We will now sanitize a real training dataset. We will use a dataset of Amazon Reviews that is available to use under the MIT license. The dataset is available at https://amazon-reviews-2023.github.io/, and can be loaded directly using the datasets package.

For this exercise we will use the reviews in the Gift_Cards category.

from datasets import load_dataset

# Try without trust_remote_code first
dataset = load_dataset(
        "McAuley-Lab/Amazon-Reviews-2023",
        "raw_review_Gift_Cards",
        streaming=True
    )

# Load the dataset as a Pandas dataframe
reviews = pd.DataFrame(dataset)

# Display the reviews
reviews
full
0 {'rating': 5.0, 'title': 'Great gift', 'text': 'Having Amazon money is always good.', 'images': [], 'asin': 'B00IX1I3G6', 'parent_asin': 'B00IX1I3G6', 'user_id': 'AHZ6XMOLEWA67S3TX7IWEXXGWSOA', 'timestamp': 1549866158332, 'helpful_vote': 0, 'verified_purchase': True}
1 {'rating': 5.0, 'title': 'amazon gift card', 'text': 'Always the perfect gift. I have never given one and had someone seem or act disappointed. Just the opposite. They are thrilled and excited to have a bit of a spree. Always the perfect size and color! Arrives in 1 day in most cases. So it's never too late! Lots of cards to chose from... thank you... birthday... wedding..baby.. and many that work for many occasions...', 'images': [], 'asin': 'B005ESMMWW', 'parent_asin': 'B005ESMMWW', 'user_id': 'AFZUK3MTBIBEDQOPAK3OATUOUKLA', 'timestamp': 1599875158120, 'helpful_vote': 0, 'verified_purchase': False}
2 {'rating': 5.0, 'title': 'perfect gift', 'text': 'When you have a person who is hard to shop for.. an amazon gift card is P E R F E C T. Man or woman... No matter what their hobby... lifestyle.. or age. All you have to do is pick the $. Don't forget to mention that it is a GIFT when you check out - you will have some gift card options. I've ordered many of these over years. They are always received with glee. Woo hoo! If you're looking for a great fit for me - this is just my size! :) Best to all!', 'images': [], 'asin': 'B01K8RIM5Y', 'parent_asin': 'B005S28ZES', 'user_id': 'AFZUK3MTBIBEDQOPAK3OATUOUKLA', 'timestamp': 1535939929239, 'helpful_vote': 27, 'verified_purchase': True}
3 {'rating': 5.0, 'title': 'Nice looking', 'text': 'The tin is a nice touch and pretty large. It's about 4&#34; in diameter and about 1/2&#34; thick. I added a pretty red ribbon and it is perfect. Who doesn't love shopping on Amazon? Arrived quickly, I have Prime... but I think they ship the gift cards out SUPER fast... like over night. In case you need it for a FAST gift.', 'images': [], 'asin': 'B0091JKVU0', 'parent_asin': 'B00ADR2LV6', 'user_id': 'AFZUK3MTBIBEDQOPAK3OATUOUKLA', 'timestamp': 1418439577000, 'helpful_vote': 0, 'verified_purchase': False}
4 {'rating': 1.0, 'title': 'Not $10 Gift Cards', 'text': 'I bought this pack of Starbucks Gift cards in 2019. Ive given them to friends and I gave 2 to my daughter.<br />My daughter used one recently and it had $6.52 on the card not $10.00. She had the cashier check the balance of the other card and it had $5.32 on it! She had forgotten that she had these gift cards, so yes, 2 years later decided to use the when she found them! Do they decline in value? And then both had random amounts on them! I'm embarrassed now to have given them as gifts! Friends receiving the gift card aren't going to tell you that weren't able to cover their order with the card you gave them!!!', 'images': [], 'asin': 'B00FTGTM5E', 'parent_asin': 'B00FTGTIOE', 'user_id': 'AH5L7ILVA6HYLZOUZIQAWNHVVK3A', 'timestamp': 1638068808115, 'helpful_vote': 2, 'verified_purchase': True}
... ...
152405 {'rating': 5.0, 'title': 'I gave it to my sister and she immediately comented on how pretty the bag was', 'text': 'I was really impressed with the pink bag. I gave it to my sister and she immediately comented on how pretty the bag was.', 'images': [], 'asin': 'B07641DGK2', 'parent_asin': 'B075MZKGRL', 'user_id': 'AHJTM4W63VAUPIIWDFYYVF4LXF6Q', 'timestamp': 1526779846316, 'helpful_vote': 0, 'verified_purchase': True}
152406 {'rating': 5.0, 'title': 'Gran idea y majestuosa forma de pago', 'text': 'Realmente es fascinante como hacen de algo muchas veces tan complicado para migrantes que aún no pueden obtener formas de pago electrónica, estos medios para ayudarte a resolver esa problemática. Ojalá todas las tiendas y lugares de uso público lo implementaran. Gracias', 'images': [], 'asin': 'B086KKT3RX', 'parent_asin': 'B086KKT3RX', 'user_id': 'AGX4ST62YMZQD6F2SI5REE2L3WTA', 'timestamp': 1685036728250, 'helpful_vote': 2, 'verified_purchase': True}
152407 {'rating': 1.0, 'title': 'called right away to cancel, was told no', 'text': 'Hit accidentally', 'images': [], 'asin': 'B00IX1I3G6', 'parent_asin': 'B00IX1I3G6', 'user_id': 'AEGTKHWDDGPJ254AZCFPSOK6USHQ', 'timestamp': 1597328047866, 'helpful_vote': 0, 'verified_purchase': True}
152408 {'rating': 5.0, 'title': 'Five Stars', 'text': 'Great', 'images': [], 'asin': 'B00IX1I3G6', 'parent_asin': 'B00IX1I3G6', 'user_id': 'AFBIM3MJIKV6OHJJDDZS3BZJYW4A', 'timestamp': 1434301786000, 'helpful_vote': 0, 'verified_purchase': True}
152409 {'rating': 4.0, 'title': 'Four Stars', 'text': 'muy bien', 'images': [], 'asin': 'B00IX1I3G6', 'parent_asin': 'B00IX1I3G6', 'user_id': 'AHNAE7DVWBGSJINNF4F3JBDJ2GSQ', 'timestamp': 1490208994000, 'helpful_vote': 0, 'verified_purchase': True}

152410 rows × 1 columns

As you can see, this dataset includes 152410 reviews. Our goal in the following will be to create a version of this dataset, but with a sanitized text column.

Since the dataset is very big, let’s work over an extract of it that consists of 500 reviews.

extract = reviews.truncate(after=499)
extract
full
0 {'rating': 5.0, 'title': 'Great gift', 'text': 'Having Amazon money is always good.', 'images': [], 'asin': 'B00IX1I3G6', 'parent_asin': 'B00IX1I3G6', 'user_id': 'AHZ6XMOLEWA67S3TX7IWEXXGWSOA', 'timestamp': 1549866158332, 'helpful_vote': 0, 'verified_purchase': True}
1 {'rating': 5.0, 'title': 'amazon gift card', 'text': 'Always the perfect gift. I have never given one and had someone seem or act disappointed. Just the opposite. They are thrilled and excited to have a bit of a spree. Always the perfect size and color! Arrives in 1 day in most cases. So it's never too late! Lots of cards to chose from... thank you... birthday... wedding..baby.. and many that work for many occasions...', 'images': [], 'asin': 'B005ESMMWW', 'parent_asin': 'B005ESMMWW', 'user_id': 'AFZUK3MTBIBEDQOPAK3OATUOUKLA', 'timestamp': 1599875158120, 'helpful_vote': 0, 'verified_purchase': False}
2 {'rating': 5.0, 'title': 'perfect gift', 'text': 'When you have a person who is hard to shop for.. an amazon gift card is P E R F E C T. Man or woman... No matter what their hobby... lifestyle.. or age. All you have to do is pick the $. Don't forget to mention that it is a GIFT when you check out - you will have some gift card options. I've ordered many of these over years. They are always received with glee. Woo hoo! If you're looking for a great fit for me - this is just my size! :) Best to all!', 'images': [], 'asin': 'B01K8RIM5Y', 'parent_asin': 'B005S28ZES', 'user_id': 'AFZUK3MTBIBEDQOPAK3OATUOUKLA', 'timestamp': 1535939929239, 'helpful_vote': 27, 'verified_purchase': True}
3 {'rating': 5.0, 'title': 'Nice looking', 'text': 'The tin is a nice touch and pretty large. It's about 4&#34; in diameter and about 1/2&#34; thick. I added a pretty red ribbon and it is perfect. Who doesn't love shopping on Amazon? Arrived quickly, I have Prime... but I think they ship the gift cards out SUPER fast... like over night. In case you need it for a FAST gift.', 'images': [], 'asin': 'B0091JKVU0', 'parent_asin': 'B00ADR2LV6', 'user_id': 'AFZUK3MTBIBEDQOPAK3OATUOUKLA', 'timestamp': 1418439577000, 'helpful_vote': 0, 'verified_purchase': False}
4 {'rating': 1.0, 'title': 'Not $10 Gift Cards', 'text': 'I bought this pack of Starbucks Gift cards in 2019. Ive given them to friends and I gave 2 to my daughter.<br />My daughter used one recently and it had $6.52 on the card not $10.00. She had the cashier check the balance of the other card and it had $5.32 on it! She had forgotten that she had these gift cards, so yes, 2 years later decided to use the when she found them! Do they decline in value? And then both had random amounts on them! I'm embarrassed now to have given them as gifts! Friends receiving the gift card aren't going to tell you that weren't able to cover their order with the card you gave them!!!', 'images': [], 'asin': 'B00FTGTM5E', 'parent_asin': 'B00FTGTIOE', 'user_id': 'AH5L7ILVA6HYLZOUZIQAWNHVVK3A', 'timestamp': 1638068808115, 'helpful_vote': 2, 'verified_purchase': True}
... ...
495 {'rating': 5.0, 'title': 'Great for the person who has everything', 'text': 'Easily the best thing to give to someone who has everything. After all just about everything is on amazon these days.', 'images': [], 'asin': 'B07SRDJDYD', 'parent_asin': 'B071X4ZX3X', 'user_id': 'AGCLWH5GAJZSGG6Q7RPPEUPS5PQQ', 'timestamp': 1577853022718, 'helpful_vote': 0, 'verified_purchase': True}
496 {'rating': 5.0, 'title': 'Five Stars', 'text': 'Can’t get easier than this!', 'images': [], 'asin': 'B00IX1I3G6', 'parent_asin': 'B00IX1I3G6', 'user_id': 'AE2D6CPWGFIJUD5RW3HB2D4KQSLA', 'timestamp': 1517527540320, 'helpful_vote': 0, 'verified_purchase': True}
497 {'rating': 5.0, 'title': 'Nicer than just a gift card', 'text': 'Usable after redeeming', 'images': [], 'asin': 'B0143HEFMO', 'parent_asin': 'B017T6CUS2', 'user_id': 'AHH4GBP6MBMRVTFXAWZ7O2TS7ANQ', 'timestamp': 1460839506000, 'helpful_vote': 0, 'verified_purchase': True}
498 {'rating': 3.0, 'title': 'Idk', 'text': 'I don't remember this app. Thought it was just part of Amazon', 'images': [], 'asin': 'B00IX1I3G6', 'parent_asin': 'B00IX1I3G6', 'user_id': 'AENQLNJRODPGBYYHHJJANEN53NRA', 'timestamp': 1542727126417, 'helpful_vote': 0, 'verified_purchase': True}
499 {'rating': 5.0, 'title': 'Perfect gift', 'text': 'Good gift', 'images': [], 'asin': 'B076417B1K', 'parent_asin': 'B076417B1K', 'user_id': 'AFIVWQI2Y3CEQW5HUIDJ3WTH7PEQ', 'timestamp': 1536437626301, 'helpful_vote': 0, 'verified_purchase': True}

500 rows × 1 columns

Challenge

Challenge: Exercise 3#

    Try it yourself!
    Apply your sanitization function on the text of the reviews in extract above to create a sanitized version of that column. You may want to print something when a sanitization operation was applied on the text (e.g., the text before and after) to witness the sanitization happening.
###### YOUR CODE HERE ######






###### END OF CODE ######

Tip: Use your sanitize function that you defined above and panda’s apply method to act on the full column text that you want to modify. Remove the # before the load instruction in the next code cell to display sample solutions. You may need to run the cell twice, in order to actually run the code.

# %load ../mlu_utils/solutions/lab1_ex3_solutions.txt

This concludes the data sanitization part of the lab. You should now be able to perform data sanitization on datasets of any size leveraging Amazon services and tools for security and privacy: Amazon Comprehend.

2. Experiment with Bedrock Guardrails#

In the previous section, we focused on sanitizing our training data by identifying and removing sensitive personally identifiable information using Amazon Comprehend. However, data protection is an ongoing challenge in responsible AI development.

In the second part of the lab, we will explore the use of Guardrails for Amazon Bedrock - a set of configurable safeguards designed to help mitigate potential risks and harms during the model deployment process. Bedrock Guardrails can be used to enforce a variety of checks, from content filtering to bias detection to safety constraints. By experimenting with these guardrails, we can learn how to build in proactive protections that complement our initial data cleansing efforts.

Implementing robust guardrails is a crucial step in ensuring our AI systems behave in a responsible manner that is secure and aligned with our intended use cases.

# Create boto3 clients for Bedrock
bedrock = boto3.client("bedrock", region_name="us-east-1")
bedrock_runtime = boto3.client("bedrock-runtime", region_name="us-east-1")

2.1 Create a guardrail programmatically#

First, we’re going to create a guardrail programmatically. Note that this will create a guardrail in your account; we will delete the guardrail at the end of this lab, but you can also delete it in the console.

# make sure there is no previous guardrails created by this notebook

paginator = bedrock.get_paginator('list_guardrails')
for page in paginator.paginate():
    for g in page['guardrails']:
        if g['name'].startswith('MLU-guardrail'):
            print(f"Deleting - ID: {g['id']}  Name: {g['name']}  Status: {g['status']}")
            bedrock.delete_guardrail(guardrailIdentifier=g['id'])
            print(f"  ✓ Deleted")
guardrail = bedrock.create_guardrail(
    name='MLU-guardrail-2',
    blockedInputMessaging="Sorry, your input has been blocked.",
    blockedOutputsMessaging="Sorry, your output has been blocked.",
    # Deny financial advice
    topicPolicyConfig={"topicsConfig": [
        {
            "definition": "Financial Advice",
            "name": "Finance",
            "type": "DENY"
        }
    ]},
    # Deny the word Google
    wordPolicyConfig={"wordsConfig": [
        {"text": "Google"}
    ]},
    # Anonymize NAME's
    sensitiveInformationPolicyConfig={
        'piiEntitiesConfig': [
            {
                'type': 'NAME',
                'action': 'ANONYMIZE'
            },
        ],
    },
    # Don't filter hate speech in input, filter in output
    contentPolicyConfig={"filtersConfig": [
        {
            "inputStrength": "NONE",
            "outputStrength": "LOW",
            "type": "HATE"
        }
    ]},
)

We will now need to instantiate this guardrail, i.e., create a version of this guardrail for the configuration we specified.

guardrail = bedrock.create_guardrail_version(
    guardrailIdentifier=guardrail["guardrailId"],
    description="First version",
)

guardrail_id = guardrail["guardrailId"]
guardrail_version = guardrail["version"]

print(f"Successfully created guardrail with ID {guardrail_id} version {guardrail_version}")
Successfully created guardrail with ID u4dbn0iqpskp version 1

Let’s now start with a simple example to demonstrate how to apply the guardrail we just created.

When we created the guardrail, we specified in the contentPolicyConfig section that hate speech should be filtered on the outputs (i.e., in the LLM responses) but not on the inputs (i.e., the prompts). Let’s try this!

time.sleep(20) # allow guardrail to be created and activated
# A prompt that is hate speech.
example_hate_speech = "I hate everyone who is different from me."

# Apply the guardrail with the example in the prompts.
response = bedrock_runtime.apply_guardrail(
    guardrailIdentifier=guardrail_id,
    guardrailVersion=guardrail_version,
    source="INPUT",
    content=[{"text": {"text": example_hate_speech}}],
)

print(json.dumps(response, indent=2))
{
  "ResponseMetadata": {
    "RequestId": "ef46d396-0135-494f-800d-9f02d7ca10b1",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 11 Jun 2026 22:53:59 GMT",
      "content-type": "application/json",
      "content-length": "1254",
      "connection": "keep-alive",
      "x-amzn-requestid": "ef46d396-0135-494f-800d-9f02d7ca10b1"
    },
    "RetryAttempts": 0
  },
  "usage": {
    "topicPolicyUnits": 1,
    "contentPolicyUnits": 0,
    "wordPolicyUnits": 1,
    "sensitiveInformationPolicyUnits": 0,
    "sensitiveInformationPolicyFreeUnits": 0,
    "contextualGroundingPolicyUnits": 0,
    "contentPolicyImageUnits": 0,
    "automatedReasoningPolicyUnits": 0,
    "automatedReasoningPolicies": 0
  },
  "action": "NONE",
  "actionReason": "No action.",
  "outputs": [],
  "assessments": [
    {
      "invocationMetrics": {
        "guardrailProcessingLatency": 111,
        "usage": {
          "topicPolicyUnits": 1,
          "contentPolicyUnits": 0,
          "wordPolicyUnits": 1,
          "sensitiveInformationPolicyUnits": 0,
          "sensitiveInformationPolicyFreeUnits": 0,
          "contextualGroundingPolicyUnits": 0,
          "contentPolicyImageUnits": 0,
          "automatedReasoningPolicyUnits": 0,
          "automatedReasoningPolicies": 0
        },
        "guardrailCoverage": {
          "textCharacters": {
            "guarded": 41,
            "total": 41
          }
        }
      },
      "appliedGuardrailDetails": {
        "guardrailId": "u4dbn0iqpskp",
        "guardrailVersion": "1",
        "guardrailArn": "arn:aws:bedrock:us-east-1:872034505071:guardrail/u4dbn0iqpskp",
        "guardrailOrigin": [
          "REQUEST"
        ],
        "guardrailOwnership": "SELF"
      }
    }
  ],
  "guardrailCoverage": {
    "textCharacters": {
      "guarded": 41,
      "total": 41
    }
  }
}

As you can see, the guardrail did not intervene, and performed no action ("action": "NONE"). This is what we expected, since the guardrail is not supposed to filter on INPUTs.

Let’s now try the guardrail on the example_hate_speech as if it was in the response from the LLM.

# Apply the guardrail with the example in the LLM response.
response = bedrock_runtime.apply_guardrail(
    guardrailIdentifier=guardrail_id,
    guardrailVersion=guardrail_version,
    source="OUTPUT",
    content=[{"text": {"text": example_hate_speech}}],
)

print(json.dumps(response, indent=2))
{
  "ResponseMetadata": {
    "RequestId": "b24b18d4-dfdc-47a7-bb7e-71b294125c77",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 11 Jun 2026 22:53:59 GMT",
      "content-type": "application/json",
      "content-length": "1509",
      "connection": "keep-alive",
      "x-amzn-requestid": "b24b18d4-dfdc-47a7-bb7e-71b294125c77"
    },
    "RetryAttempts": 0
  },
  "usage": {
    "topicPolicyUnits": 1,
    "contentPolicyUnits": 1,
    "wordPolicyUnits": 1,
    "sensitiveInformationPolicyUnits": 1,
    "sensitiveInformationPolicyFreeUnits": 0,
    "contextualGroundingPolicyUnits": 0,
    "contentPolicyImageUnits": 0,
    "automatedReasoningPolicyUnits": 0,
    "automatedReasoningPolicies": 0
  },
  "action": "GUARDRAIL_INTERVENED",
  "actionReason": "Guardrail blocked.",
  "outputs": [
    {
      "text": "Sorry, your output has been blocked."
    }
  ],
  "assessments": [
    {
      "contentPolicy": {
        "filters": [
          {
            "type": "HATE",
            "confidence": "HIGH",
            "filterStrength": "LOW",
            "action": "BLOCKED",
            "detected": true
          }
        ]
      },
      "invocationMetrics": {
        "guardrailProcessingLatency": 112,
        "usage": {
          "topicPolicyUnits": 1,
          "contentPolicyUnits": 1,
          "wordPolicyUnits": 1,
          "sensitiveInformationPolicyUnits": 1,
          "sensitiveInformationPolicyFreeUnits": 0,
          "contextualGroundingPolicyUnits": 0,
          "contentPolicyImageUnits": 0,
          "automatedReasoningPolicyUnits": 0,
          "automatedReasoningPolicies": 0
        },
        "guardrailCoverage": {
          "textCharacters": {
            "guarded": 41,
            "total": 41
          }
        }
      },
      "appliedGuardrailDetails": {
        "guardrailId": "u4dbn0iqpskp",
        "guardrailVersion": "1",
        "guardrailArn": "arn:aws:bedrock:us-east-1:872034505071:guardrail/u4dbn0iqpskp",
        "guardrailOrigin": [
          "REQUEST"
        ],
        "guardrailOwnership": "SELF"
      }
    }
  ],
  "guardrailCoverage": {
    "textCharacters": {
      "guarded": 41,
      "total": 41
    }
  }
}

Now, you can see that the guardrail intervened ("action": "GUARDRAIL_INTERVENED"). This is what we expected, since the guardrail is supposed to filter hate speech in OUTPUTs and answer with "Sorry, your output has been blocked.".

Challenge

Challenge: Exercise 4#

    Try it yourself!
    Try to get the guardrail to intervene on the denied topic that was specified in the guardrail, which is related to finantial advice.
###### YOUR CODE HERE ######






###### END OF CODE ######

Tip: Think of a prompt in which a user asks the LLM for specific advice on a financial transaction and send such prompt as content to the Bedrock apply_guardrails method. Remove the # before the load instruction in the next code cell to display sample solutions. You may need to run the cell twice, in order to actually run the code.

# %load ../mlu_utils/solutions/lab1_ex4_solutions.txt

2.2 Protect PIIs and sensitive information using Guardrails#

For the final part of the lab, we’re going to look at PII protection using guardrails. Guardrails for Amazon Bedrock support sensitive information filters, and allow to block or mask sensitive information such as personally identifiable information (PII) or custom regexes in user inputs and model responses. The Bedrock guardrails service is internally calling Amazon Comprehend, which ties nicely with the examples shown in part 1 of this lab.

In the guardrail we created above, we asked for NAMEs to be masked. Let’s try it out!

Note that guardrails that apply on PIIs only work on the LLM responses, and will not be applied on the prompts.

sentence = "You can call John Wick right away!"

response = bedrock_runtime.apply_guardrail(
    guardrailIdentifier=guardrail_id,
    guardrailVersion=guardrail_version,
    source="OUTPUT",
    content=[{"text": {"text": sentence}}],
)

print(json.dumps(response, indent=2))
{
  "ResponseMetadata": {
    "RequestId": "dc034c47-11dc-4940-8901-fdf3892ce52b",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "date": "Thu, 11 Jun 2026 22:54:00 GMT",
      "content-type": "application/json",
      "content-length": "1461",
      "connection": "keep-alive",
      "x-amzn-requestid": "dc034c47-11dc-4940-8901-fdf3892ce52b"
    },
    "RetryAttempts": 0
  },
  "usage": {
    "topicPolicyUnits": 1,
    "contentPolicyUnits": 1,
    "wordPolicyUnits": 1,
    "sensitiveInformationPolicyUnits": 1,
    "sensitiveInformationPolicyFreeUnits": 0,
    "contextualGroundingPolicyUnits": 0,
    "contentPolicyImageUnits": 0,
    "automatedReasoningPolicyUnits": 0,
    "automatedReasoningPolicies": 0
  },
  "action": "GUARDRAIL_INTERVENED",
  "actionReason": "Guardrail masked.",
  "outputs": [
    {
      "text": "You can call {NAME} right away!"
    }
  ],
  "assessments": [
    {
      "sensitiveInformationPolicy": {
        "piiEntities": [
          {
            "match": "John Wick",
            "type": "NAME",
            "action": "ANONYMIZED",
            "detected": true
          }
        ],
        "regexes": []
      },
      "invocationMetrics": {
        "guardrailProcessingLatency": 114,
        "usage": {
          "topicPolicyUnits": 1,
          "contentPolicyUnits": 1,
          "wordPolicyUnits": 1,
          "sensitiveInformationPolicyUnits": 1,
          "sensitiveInformationPolicyFreeUnits": 0,
          "contextualGroundingPolicyUnits": 0,
          "contentPolicyImageUnits": 0,
          "automatedReasoningPolicyUnits": 0,
          "automatedReasoningPolicies": 0
        },
        "guardrailCoverage": {
          "textCharacters": {
            "guarded": 34,
            "total": 34
          }
        }
      },
      "appliedGuardrailDetails": {
        "guardrailId": "u4dbn0iqpskp",
        "guardrailVersion": "1",
        "guardrailArn": "arn:aws:bedrock:us-east-1:872034505071:guardrail/u4dbn0iqpskp",
        "guardrailOrigin": [
          "REQUEST"
        ],
        "guardrailOwnership": "SELF"
      }
    }
  ],
  "guardrailCoverage": {
    "textCharacters": {
      "guarded": 34,
      "total": 34
    }
  }
}

You can see that the guardrail intervened, and masked the result by outputting the value "You can call {NAME} right away!\n". Similarly to the first part of this lab, masking in Bedrock guardrails replaces the detected PII by a placeholder stating the type of PII.

Guardrails can also be used to intervene on custom regexes, or when detecting specific words, which provides supplementary data protection.

Challenge

Challenge: Exercise 5#

    Try it yourself!
    Reproduce the same sanitization performed in Exercise 2 over the sentence "James and Rose are my friends. Ironically, they live at 31-33 James St, New York! You can contact them at 123-456-7890.". This will require to update the guardrail using the Bedrock client, create a new version of the guardrail, and apply the guardrail as above.
###### YOUR CODE HERE ######






###### END OF CODE ######

Tip: Define all PII types and pass a sensitiveInformationPolicyConfig with action ANONYMIZE to all PII types in piiEntitiesConfig following the documentation of the update_guardrails method. Then use create_guardrail_version to save the updated version and apply_guardrail to use it with your bedrock_runtime. You may want to wait ~1s between the create_guardrail_version and apply_guardrail calls to ensure the version is accessible. Remove the # before the load instruction in the next code cell to display sample solutions. You may need to run the cell twice, in order to actually run the code.

# %load ../mlu_utils/solutions/lab1_ex5_solutions.txt

Extra experimentation#

If you finished early, you can continue experimenting with guardrails. For example, you can try to invoke an model in Amazon Bedrock that uses your guardrail, and make it intervene.

Guardrails are a very useful tool in your responsible AI toolkit.

When you are done, don’t forget to delete your guardrail.

# Delete all guardrail created
paginator = bedrock.get_paginator('list_guardrails')
for page in paginator.paginate():
    for g in page['guardrails']:
        if g['name'].startswith('MLU-guardrail'):
            print(f"Deleting - ID: {g['id']}  Name: {g['name']}  Status: {g['status']}")
            bedrock.delete_guardrail(guardrailIdentifier=g['id'])
            print(f"  ✓ Deleted")
Deleting - ID: u4dbn0iqpskp  Name: MLU-guardrail-2  Status: READY
  ✓ Deleted

3. Quizzes#

Well done on completing the lab! Now, it's time for a brief knowledge assessment.

Challenge

Challenge: Knowledge Assessment#

    Answer the following questions to test your understanding of data protection.
import sys
sys.path.append('..')

from mlu_utils.quiz_questions import lab2_question1, lab2_question2

lab2_question1.display()
lab2_question2.display()

Conclusion#

In this lab, you have:

    Learned how to detect PII entities using Amazon Comprehend
    Created sanitization functions to protect sensitive data
    Applied sanitization to a real dataset
    Created and configured Bedrock Guardrails
    Used guardrails to protect against sensitive topics and PII exposure

Additional Resources#

    Amazon Comprehend
    Guardrails for Amazon Bedrock

Thank you!#