diff --git a/.changelog/39440.txt b/.changelog/39440.txt new file mode 100644 index 00000000000..4b73bc8ea43 --- /dev/null +++ b/.changelog/39440.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_bedrockagent_agent: Add `guardrail_configuration` argument +``` diff --git a/internal/service/bedrockagent/agent.go b/internal/service/bedrockagent/agent.go index a4cf7bf3811..5b7dccb5a7f 100644 --- a/internal/service/bedrockagent/agent.go +++ b/internal/service/bedrockagent/agent.go @@ -100,6 +100,19 @@ func (r *agentResource) Schema(ctx context.Context, request resource.SchemaReque "foundation_model": schema.StringAttribute{ Required: true, }, + "guardrail_configuration": schema.ListAttribute{ + CustomType: fwtypes.NewListNestedObjectTypeOf[guardrailConfigurationModel](ctx), + Optional: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + ElementType: types.ObjectType{ + AttrTypes: fwtypes.AttributeTypesMust[guardrailConfigurationModel](ctx), + }, + }, names.AttrID: framework.IDAttribute(), "idle_session_ttl_in_seconds": schema.Int64Attribute{ Optional: true, @@ -282,6 +295,7 @@ func (r *agentResource) Update(ctx context.Context, request resource.UpdateReque !new.Description.Equal(old.Description) || !new.Instruction.Equal(old.Instruction) || !new.FoundationModel.Equal(old.FoundationModel) || + !new.GuardrailConfiguration.Equal(old.GuardrailConfiguration) || !new.PromptOverrideConfiguration.Equal(old.PromptOverrideConfiguration) { input := &bedrockagent.UpdateAgentInput{ AgentId: fwflex.StringFromFramework(ctx, new.AgentID), @@ -297,6 +311,16 @@ func (r *agentResource) Update(ctx context.Context, request resource.UpdateReque input.CustomerEncryptionKeyArn = fwflex.StringFromFramework(ctx, new.CustomerEncryptionKeyARN) } + if !new.GuardrailConfiguration.Equal(old.GuardrailConfiguration) && !new.GuardrailConfiguration.IsNull() { + guardrailConfiguration := &awstypes.GuardrailConfiguration{} + response.Diagnostics.Append(fwflex.Expand(ctx, new.GuardrailConfiguration, guardrailConfiguration)...) + if response.Diagnostics.HasError() { + return + } + + input.GuardrailConfiguration = guardrailConfiguration + } + if !new.PromptOverrideConfiguration.Equal(old.PromptOverrideConfiguration) { promptOverrideConfiguration := &awstypes.PromptOverrideConfiguration{} response.Diagnostics.Append(fwflex.Expand(ctx, new.PromptOverrideConfiguration, promptOverrideConfiguration)...) @@ -570,6 +594,7 @@ type agentResourceModel struct { CustomerEncryptionKeyARN fwtypes.ARN `tfsdk:"customer_encryption_key_arn"` Description types.String `tfsdk:"description"` FoundationModel types.String `tfsdk:"foundation_model"` + GuardrailConfiguration fwtypes.ListNestedObjectValueOf[guardrailConfigurationModel] `tfsdk:"guardrail_configuration"` ID types.String `tfsdk:"id"` IdleSessionTTLInSeconds types.Int64 `tfsdk:"idle_session_ttl_in_seconds"` Instruction types.String `tfsdk:"instruction"` @@ -591,6 +616,11 @@ func (m *agentResourceModel) setID() { m.ID = m.AgentID } +type guardrailConfigurationModel struct { + GuardrailIdentifier types.String `tfsdk:"guardrail_identifier"` + GuardrailVersion types.String `tfsdk:"guardrail_version"` +} + type promptOverrideConfigurationModel struct { OverrideLambda fwtypes.ARN `tfsdk:"override_lambda"` PromptConfigurations fwtypes.SetNestedObjectValueOf[promptConfigurationModel] `tfsdk:"prompt_configurations"` diff --git a/internal/service/bedrockagent/agent_test.go b/internal/service/bedrockagent/agent_test.go index 574f06e1998..d56d821dec5 100644 --- a/internal/service/bedrockagent/agent_test.go +++ b/internal/service/bedrockagent/agent_test.go @@ -36,6 +36,7 @@ func TestAccBedrockAgentAgent_basic(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccCheckAgentExists(ctx, resourceName, &v), resource.TestCheckResourceAttr(resourceName, "agent_name", rName), + resource.TestCheckResourceAttr(resourceName, "guardrail_configuration.#", acctest.Ct0), resource.TestCheckResourceAttr(resourceName, "prompt_override_configuration.#", acctest.Ct1), resource.TestCheckResourceAttr(resourceName, names.AttrDescription, "basic claude"), resource.TestCheckResourceAttr(resourceName, "prepare_agent", acctest.CtTrue), @@ -187,6 +188,65 @@ func TestAccBedrockAgentAgent_addPrompt(t *testing.T) { }) } +func TestAccBedrockAgentAgent_guardrail(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_bedrockagent_agent.test" + guardrailResourceName := "aws_bedrock_guardrail.test" + var v awstypes.Agent + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckPartitionHasService(t, names.BedrockEndpointID) }, + ErrorCheck: acctest.ErrorCheck(t, names.BedrockAgentServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAgentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAgentConfig_guardrail_noConfig(rName, "anthropic.claude-v2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAgentExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "agent_name", rName), + resource.TestCheckResourceAttr(resourceName, "guardrail_configuration.#", acctest.Ct0), + ), + }, + { + Config: testAccAgentConfig_guardrail_withConfig(rName, "anthropic.claude-v2", "DRAFT"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAgentExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "agent_name", rName), + resource.TestCheckResourceAttr(resourceName, "guardrail_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttrPair(resourceName, "guardrail_configuration.0.guardrail_identifier", guardrailResourceName, "guardrail_id"), + resource.TestCheckResourceAttr(resourceName, "guardrail_configuration.0.guardrail_version", "DRAFT"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"skip_resource_in_use_check"}, + }, + { + Config: testAccAgentConfig_guardrail_withConfig(rName, "anthropic.claude-v2", acctest.Ct1), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAgentExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "agent_name", rName), + resource.TestCheckResourceAttr(resourceName, "guardrail_configuration.#", acctest.Ct1), + resource.TestCheckResourceAttrPair(resourceName, "guardrail_configuration.0.guardrail_identifier", guardrailResourceName, "guardrail_id"), + resource.TestCheckResourceAttr(resourceName, "guardrail_configuration.0.guardrail_version", acctest.Ct1), + ), + }, + { + Config: testAccAgentConfig_guardrail_noConfig(rName, "anthropic.claude-v2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAgentExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "agent_name", rName), + resource.TestCheckResourceAttr(resourceName, "guardrail_configuration.#", acctest.Ct0), + ), + }, + }, + }) +} + func TestAccBedrockAgentAgent_update(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -391,6 +451,36 @@ data "aws_partition" "current_agent" {} `, rName, model) } +func testAccAgent_guardrail(rName string) string { + return fmt.Sprintf(` +data "aws_iam_policy_document" "test_agent_guardrail_permissions" { + statement { + actions = ["bedrock:ApplyGuardrail"] + resources = [aws_bedrock_guardrail.test.guardrail_arn] + } +} + +resource "aws_iam_role_policy" "test_agent_guardrail" { + role = aws_iam_role.test_agent.id + policy = data.aws_iam_policy_document.test_agent_guardrail_permissions.json +} + +resource "aws_bedrock_guardrail" "test" { + name = %[1]q + description = %[1]q + blocked_input_messaging = "Sorry, I cannot answer this question." + blocked_outputs_messaging = "Sorry, I cannot answer this question." + content_policy_config { + filters_config { + input_strength = "MEDIUM" + output_strength = "MEDIUM" + type = "HATE" + } + } +} +`, rName) +} + func testAccAgentConfig_basic(rName, model, description string) string { return acctest.ConfigCompose(testAccAgent_base(rName, model), fmt.Sprintf(` resource "aws_bedrockagent_agent" "test" { @@ -560,3 +650,31 @@ resource "aws_bedrockagent_agent" "test" { } `, rName, model, desc)) } + +func testAccAgentConfig_guardrail_noConfig(rName, model string) string { + return acctest.ConfigCompose(testAccAgent_base(rName, model), testAccAgent_guardrail(rName), fmt.Sprintf(` +resource "aws_bedrockagent_agent" "test" { + agent_name = %[1]q + agent_resource_role_arn = aws_iam_role.test_agent.arn + instruction = file("${path.module}/test-fixtures/instruction.txt") + foundation_model = %[2]q + skip_resource_in_use_check = true +} +`, rName, model)) +} + +func testAccAgentConfig_guardrail_withConfig(rName, model, guardrailVersion string) string { + return acctest.ConfigCompose(testAccAgent_base(rName, model), testAccAgent_guardrail(rName), fmt.Sprintf(` +resource "aws_bedrockagent_agent" "test" { + agent_name = %[1]q + agent_resource_role_arn = aws_iam_role.test_agent.arn + instruction = file("${path.module}/test-fixtures/instruction.txt") + foundation_model = %[2]q + skip_resource_in_use_check = true + guardrail_configuration { + guardrail_identifier = aws_bedrock_guardrail.test.guardrail_id + guardrail_version = %[3]q + } +} +`, rName, model, guardrailVersion)) +} diff --git a/website/docs/r/bedrockagent_agent.html.markdown b/website/docs/r/bedrockagent_agent.html.markdown index 0939a97c525..58fe852c0bc 100644 --- a/website/docs/r/bedrockagent_agent.html.markdown +++ b/website/docs/r/bedrockagent_agent.html.markdown @@ -79,6 +79,7 @@ The following arguments are optional: * `customer_encryption_key_arn` - (Optional) ARN of the AWS KMS key that encrypts the agent. * `description` - (Optional) Description of the agent. +* `guardrail_config` - (Optional) Details about the guardrail associated with the agent. See [`guardrail_config` Block](#guardrail_config-block) for details. * `idle_session_ttl_in_seconds` - (Optional) Number of seconds for which Amazon Bedrock keeps information about a user's conversation with the agent. A user interaction remains active for the amount of time specified. If no conversation occurs during this time, the session expires and Amazon Bedrock deletes any data provided before the timeout. * `instruction` - (Optional) Instructions that tell the agent what it should do and how it should interact with users. * `prepare_agent` (Optional) Whether to prepare the agent after creation or modification. Defaults to `true`. @@ -86,6 +87,13 @@ The following arguments are optional: * `skip_resource_in_use_check` - (Optional) Whether the in-use check is skipped when deleting the agent. * `tags` - (Optional) Map of tags assigned to the resource. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +### `guardrail_config` Block + +The `guardrail_config` configuration block supports the following arguments: + +* `guardrail_identifier` - (Optional) Unique identifier of the guardrail. +* `guardrail_version` - (Optional) Version of the guardrail. + ### `prompt_override_configuration` Block The `prompt_override_configuration` configuration block supports the following arguments: