Streamline and govern AI

Give developers a guided experience for deploying AI models with Bedrock

AWS Bedrock is a framework for customizing LLMs based on a variety of foundational models: AWS Titan, Anthropic Claude, AI21 Labs Jambda, and more. While Bedrock is a general-purpose framework, the models that it supports have heterogenous parameter requirements.

In this guide, we'll adapt an AWS Bedrock Terraform example into a Resourcely Blueprint with attached Guardrails. This will give developers a simplified form for deploying AWS Bedrock, along with guidance and rules that help those developers configure their models successfully.

To get started, sign up for a free Resourcely account here.

The Bedrock resource uses foundational models as a baseline, takes training data from an S3 bucket, applies hyperparameter settings to the foundational model, and outputs data to an S3 bucket.

AWS Bedrock Terraform Example
data "aws_bedrock_foundation_model" "example" {
  model_id = "amazon.titan-text-express-v1"
}

resource "aws_bedrock_custom_model" "example" {
  custom_model_name     = "example-model"
  job_name              = "example-job-1"
  base_model_identifier = data.aws_bedrock_foundation_model.example.model_arn
  role_arn              = aws_iam_role.example.arn

  hyperparameters = {
    "epochCount"              = "1"
    "batchSize"               = "1"
    "learningRate"            = "0.005"
    "learningRateWarmupSteps" = "0"
  }

  output_data_config {
    s3_uri = "s3://${aws_s3_bucket.output.id}/data/"
  }

  training_data_config {
    s3_uri = "s3://${aws_s3_bucket.training.id}/data/train.jsonl"
  }
}

There are several failure cases here:

  • Incorrect hyperparameter settings

  • Misconfigured related resources (S3, IAM, etc.)

Deploying AI with a Resourcely Blueprint

First, we'll adapt the aws_bedrock_custom_model resource into a Blueprint, giving users a form that will generate Terraform and create the resource on their behalf:

AWS Bedrock Resourcely Blueprint
---
constants:
  __name: "{{ name }}_{{ __guid }}"
variables:
  model_id:
    desc: "Select the foundational model to use."
    global_value: aws_foundational_models
  custom_model_name:
    desc: "The name of the custom model."
    required: true
    suggest: "example-model"
    group: Model configuration
  job_name:
    suggest: "bedrock-example-job-1"
    desc: "The name of the job for the custom model."
    required: true
    group: Model configuration
  base_model_identifier:
    desc: "The ARN of the base foundation model to customize."
    required: true
    group: Model configuration
  epoch_count:
    desc: "Number of epochs for training."
    required: true
    suggest: "1"
    group: Hyperparameters
  batch_size:
    desc: "Batch size for the training job."
    required: true
    suggest: "8"
    group: Hyperparameters
  learning_rate:
    desc: "Learning rate for the training job."
    required: true
    suggest: "0.00001"
    group: Hyperparameters
  early_stopping_patience:
    desc: "Patience parameter for early stopping during training."
    required: true
    suggest: "6"
    group: Hyperparameters
  early_stopping_threshold:
    desc: "Threshold for early stopping."
    required: true
    suggest: "0.01"
    group: Hyperparameters
  eval_percentage:
    desc: "Percentage of the data used for evaluation."
    required: true
    suggest: "20.0"
    group: Hyperparameters
  output_s3_uri:
    desc: "S3 URI for output data."
    required: true
    links_to: resource.aws_s3_bucket.id
    group: Data configuration
  training_data_s3_uri:
    desc: "S3 URI for training data."
    required: true
    links_to: resource.aws_s3_bucket.id
    group: Data configuration

groups:
  Model configuration:
    order: 1
    desc: "Configuration for the custom model and its job settings."
  Hyperparameters:
    order: 2
    desc: "Configuration for the training hyperparameters."
  Data configuration:
    order: 3
    desc: "Configuration for the S3 locations of training and output data."
---

data "aws_caller_identity" "current" {}

data "aws_bedrock_foundation_model" "{{ __name }}" {
  model_id = {{ model_id }}
}

resource "aws_bedrock_custom_model" "{{ __name }}" {
  custom_model_name     = {{ custom_model_name }}
  job_name              = {{ job_name }}
  base_model_identifier = data.aws_bedrock_foundation_model.{{ __name }}.model_arn
  role_arn              = {{ role_arn }}
  customization_type    = "FINE_TUNING"

  hyperparameters = {
    epochCount             = {{ epoch_count }}
    batchSize              = {{ batch_size }}
    learningRate           = {{ learning_rate }}
    earlyStoppingPatience  = {{ early_stopping_patience }}
    earlyStoppingThreshold = {{ early_stopping_threshold }}
    evalPercentage         = {{ eval_percentage }}
  }

 output_data_config {
    s3_uri = "s3://{{ output_s3_uri }}/data/"
  }

  training_data_config {
    s3_uri = "s3://{{ training_data_s3_uri }}/data/train.jsonl"
  }
}

Turning Terraform parameters into UI inputs

We have added variables for most AWS Bedrock custom model options:

  • Model name

  • Job name

  • Foundational model

  • Hyperparameters

Adding these variables immediately gives us input fields in our form. See epoch count, for example

   epochCount             = {{ epoch_count }}

The {{ epoch_count }} variable references the following frontmatter:

  epoch_count:
    desc: "Number of epochs for training."
    required: true
    suggest: "1"
    group: Hyperparameters    

See the Developer UI tab for a preview of this variable in the UI

Custom model list

The Terraform provider uses a base_model_identifier, which expect the Amazon Resource Name (ARN) of the base model. To make it easy for our developers to simply pick a model (without having to look up its ARN), we create a Global Value list and reference it in the model_id variable:

  model_id:
    desc: "Select the foundational model to use."
    global_value: aws_foundational_models

The aws_foundational_models set of global values looks like this in our resulting form:

Adding complementary resources

We don't want to assume that our developers will know how to properly configure IAM and S3 to use with this Bedrock model. We'll add the following Resourcely Blueprint code:

Adding complementary resources
resource "aws_iam_role" "bedrock_{{ __name }}" {
  assume_role_policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Principal" : { "Service" : "bedrock.amazonaws.com" },
          "Action" : "sts:AssumeRole",
        }
      ]
    }
  )
}

resource "aws_iam_policy" "bedrock_access_policy_{{ __name }}" {
  policy = jsonencode(
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:GetObject",
            "s3:ListBucket",
            "s3:PutObject"
          ],
          "Resource": [
            "${ aws_s3_bucket.{{ __name }}.arn }",
            "${ aws_s3_bucket.{{ __name }}.arn }/*"
          ]
        },
        {
          "Effect": "Allow",
          "Action": [
            "bedrock:InvokeModel",
            "bedrock:GetFoundationModel",
            "bedrock:ListModels",
            "bedrock:GetModel",
            "bedrock:CreateModelCustomizationJob"
          ],
          "Resource": data.aws_bedrock_foundation_model.{{ __name }}.model_arn
        }
      ]
    }
  )
}

resource "aws_iam_role_policy_attachment" "attach_bedrock_access_policy_{{ __name }}" {
  role       = aws_iam_role.bedrock_{{ __name }}.name
  policy_arn = aws_iam_policy.bedrock_access_policy_{{ __name }}.arn
}

resource "aws_s3_bucket" "{{ __name }}" {
  bucket = "{{ bucket }}"
}

resource "aws_s3_bucket_public_access_block" "{{ __name }}" {
    bucket = aws_s3_bucket.{{ __name }}.id

    block_public_acls       = true
    block_public_policy     = true
    ignore_public_acls      = true
    restrict_public_buckets = true
}
```

This code is a good mix of hardcoded Terraform values, and Resourcely variables that take user input. We'll go through each of the added sections:

First, we add an IAM Role that is assumable. This is all hardcoded except for the name of the role, meaning that it will not be impacted by any user input.

resource "aws_iam_role" "bedrock_{{ __name }}" {
  assume_role_policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Principal" : { "Service" : "bedrock.amazonaws.com" },
          "Action" : "sts:AssumeRole",
        }
      ]
    }
  )
}

Now we have a fully completed Blueprint that abstracts away complexity, while allowing developers to generate Terraform and deploy the infrastructure they need in a secure-by-default way.

Giving guidance with Guardrails

Now that we have made it easier to deploy AI for our developers, we want to give them more guidance. All of the various models supported by Bedrock have different hyperparameter minimums and maximums. We can build this guidance into Resourcely with Guardrails.

Adding a context question

Let's say that you want to control when this guidance is displayed, through the use of a separate question. You can create Global Context questions, where users choose which AI model they want to use.

Building our Guardrail

Now that we have context, we can customize our Guardrails. This will let us put restrictions on our form that are different, based on the model they are using.

GUARDRAIL "Hyperparameter guidelines for Titan models"
  WHEN aws_bedrock_custom_model AND CONTEXT bedrock_model_type = "Titan"
    REQUIRE hyperparameters.epochCount MATCHES REGEX "^[1-5]$"

Resulting form differences

This Guardrail requires that epochCount is a whole integer from 1 to 5 when the model type is Titan. In our form for users, a Guardrail lock now appears.

Advanced Guardrail

We can add more restrictions for each of our model-specific Guardrails:

GUARDRAIL "Hyperparameter guidelines for Titan models"
  WHEN aws_bedrock_custom_model AND CONTEXT bedrock_model_type = "Titan"
    REQUIRE hyperparameters.epochCount MATCHES REGEX "^[1-5]$" 
      AND hyperparameters.batchSize = "1" 
      AND hyperparameters.learningRate MATCHES REGEX "^(0\.0000001|\.000001|0\.00001)$" AND hyperparameters.learningRateWarmupSteps MATCHES REGEX "^[0-20]$"

This advanced Guardrail will guide the user to select appropriate values for Titan mode parameters, ensuring that their models don't needlessly fail - wasting infrastructure and time.

Final Blueprint

Putting it all together, here is the final Resourcely Blueprint for streamlining AI deployment:

AWS Bedrock with S3 and IAM Blueprint
---
constants:
  __name: "{{ name }}_{{ __guid }}"
variables:
  YourIAMUserName:
    suggest: "myusername"
  model_id:
    desc: "Select the foundational model to use."
    global_value: aws_foundational_models
  custom_model_name:
    desc: "The name of the custom model."
    required: true
    suggest: "example-model"
    group: Model configuration
  job_name:
    suggest: "bedrock-example-job-1"
    desc: "The name of the job for the custom model."
    required: true
    group: Model configuration
  base_model_identifier:
    desc: "The ARN of the base foundation model to customize."
    required: true
    group: Model configuration
  role_arn:
    desc: "The ARN of the IAM role with permissions to manage the custom model."
    required: true
    links_to: resource.aws_iam_role.arn
    group: Model configuration
  epoch_count:
    desc: "Number of epochs for training."
    required: true
    suggest: "1"
    group: Hyperparameters
  batch_size:
    desc: "Batch size for the training job."
    required: true
    suggest: "8"
    group: Hyperparameters
  learning_rate:
    desc: "Learning rate for the training job."
    required: true
    suggest: "0.00001"
    group: Hyperparameters
  early_stopping_patience:
    desc: "Patience parameter for early stopping during training."
    required: true
    suggest: "6"
    group: Hyperparameters
  early_stopping_threshold:
    desc: "Threshold for early stopping."
    required: true
    suggest: "0.01"
    group: Hyperparameters
  eval_percentage:
    desc: "Percentage of the data used for evaluation."
    required: true
    suggest: "20.0"
    group: Hyperparameters
  output_s3_uri:
    desc: "S3 URI for output data."
    required: true
    links_to: resource.aws_s3_bucket.id
    group: Data configuration
  training_data_s3_uri:
    desc: "S3 URI for training data."
    required: true
    links_to: resource.aws_s3_bucket.id
    group: Data configuration
  bucket:
    group: Data configuration
    suggest: mybasicbedrockbucket

groups:
  Model configuration:
    order: 1
    desc: "Configuration for the custom model and its job settings."
  Hyperparameters:
    order: 2
    desc: "Configuration for the training hyperparameters."
  Data configuration:
    order: 3
    desc: "Configuration for the S3 locations of training and output data."
  Bucket:
    order: 4
    desc: "The S3 bucket where the training data is stored"
---

data "aws_caller_identity" "current" {}

data "aws_bedrock_foundation_model" "{{ __name }}" {
  model_id = {{ model_id }}
}

resource "aws_bedrock_custom_model" "{{ __name }}" {
  custom_model_name     = {{ custom_model_name }}
  job_name              = {{ job_name }}
  base_model_identifier = data.aws_bedrock_foundation_model.{{ __name }}.model_arn
  role_arn              = {{ role_arn }}
  customization_type    = "FINE_TUNING"

  hyperparameters = {
    epochCount             = {{ epoch_count }}
    batchSize              = {{ batch_size }}
    learningRate           = {{ learning_rate }}
    earlyStoppingPatience  = {{ early_stopping_patience }}
    earlyStoppingThreshold = {{ early_stopping_threshold }}
    evalPercentage         = {{ eval_percentage }}
  }

 output_data_config {
    s3_uri = "s3://{{ output_s3_uri }}/data/"
  }

  training_data_config {
    s3_uri = "s3://{{ training_data_s3_uri }}/data/train.jsonl"
  }
}

resource "aws_iam_role" "bedrock_{{ __name }}" {
  assume_role_policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Principal" : { "Service" : "bedrock.amazonaws.com" },
          "Action" : "sts:AssumeRole",
        }
      ]
    }
  )
}

resource "aws_iam_policy" "bedrock_access_policy_{{ __name }}" {
  policy = jsonencode(
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:GetObject",
            "s3:ListBucket",
            "s3:PutObject"
          ],
          "Resource": [
            "${ aws_s3_bucket.{{ __name }}.arn }",
            "${ aws_s3_bucket.{{ __name }}.arn }/*"
          ]
        },
        {
          "Effect": "Allow",
          "Action": [
            "bedrock:InvokeModel",
            "bedrock:GetFoundationModel",
            "bedrock:ListModels",
            "bedrock:GetModel",
            "bedrock:CreateModelCustomizationJob"
          ],
          "Resource": data.aws_bedrock_foundation_model.{{ __name }}.model_arn
        }
      ]
    }
  )
}

resource "aws_iam_role_policy_attachment" "attach_bedrock_access_policy_{{ __name }}" {
  role       = aws_iam_role.bedrock_{{ __name }}.name
  policy_arn = aws_iam_policy.bedrock_access_policy_{{ __name }}.arn
}

resource "aws_s3_bucket" "{{ __name }}" {
  bucket = "{{ bucket }}"
}

resource "aws_s3_bucket_public_access_block" "{{ __name }}" {
    bucket = aws_s3_bucket.{{ __name }}.id

    block_public_acls       = true
    block_public_policy     = true
    ignore_public_acls      = true
    restrict_public_buckets = true
}

Hyperparameter Guardrail
GUARDRAIL "Hyperparameter guidelines for Titan models"
  WHEN aws_bedrock_custom_model AND CONTEXT bedrock_model_type = "Titan"
    REQUIRE hyperparameters.epochCount MATCHES REGEX "^[1-5]$" 
      AND hyperparameters.batchSize = "1" 
      AND hyperparameters.learningRate MATCHES REGEX "^(0\.0000001|\.000001|0\.00001)$" AND hyperparameters.learningRateWarmupSteps MATCHES REGEX "^[0-20]$"

Terraform is created

When this form is completed, properly configured Terraform that meets your requirements is generated and submitted through your CI pipeline. This gives your company the benefits of infrastructure as code, without the steep learning curve or specialized cloud services knowledge.

Get started with Resourcely

If you want to recreate this for yourself, you can do so using Resourcely! Sign up for free here, and find the preloaded Bedrock Guardrails in the Foundry.

Last updated