diff --git a/packages/@aws-cdk/aws-bedrock-alpha/README.md b/packages/@aws-cdk/aws-bedrock-alpha/README.md index 39916d00c732e..7315893598d09 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/README.md +++ b/packages/@aws-cdk/aws-bedrock-alpha/README.md @@ -35,6 +35,20 @@ This construct library facilitates the deployment of Bedrock Agents, enabling yo - [Agent Collaboration](#agent-collaboration) - [Custom Orchestration](#custom-orchestration) - [Agent Alias](#agent-alias) +- [Guardrails](#guardrails) + - [Guardrail Properties](#guardrail-properties) + - [Filter Types](#filter-types) + - [Content Filters](#content-filters) + - [Denied Topics](#denied-topics) + - [Word Filters](#word-filters) + - [PII Filters](#pii-filters) + - [Regex Filters](#regex-filters) + - [Contextual Grounding Filters](#contextual-grounding-filters) + - [Guardrail Methods](#guardrail-methods) + - [Guardrail Permissions](#guardrail-permissions) + - [Guardrail Metrics](#guardrail-metrics) + - [Importing Guardrails](#importing-guardrails) + - [Guardrail Versioning](#guardrail-versioning) - [Prompts](#prompts) - [Prompt Variants](#prompt-variants) - [Basic Text Prompt](#basic-text-prompt) @@ -66,6 +80,29 @@ const agent = new bedrock.Agent(this, 'Agent', { }); ``` +You can also create an agent with a guardrail: + +```ts fixture=default +// Create a guardrail to filter inappropriate content +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', + description: 'Legal ethical guardrails.', +}); + +guardrail.addContentFilter({ + type: bedrock.ContentFilterType.SEXUAL, + inputStrength: bedrock.ContentFilterStrength.HIGH, + outputStrength: bedrock.ContentFilterStrength.MEDIUM, +}); + +// Create an agent with the guardrail +const agentWithGuardrail = new bedrock.Agent(this, 'AgentWithGuardrail', { + foundationModel: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_HAIKU_V1_0, + instruction: 'You are a helpful and friendly agent that answers questions about literature.', + guardrail: guardrail, +}); +``` + ### Agent Properties The Bedrock Agent class supports the following properties. @@ -81,6 +118,8 @@ The Bedrock Agent class supports the following properties. | kmsKey | kms.IKey | No | The KMS key of the agent if custom encryption is configured. Defaults to AWS managed key | | description | string | No | A description of the agent. Defaults to no description | | actionGroups | AgentActionGroup[] | No | The Action Groups associated with the agent | +| guardrail | IGuardrail | No | The guardrail that will be associated with the agent. Defaults to no guardrail | +| memory | Memory | No | The type and configuration of the memory to maintain context across multiple sessions and recall past interactions. Defaults to no memory | | promptOverrideConfiguration | PromptOverrideConfiguration | No | Overrides some prompt templates in different parts of an agent sequence configuration | | userInputEnabled | boolean | No | Select whether the agent can prompt additional information from the user when it lacks enough information. Defaults to false | | codeInterpreterEnabled | boolean | No | Select whether the agent can generate, run, and troubleshoot code when trying to complete a task. Defaults to false | @@ -621,6 +660,507 @@ const agentAlias = new bedrock.AgentAlias(this, 'myAlias', { }); ``` +## Guardrails + +Amazon Bedrock's Guardrails feature enables you to implement robust governance and control mechanisms for your generative AI applications, ensuring alignment with your specific use cases and responsible AI policies. Guardrails empowers you to create multiple tailored policy configurations, each designed to address the unique requirements and constraints of different use cases. These policy configurations can then be seamlessly applied across multiple foundation models (FMs) and Agents, ensuring a consistent user experience and standardizing safety, security, and privacy controls throughout your generative AI ecosystem. + +With Guardrails, you can define and enforce granular, customizable policies to precisely govern the behavior of your generative AI applications. You can configure the following policies in a guardrail to avoid undesirable and harmful content and remove sensitive information for privacy protection. + +Content filters – Adjust filter strengths to block input prompts or model responses containing harmful content. +Denied topics – Define a set of topics that are undesirable in the context of your application. These topics will be blocked if detected in user queries or model responses. +Word filters – Configure filters to block undesirable words, phrases, and profanity. Such words can include offensive terms, competitor names etc. +Sensitive information filters – Block or mask sensitive information such as personally identifiable information (PII) or custom regex in user inputs and model responses. +You can create a Guardrail with a minimum blockedInputMessaging, blockedOutputsMessaging and default content filter policy. + +### Basic Guardrail Creation + +#### TypeScript + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', + description: 'Legal ethical guardrails.', +}); + +// add at least one policy for the guardrail +``` + +### Guardrail Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| guardrailName | string | Yes | The name of the guardrail | +| description | string | No | The description of the guardrail | +| blockedInputMessaging | string | No | The message to return when the guardrail blocks a prompt. Default: "Sorry, your query violates our usage policy." | +| blockedOutputsMessaging | string | No | The message to return when the guardrail blocks a model response. Default: "Sorry, I am unable to answer your question because of our usage policy." | +| kmsKey | IKey | No | A custom KMS key to use for encrypting data. Default: Your data is encrypted by default with a key that AWS owns and manages for you. | +| crossRegionConfig | GuardrailCrossRegionConfigProperty | No | The cross-region configuration for the guardrail. This enables cross-region inference for enhanced language support and filtering capabilities. Default: No cross-region configuration | +| contentFilters | ContentFilter[] | No | The content filters to apply to the guardrail | +| contentFiltersTierConfig | TierConfig | No | The tier configuration to apply to content filters. Default: TierConfig.CLASSIC | +| deniedTopics | Topic[] | No | Up to 30 denied topics to block user inputs or model responses associated with the topic | +| topicsTierConfig | TierConfig | No | The tier configuration to apply to topic filters. Default: TierConfig.CLASSIC | +| wordFilters | string[] | No | The word filters to apply to the guardrail | +| managedWordListFilters | ManagedWordFilterType[] | No | The managed word filters to apply to the guardrail | +| piiFilters | PIIFilter[] | No | The PII filters to apply to the guardrail | +| regexFilters | RegexFilter[] | No | The regular expression (regex) filters to apply to the guardrail | +| contextualGroundingFilters | ContextualGroundingFilter[] | No | The contextual grounding filters to apply to the guardrail | + +### Filter Types + +#### Content Filters + +Content filters allow you to block input prompts or model responses containing harmful content. You can adjust the filter strength and configure separate actions for input and output. + +##### Content Filter Configuration + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', + // Configure tier for content filters (optional) + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, +}); + +guardrail.addContentFilter({ + type: bedrock.ContentFilterType.SEXUAL, + inputStrength: bedrock.ContentFilterStrength.HIGH, + outputStrength: bedrock.ContentFilterStrength.MEDIUM, + // props below are optional + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: true, + inputModalities: [bedrock.ModalityType.TEXT, bedrock.ModalityType.IMAGE], + outputModalities: [bedrock.ModalityType.TEXT], +}); +``` + +Available content filter types: + +- `SEXUAL`: Describes input prompts and model responses that indicates sexual interest, activity, or arousal +- `VIOLENCE`: Describes input prompts and model responses that includes glorification of or threats to inflict physical pain +- `HATE`: Describes input prompts and model responses that discriminate, criticize, insult, denounce, or dehumanize a person or group +- `INSULTS`: Describes input prompts and model responses that includes demeaning, humiliating, mocking, insulting, or belittling language +- `MISCONDUCT`: Describes input prompts and model responses that seeks or provides information about engaging in misconduct activity +- `PROMPT_ATTACK`: Enable to detect and block user inputs attempting to override system instructions + +Available content filter strengths: + +- `NONE`: No filtering +- `LOW`: Light filtering +- `MEDIUM`: Moderate filtering +- `HIGH`: Strict filtering + +Available guardrail actions: + +- `BLOCK`: Blocks the content from being processed +- `ANONYMIZE`: Masks the content with an identifier tag +- `NONE`: Takes no action + +> Warning: the ANONYMIZE action is not available in all configurations. Please refer to the documentation of each filter to see which ones +> support + +Available modality types: + +- `TEXT`: Text modality for content filters +- `IMAGE`: Image modality for content filters + +#### Tier Configuration + +Guardrails support tier configurations that determine the level of language support and robustness for content and topic filters. You can configure separate tier settings for content filters and topic filters. + +##### Tier Configuration Options + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', + // Configure tier for content filters + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, + // Configure tier for topic filters + topicsTierConfig: bedrock.TierConfig.CLASSIC, +}); +``` + +Available tier configurations: + +- `CLASSIC`: Provides established guardrails functionality supporting English, French, and Spanish languages +- `STANDARD`: Provides a more robust solution than the CLASSIC tier and has more comprehensive language support. This tier requires that your guardrail use cross-Region inference + +> Note: The STANDARD tier provides enhanced language support and more comprehensive filtering capabilities, but requires cross-Region inference to be enabled for your guardrail. + +#### Cross-Region Configuration + +You can configure a system-defined guardrail profile to use with your guardrail. Guardrail profiles define the destination AWS Regions where guardrail inference requests can be automatically routed. Using guardrail profiles helps maintain guardrail performance and reliability when demand increases. + +##### Cross-Region Configuration Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| guardrailProfileArn | string | Yes | The ARN of the system-defined guardrail profile that defines the destination AWS Regions where guardrail inference requests can be automatically routed | + +##### Cross-Region Configuration Example + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', + description: 'Guardrail with cross-region configuration for enhanced language support', + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/my-profile', + }, + // Use STANDARD tier for enhanced capabilities + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, + topicsTierConfig: bedrock.TierConfig.STANDARD, +}); +``` + +> Note: Cross-region configuration is required when using the STANDARD tier for content and topic filters. It helps maintain guardrail performance and reliability when demand increases by automatically routing inference requests to appropriate regions. + +You will need to provide the necessary permissions for cross region: https://docs.aws.amazon.com/bedrock/latest/userguide/guardrail-profiles-permissions.html . + +#### Denied Topics + +Denied topics allow you to define a set of topics that are undesirable in the context of your application. These topics will be blocked if detected in user queries or model responses. You can configure separate actions for input and output. + +##### Denied Topic Configuration + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', + // Configure tier for topic filters (optional) + topicsTierConfig: bedrock.TierConfig.STANDARD, +}); + +// Use a predefined topic +guardrail.addDeniedTopicFilter(bedrock.Topic.FINANCIAL_ADVICE); + +// Create a custom topic with input/output actions +guardrail.addDeniedTopicFilter( + bedrock.Topic.custom({ + name: 'Legal_Advice', + definition: 'Offering guidance or suggestions on legal matters, legal actions, interpretation of laws, or legal rights and responsibilities.', + examples: [ + 'Can I sue someone for this?', + 'What are my legal rights in this situation?', + 'Is this action against the law?', + 'What should I do to file a legal complaint?', + 'Can you explain this law to me?', + ], + // props below are optional + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: true, + }) +); +``` + +#### Word Filters + +Word filters allow you to block specific words, phrases, or profanity in user inputs and model responses. You can configure separate actions for input and output. + +##### Word Filter Configuration + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', +}); + +// Add managed word list with input/output actions +guardrail.addManagedWordListFilter({ + type: bedrock.ManagedWordFilterType.PROFANITY, + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: true, +}); + +// Add individual words +guardrail.addWordFilter({text: 'drugs'}); +guardrail.addWordFilter({text: 'competitor'}); + +// Add words from a file +guardrail.addWordFilterFromFile('./scripts/wordsPolicy.csv'); +``` + +#### PII Filters + +PII filters allow you to detect and handle personally identifiable information in user inputs and model responses. You can configure separate actions for input and output. + +The PII types are organized into enum-like classes for better type safety and transpilation compatibility: + +- **GeneralPIIType**: General PII types like addresses, emails, names, phone numbers +- **FinancePIIType**: Financial information like credit card numbers, PINs, SWIFT codes +- **InformationTechnologyPIIType**: IT-related data like URLs, IP addresses, AWS keys +- **USASpecificPIIType**: US-specific identifiers like SSNs, passport numbers +- **CanadaSpecificPIIType**: Canada-specific identifiers like health numbers, SINs +- **UKSpecificPIIType**: UK-specific identifiers like NHS numbers, NI numbers + +##### PII Filter Configuration + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', +}); + +// Add PII filter for addresses with input/output actions +guardrail.addPIIFilter({ + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.BLOCK, + // below props are optional + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.ANONYMIZE, + outputEnabled: true, +}); + +// Add PII filter for credit card numbers with input/output actions +guardrail.addPIIFilter({ + type: bedrock.FinancePIIType.CREDIT_DEBIT_CARD_NUMBER, + action: bedrock.GuardrailAction.BLOCK, + // below props are optional + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.ANONYMIZE, + outputEnabled: true, +}); + +// Add PII filter for email addresses +guardrail.addPIIFilter({ + type: bedrock.GeneralPIIType.EMAIL, + action: bedrock.GuardrailAction.ANONYMIZE, +}); + +// Add PII filter for US Social Security Numbers +guardrail.addPIIFilter({ + type: bedrock.USASpecificPIIType.US_SOCIAL_SECURITY_NUMBER, + action: bedrock.GuardrailAction.BLOCK, +}); + +// Add PII filter for IP addresses +guardrail.addPIIFilter({ + type: bedrock.InformationTechnologyPIIType.IP_ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, +}); +``` + +##### Available PII Types + +**GeneralPIIType:** + +- `ADDRESS`: Physical addresses +- `AGE`: Individual's age +- `DRIVER_ID`: Driver's license numbers +- `EMAIL`: Email addresses +- `LICENSE_PLATE`: Vehicle license plates +- `NAME`: Individual names +- `PASSWORD`: Passwords +- `PHONE`: Phone numbers +- `USERNAME`: User account names +- `VEHICLE_IDENTIFICATION_NUMBER`: Vehicle VINs + +**FinancePIIType:** + +- `CREDIT_DEBIT_CARD_CVV`: Card verification codes +- `CREDIT_DEBIT_CARD_EXPIRY`: Card expiration dates +- `CREDIT_DEBIT_CARD_NUMBER`: Credit/debit card numbers +- `PIN`: Personal identification numbers +- `SWIFT_CODE`: Bank SWIFT codes +- `INTERNATIONAL_BANK_ACCOUNT_NUMBER`: IBAN numbers + +**InformationTechnologyPIIType:** + +- `URL`: Web addresses +- `IP_ADDRESS`: IPv4 addresses +- `MAC_ADDRESS`: Network interface MAC addresses +- `AWS_ACCESS_KEY`: AWS access key IDs +- `AWS_SECRET_KEY`: AWS secret access keys + +**USASpecificPIIType:** + +- `US_BANK_ACCOUNT_NUMBER`: US bank account numbers +- `US_BANK_ROUTING_NUMBER`: US bank routing numbers +- `US_INDIVIDUAL_TAX_IDENTIFICATION_NUMBER`: US ITINs +- `US_PASSPORT_NUMBER`: US passport numbers +- `US_SOCIAL_SECURITY_NUMBER`: US Social Security Numbers + +**CanadaSpecificPIIType:** + +- `CA_HEALTH_NUMBER`: Canadian Health Service Numbers +- `CA_SOCIAL_INSURANCE_NUMBER`: Canadian Social Insurance Numbers + +**UKSpecificPIIType:** + +- `UK_NATIONAL_HEALTH_SERVICE_NUMBER`: UK NHS numbers +- `UK_NATIONAL_INSURANCE_NUMBER`: UK National Insurance numbers +- `UK_UNIQUE_TAXPAYER_REFERENCE_NUMBER`: UK UTR numbers + +#### Regex Filters + +Regex filters allow you to detect and handle custom patterns in user inputs and model responses. You can configure separate actions for input and output. + +##### Regex Filter Configuration + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', +}); +// Add regex filter with input/output actions +guardrail.addRegexFilter({ + name: 'TestRegexFilter', + pattern: 'test-pattern', + action: bedrock.GuardrailAction.ANONYMIZE, + // below props are optional + description: 'This is a test regex filter', + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.ANONYMIZE, + outputEnabled: true, +}); +``` + +#### Contextual Grounding Filters + +Contextual grounding filters allow you to ensure that model responses are factually correct and relevant to the user's query. You can configure the action and enable/disable the filter. + +##### Contextual Grounding Filter Configuration + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', +}); +// Add contextual grounding filter with action and enabled flag +guardrail.addContextualGroundingFilter({ + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.8, + // the properties below are optional + action: bedrock.GuardrailAction.BLOCK, + enabled: true, +}); +``` + +### Guardrail Methods + +| Method | Description | +|--------|-------------| +| `addContentFilter()` | Adds a content filter to the guardrail | +| `addDeniedTopicFilter()` | Adds a denied topic filter to the guardrail | +| `addWordFilter()` | Adds a word filter to the guardrail | +| `addManagedWordListFilter()` | Adds a managed word list filter to the guardrail | +| `addWordFilterFromFile()` | Adds word filters from a file to the guardrail | +| `addPIIFilter()` | Adds a PII filter to the guardrail | +| `addRegexFilter()` | Adds a regex filter to the guardrail | +| `addContextualGroundingFilter()` | Adds a contextual grounding filter to the guardrail | +| `createVersion()` | Creates a new version of the guardrail | + +### Guardrail Permissions + +Guardrails provide methods to grant permissions to other resources that need to interact with the guardrail. + +#### Permission Methods + +| Method | Description | Parameters | +|--------|-------------|------------| +| `grant(grantee, ...actions)` | Grants the given principal identity permissions to perform actions on this guardrail | `grantee`: The principal to grant permissions to
`actions`: The actions to grant (e.g., `bedrock:GetGuardrail`, `bedrock:ListGuardrails`) | +| `grantApply(grantee)` | Grants the given identity permissions to apply the guardrail | `grantee`: The principal to grant permissions to | + +#### Permission Examples + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', +}); + +const lambdaFunction = new lambda.Function(this, 'testLambda', { + runtime: lambda.Runtime.PYTHON_3_12, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, '../lambda/my-code')), +}); + +// Grant specific permissions to a Lambda function +guardrail.grant(lambdaFunction, 'bedrock:GetGuardrail', 'bedrock:ListGuardrails'); + +// Grant permissions to apply the guardrail +guardrail.grantApply(lambdaFunction); +``` + +### Guardrail Metrics + +Amazon Bedrock provides metrics for your guardrails, allowing you to monitor their effectiveness and usage. These metrics are available in CloudWatch and can be used to create dashboards and alarms. + +#### Metrics Examples + +```ts fixture=default +import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; + +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', +}); +// Get a specific metric for this guardrail +const invocationsMetric = guardrail.metricInvocations({ + statistic: 'Sum', + period: Duration.minutes(5), +}); + +// Create a CloudWatch alarm for high invocation latency +new cloudwatch.Alarm(this, 'HighLatencyAlarm', { + metric: guardrail.metricInvocationLatency(), + threshold: 1000, // 1 second + evaluationPeriods: 3, +}); + +// Get metrics for all guardrails +const allInvocationsMetric = bedrock.Guardrail.metricAllInvocations(); +``` + +### Importing Guardrails + +You can import existing guardrails using the `fromGuardrailAttributes` or `fromCfnGuardrail` methods. + +#### Import Configuration + +```ts fixture=default +declare const stack: Stack; +const cmk = new kms.Key(this, 'cmk', {}); +// Import an existing guardrail by ARN +const importedGuardrail = bedrock.Guardrail.fromGuardrailAttributes(stack, 'TestGuardrail', { + guardrailArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail/oygh3o8g7rtl', + guardrailVersion: '1', //optional + kmsKey: cmk, //optional +}); +``` + +```ts fixture=default +import * as bedrockl1 from 'aws-cdk-lib/aws-bedrock'; +// Import a guardrail created through the L1 CDK CfnGuardrail construct +const l1guardrail = new bedrockl1.CfnGuardrail(this, 'MyCfnGuardrail', { + blockedInputMessaging: 'blockedInputMessaging', + blockedOutputsMessaging: 'blockedOutputsMessaging', + name: 'namemycfnguardrails', + wordPolicyConfig: { + wordsConfig: [ + { + text: 'drugs', + }, + ], + }, +}); + +const importedGuardrail = bedrock.Guardrail.fromCfnGuardrail(l1guardrail); +``` + +### Guardrail Versioning + +Guardrails support versioning, allowing you to track changes and maintain multiple versions of your guardrail configurations. + +#### Version Configuration + +```ts fixture=default +const guardrail = new bedrock.Guardrail(this, 'bedrockGuardrails', { + guardrailName: 'my-BedrockGuardrails', +}); +// Create a new version of the guardrail +guardrail.createVersion('testversion'); +``` + ## Prompts Amazon Bedrock provides the ability to create and save prompts using Prompt management so that you can save time by applying the same prompt to different workflows. You can include variables in the prompt so that you can adjust the prompt for different use case. diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts index 607b4c4d8cdce..9db5ac7abba0a 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts @@ -16,6 +16,7 @@ import { AgentCollaborator } from './agent-collaborator'; import { AgentCollaboration } from './agent-collaboration'; import { PromptOverrideConfiguration } from './prompt-override'; import { AssetApiSchema, S3ApiSchema } from './api-schema'; +import { IGuardrail } from '../guardrails/guardrails'; import * as validation from './validation-helpers'; import { IBedrockInvokable } from '.././models'; import { Memory } from './memory'; @@ -178,7 +179,6 @@ export abstract class AgentBase extends Resource implements IAgent { /** * Properties for creating a CDK managed Bedrock Agent. * TODO: Knowledge bases configuration will be added in a future update - * TODO: Guardrails configuration will be added in a future update * TODO: Inference profile configuration will be added in a future update * */ @@ -241,7 +241,11 @@ export interface AgentProps { * @default - Only default action groups (UserInput and CodeInterpreter) are added */ readonly actionGroups?: AgentActionGroup[]; - + /** + * The guardrail that will be associated with the agent. + * @default - No guardrail is provided. + */ + readonly guardrail?: IGuardrail; /** * Overrides some prompt templates in different parts of an agent sequence configuration. * @@ -408,7 +412,10 @@ export class Agent extends AgentBase implements IAgent { * action groups associated with the ageny */ public readonly actionGroups: AgentActionGroup[] = []; - + /** + * The guardrail that will be associated with the agent. + */ + public guardrail?: IGuardrail; // ------------------------------------------------------ // CDK-only attributes // ------------------------------------------------------ @@ -519,6 +526,10 @@ export class Agent extends AgentBase implements IAgent { }); } + if (props.guardrail) { + this.addGuardrail(props.guardrail); + } + // Grant permissions for custom orchestration if provided if (this.customOrchestrationExecutor?.lambdaFunction) { this.customOrchestrationExecutor.lambdaFunction.grantInvoke(this.role); @@ -540,6 +551,7 @@ export class Agent extends AgentBase implements IAgent { customerEncryptionKeyArn: props.kmsKey?.keyArn, description: props.description, foundationModel: this.foundationModel.invokableArn, + guardrailConfiguration: Lazy.any({ produce: () => this.renderGuardrail() }), idleSessionTtlInSeconds: this.idleSessionTTL.toSeconds(), instruction: props.instruction, memoryConfiguration: props.memory?._render(), @@ -581,6 +593,19 @@ export class Agent extends AgentBase implements IAgent { // HELPER METHODS - addX() // ------------------------------------------------------ + /** + * Add guardrail to the agent. + */ + @MethodMetadata() + public addGuardrail(guardrail: IGuardrail) { + // Do some checks + validation.throwIfInvalid(this.validateGuardrail, guardrail); + // Add it to the construct + this.guardrail = guardrail; + // Handle permissions + guardrail.grantApply(this.role); + } + /** * Adds an action group to the agent and configures necessary permissions. * @@ -662,6 +687,20 @@ export class Agent extends AgentBase implements IAgent { // Lazy Renderers // ------------------------------------------------------ + /** + * Render the guardrail configuration. + * + * @internal This is an internal core function and should not be called directly. + */ + private renderGuardrail(): bedrock.CfnAgent.GuardrailConfigurationProperty | undefined { + return this.guardrail + ? { + guardrailIdentifier: this.guardrail.guardrailId, + guardrailVersion: this.guardrail.guardrailVersion, + } + : undefined; + } + /** * Render the action groups * @@ -715,6 +754,24 @@ export class Agent extends AgentBase implements IAgent { // ------------------------------------------------------ // Validators // ------------------------------------------------------ + /** + * Checks if the Guardrail is valid + * + * @param guardrail - The guardrail to validate + * @returns Array of validation error messages, empty if valid + */ + private validateGuardrail = (guardrail: IGuardrail) => { + let errors: string[] = []; + if (this.guardrail) { + errors.push( + `Cannot add Guardrail ${guardrail.guardrailId}. ` + + `Guardrail ${this.guardrail.guardrailId} has already been specified for this agent.`, + ); + } + errors.push(...validation.validateFieldPattern(guardrail.guardrailVersion, 'version', /^(([0-9]{1,8})|(DRAFT))$/)); + return errors; + }; + /** * Check if the action group is valid * diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrail-filters.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrail-filters.ts new file mode 100644 index 0000000000000..e595d82b583b2 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrail-filters.ts @@ -0,0 +1,821 @@ +import { ValidationRule } from 'aws-cdk-lib/core/lib/helpers-internal'; + +/** + * Guardrail action when a sensitive entity is detected. + */ +export enum GuardrailAction { + /** + * If sensitive information is detected in the prompt or response, the guardrail + * blocks all the content and returns a message that you configure. + */ + BLOCK = 'BLOCK', + /** + * If sensitive information is detected in the model response, the guardrail masks + * it with an identifier, the sensitive information is masked and replaced with + * identifier tags (for example: [NAME-1], [NAME-2], [EMAIL-1], etc.). + */ + ANONYMIZE = 'ANONYMIZE', + /** + * Do not take any action. + */ + NONE = 'NONE', +} + +/****************************************************************************** + * TIER CONFIG +*****************************************************************************/ +export enum TierConfig { + /** + * Provides established guardrails functionality supporting English, French, and Spanish languages. + */ + CLASSIC = 'CLASSIC', + /** + * Provides a more robust solution than the CLASSIC tier and has more comprehensive language support. This tier requires that your guardrail use cross-Region inference. + */ + STANDARD = 'STANDARD', +} + +/****************************************************************************** + * CONTENT FILTERS + *****************************************************************************/ +/** + * The strength of the content filter. As you increase the filter strength, + * the likelihood of filtering harmful content increases and the probability + * of seeing harmful content in your application reduces. + */ +export enum ContentFilterStrength { + /** + * No content filtering applied. + */ + NONE = 'NONE', + /** + * Low strength content filtering - minimal filtering of harmful content. + */ + LOW = 'LOW', + /** + * Medium strength content filtering - balanced filtering of harmful content. + */ + MEDIUM = 'MEDIUM', + /** + * High strength content filtering - aggressive filtering of harmful content. + */ + HIGH = 'HIGH', +} + +/** + * The type of modality that can be used in content filters. + */ +export enum ModalityType { + /** + * Text modality for content filters. + */ + TEXT = 'TEXT', + /** + * Image modality for content filters. + */ + IMAGE = 'IMAGE', +} + +/** + * The type of harmful category usable in a content filter. + */ +export enum ContentFilterType { + /** + * Describes input prompts and model responses that indicates sexual interest, activity, + * or arousal using direct or indirect references to body parts, physical traits, or sex. + */ + SEXUAL = 'SEXUAL', + /** + * Describes input prompts and model responses that includes glorification of or threats + * to inflict physical pain, hurt, or injury toward a person, group or thing. + */ + VIOLENCE = 'VIOLENCE', + /** + * Describes input prompts and model responses that discriminate, criticize, insult, + * denounce, or dehumanize a person or group on the basis of an identity (such as race, + * ethnicity, gender, religion, sexual orientation, ability, and national origin). + */ + HATE = 'HATE', + /** + * Describes input prompts and model responses that includes demeaning, humiliating, + * mocking, insulting, or belittling language. This type of language is also labeled + * as bullying. + */ + INSULTS = 'INSULTS', + /** + * Describes input prompts and model responses that seeks or provides information + * about engaging in misconduct activity, or harming, defrauding, or taking advantage + * of a person, group or institution. + */ + MISCONDUCT = 'MISCONDUCT', + /** + * Enable to detect and block user inputs attempting to override system instructions. + * To avoid misclassifying system prompts as a prompt attack and ensure that the filters + * are selectively applied to user inputs, use input tagging. + */ + PROMPT_ATTACK = 'PROMPT_ATTACK', +} + +/** + * Interface to declare a content filter. + */ +export interface ContentFilter { + /** + * The type of harmful category that the content filter is applied to + */ + readonly type: ContentFilterType; + /** + * The strength of the content filter to apply to prompts / user input. + */ + readonly inputStrength: ContentFilterStrength; + /** + * The strength of the content filter to apply to model responses. + */ + readonly outputStrength: ContentFilterStrength; + /** + * The action to take when content is detected in the input. + * @default GuardrailAction.BLOCK + */ + readonly inputAction?: GuardrailAction; + /** + * Whether the content filter is enabled for input. + * @default true + */ + readonly inputEnabled?: boolean; + /** + * The action to take when content is detected in the output. + * @default GuardrailAction.BLOCK + */ + readonly outputAction?: GuardrailAction; + /** + * Whether the content filter is enabled for output. + * @default true + */ + readonly outputEnabled?: boolean; + /** + * The input modalities to apply the content filter to. + * @default undefined - Applies to text modality + */ + readonly inputModalities?: ModalityType[]; + /** + * The output modalities to apply the content filter to. + * @default undefined - Applies to text modality + */ + readonly outputModalities?: ModalityType[]; +} + +/****************************************************************************** + * TOPIC FILTERS + *****************************************************************************/ +/** + * Interface for creating a custom Topic + */ +export interface CustomTopicProps { + /** + * The name of the topic to deny. + */ + readonly name: string; + /** + * Provide a clear definition to detect and block user inputs and FM responses + * that fall into this topic. Avoid starting with "don't". + * @example `Investment advice refers to inquiries, guidance, or recommendations + * regarding the management or allocation of funds or assets with the goal of + * generating returns or achieving specific financial objectives.` + */ + readonly definition: string; + /** + * Representative phrases that refer to the topic. These phrases can represent + * a user input or a model response. Add between 1 and 100 phrases, up to 100 characters + * each. + * @example "Where should I invest my money?" + */ + readonly examples: string[]; + /** + * The action to take when a topic is detected in the input. + * @default GuardrailAction.BLOCK + */ + readonly inputAction?: GuardrailAction; + /** + * Whether the topic filter is enabled for input. + * @default true + */ + readonly inputEnabled?: boolean; + /** + * The action to take when a topic is detected in the output. + * @default GuardrailAction.BLOCK + */ + readonly outputAction?: GuardrailAction; + /** + * Whether the topic filter is enabled for output. + * @default true + */ + readonly outputEnabled?: boolean; +} + +/** + * Represents predefined topics that can be used to filter content. + */ +export class Topic { + /** + * Filter for financial advice and investment recommendations. + */ + public static readonly FINANCIAL_ADVICE = new Topic({ + name: 'Financial_Advice', + definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + inputAction: GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: GuardrailAction.BLOCK, + outputEnabled: true, + }); + + /** + * Filter for inappropriate or explicit content. + */ + public static readonly INAPPROPRIATE_CONTENT = new Topic({ + name: 'Inappropriate_Content', + definition: + 'Conversations that express views, opinions, or endorsements related to political parties, political ideologies, elections, or political figures.', + examples: [ + "What's your stance on the current government policies?", + 'Do you support party X or Y in the upcoming election?', + 'Can you tell me which political party is better?', + 'What do you think about the new policy introduced by the president?', + 'Should I vote for this candidate?', + ], + inputAction: GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: GuardrailAction.BLOCK, + outputEnabled: true, + }); + + /** + * Filter for legal advice and recommendations. + */ + public static readonly LEGAL_ADVICE = new Topic({ + name: 'Legal_Advice', + definition: + 'Providing recommendations, diagnosis, treatment options, or guidance on medical conditions, symptoms, medications, or health-related issues.', + examples: [ + 'What should I do to treat a persistent cough?', + 'Can you recommend some medication for my headache?', + 'What are the symptoms of a specific medical condition?', + 'Is this drug effective for treating my illness?', + "Do I need to see a doctor for this pain I'm experiencing?", + ], + inputAction: GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: GuardrailAction.BLOCK, + outputEnabled: true, + }); + + /** + * Filter for medical advice and health recommendations. + */ + public static readonly MEDICAL_ADVICE = new Topic({ + name: 'Medical_Advice', + definition: + 'Any discussions or references that include hate speech, discriminatory remarks, sexual content, or explicit language.', + examples: [ + 'Why are people from X country so [discriminatory remark]?', + 'Can you tell me a dirty joke?', + '[Use of explicit language]', + 'This service is as bad as [hate speech].', + 'Do you have any adult content or products?', + ], + inputAction: GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: GuardrailAction.BLOCK, + outputEnabled: true, + }); + + /** + * Filter for political advice and recommendations. + */ + public static readonly POLITICAL_ADVICE = new Topic({ + name: 'Political_Advice', + definition: + 'Offering guidance or suggestions on legal matters, legal actions, interpretation of laws, or legal rights and responsibilities.', + examples: [ + 'Can I sue someone for this?', + 'What are my legal rights in this situation?', + 'Is this action against the law?', + 'What should I do to file a legal complaint?', + 'Can you explain this law to me?', + ], + inputAction: GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: GuardrailAction.BLOCK, + outputEnabled: true, + }); + + /** + * Create a custom topic filter. + * @param props Properties for the custom topic filter + */ + public static custom(props: CustomTopicProps): Topic { + return new Topic(props); + } + + /** + * The name of the topic to deny. + */ + readonly name: string; + /** + * Definition of the topic. + */ + readonly definition: string; + /** + * Representative phrases that refer to the topic. + */ + readonly examples?: string[]; + /** + * The action to take when a topic is detected in the input. + * @default GuardrailAction.BLOCK + */ + readonly inputAction?: GuardrailAction; + /** + * Whether the topic filter is enabled for input. + * @default true + */ + readonly inputEnabled?: boolean; + /** + * The action to take when a topic is detected in the output. + * @default GuardrailAction.BLOCK + */ + readonly outputAction?: GuardrailAction; + /** + * Whether the topic filter is enabled for output. + * @default true + */ + readonly outputEnabled?: boolean; + + protected constructor(props: CustomTopicProps) { + // Validate examples field constraints + const examplesValidationRules: ValidationRule[] = [ + { + condition: (p) => !p.examples || p.examples.length < 1, + message: () => 'examples field must contain at least 1 example', + }, + { + condition: (p) => p.examples && p.examples.length > 100, + message: () => 'examples field cannot contain more than 100 examples', + }, + ]; + + // Note: We can't use validateAllProps here since we don't have a Construct scope + // Instead, we'll validate directly + for (const rule of examplesValidationRules) { + if (rule.condition(props)) { + throw new Error(rule.message(props)); + } + } + + this.name = props.name; + this.definition = props.definition; + this.examples = props.examples; + this.inputAction = props.inputAction; + this.inputEnabled = props.inputEnabled; + this.outputAction = props.outputAction; + this.outputEnabled = props.outputEnabled; + } +} + +/****************************************************************************** + * WORD FILTERS + *****************************************************************************/ +/** + * Interface to define a Word Filter. + */ +export interface WordFilter { + /** + * The text to filter. + */ + readonly text: string; + /** + * The action to take when a word is detected in the input. + * @default GuardrailAction.BLOCK + */ + readonly inputAction?: GuardrailAction; + /** + * Whether the word filter is enabled for input. + * @default true + */ + readonly inputEnabled?: boolean; + /** + * The action to take when a word is detected in the output. + * @default GuardrailAction.BLOCK + */ + readonly outputAction?: GuardrailAction; + /** + * Whether the word filter is enabled for output. + * @default true + */ + readonly outputEnabled?: boolean; +} + +/** + * Managed word list filter types supported by Amazon Bedrock. + */ +export enum ManagedWordFilterType { + /** + * Filter for profanity and explicit language. + */ + PROFANITY = 'PROFANITY', +} + +/** + * Interface for managed word list filters. + */ +export interface ManagedWordFilter { + /** + * The type of managed word filter. + * @default ManagedWordFilterType.PROFANITY + */ + readonly type?: ManagedWordFilterType; + /** + * The action to take when a managed word is detected in the input. + * @default GuardrailAction.BLOCK + */ + readonly inputAction?: GuardrailAction; + /** + * Whether the managed word filter is enabled for input. + * @default true + */ + readonly inputEnabled?: boolean; + /** + * The action to take when a managed word is detected in the output. + * @default GuardrailAction.BLOCK + */ + readonly outputAction?: GuardrailAction; + /** + * Whether the managed word filter is enabled for output. + * @default true + */ + readonly outputEnabled?: boolean; +} + +/****************************************************************************** + * SENSITIVE INFORMATION FILTERS - PII + *****************************************************************************/ +/** + * Abstract base class for all PII types. + */ +export abstract class PIIType { + /** + * The string value of the PII type. + */ + public readonly value: string; + + /** + * The string value of the PII type. + */ + protected constructor(value: string) { + this.value = value; + } + + /** + * Returns the string representation of the PII type. + * @returns The string value of the PII type. + */ + toString(): string { return this.value; } +} + +/** + * Types of PII that are general, and not domain-specific. + */ +export class GeneralPIIType extends PIIType { + /** + * A physical address, such as "100 Main Street, Anytown, USA" or "Suite #12, + * Building 123". An address can include information such as the street, building, + * location, city, state, country, county, zip code, precinct, and neighborhood. + */ + public static readonly ADDRESS = new GeneralPIIType('ADDRESS'); + /** + * An individual's age, including the quantity and unit of time. + */ + public static readonly AGE = new GeneralPIIType('AGE'); + /** + * The number assigned to a driver's license, which is an official document + * permitting an individual to operate one or more motorized vehicles on a + * public road. A driver's license number consists of alphanumeric characters. + */ + public static readonly DRIVER_ID = new GeneralPIIType('DRIVER_ID'); + /** + * An email address, such as marymajor@email.com. + */ + public static readonly EMAIL = new GeneralPIIType('EMAIL'); + /** + * A license plate for a vehicle is issued by the state or country where the + * vehicle is registered. The format for passenger vehicles is typically five + * to eight digits, consisting of upper-case letters and numbers. The format + * varies depending on the location of the issuing state or country. + */ + public static readonly LICENSE_PLATE = new GeneralPIIType('LICENSE_PLATE'); + /** + * An individual's name. This entity type does not include titles, such as Dr., + * Mr., Mrs., or Miss. + */ + public static readonly NAME = new GeneralPIIType('NAME'); + /** + * An alphanumeric string that is used as a password, such as "*very20special#pass*". + */ + public static readonly PASSWORD = new GeneralPIIType('PASSWORD'); + /** + * A phone number. This entity type also includes fax and pager numbers. + */ + public static readonly PHONE = new GeneralPIIType('PHONE'); + /** + * A user name that identifies an account, such as a login name, screen name, + * nick name, or handle. + */ + public static readonly USERNAME = new GeneralPIIType('USERNAME'); + /** + * A Vehicle Identification Number (VIN) uniquely identifies a vehicle. VIN + * content and format are defined in the ISO 3779 specification. Each country + * has specific codes and formats for VINs. + */ + public static readonly VEHICLE_IDENTIFICATION_NUMBER = new GeneralPIIType('VEHICLE_IDENTIFICATION_NUMBER'); + + private constructor(value: string) { super(value); } +} + +/** + * Types of PII in the domain of Finance. + */ +export class FinancePIIType extends PIIType { + /** + * A three-digit card verification code (CVV) that is present on VISA, MasterCard, + * and Discover credit and debit cards. For American Express credit or debit cards, + * the CVV is a four-digit numeric code. + */ + public static readonly CREDIT_DEBIT_CARD_CVV = new FinancePIIType('CREDIT_DEBIT_CARD_CVV'); + /** + * The expiration date for a credit or debit card. This number is usually four digits + * long and is often formatted as month/year or MM/YY. Guardrails recognizes expiration + * dates such as 01/21, 01/2021, and Jan 2021. + */ + public static readonly CREDIT_DEBIT_CARD_EXPIRY = new FinancePIIType('CREDIT_DEBIT_CARD_EXPIRY'); + /** + * The number for a credit or debit card. These numbers can vary from 13 to 16 digits + * in length. + */ + public static readonly CREDIT_DEBIT_CARD_NUMBER = new FinancePIIType('CREDIT_DEBIT_CARD_NUMBER'); + /** + * A four-digit personal identification number (PIN) with which you can access your + * bank account. + */ + public static readonly PIN = new FinancePIIType('PIN'); + /** + * A SWIFT code is a standard format of Bank Identifier Code (BIC) used to specify a + * particular bank or branch. Banks use these codes for money transfers such as + * international wire transfers. SWIFT codes consist of eight or 11 characters. + */ + public static readonly SWIFT_CODE = new FinancePIIType('SWIFT_CODE'); + /** + * An International Bank Account Number (IBAN). It has specific formats in each country. + */ + public static readonly INTERNATIONAL_BANK_ACCOUNT_NUMBER = new FinancePIIType('INTERNATIONAL_BANK_ACCOUNT_NUMBER'); + + private constructor(value: string) { super(value); } +} + +/** + * Types of PII in the domain of IT (Information Technology). + */ +export class InformationTechnologyPIIType extends PIIType { + /** + * A web address, such as www.example.com. + */ + public static readonly URL = new InformationTechnologyPIIType('URL'); + /** + * An IPv4 address, such as 198.51.100.0. + */ + public static readonly IP_ADDRESS = new InformationTechnologyPIIType('IP_ADDRESS'); + /** + * A media access control (MAC) address assigned to a network interface. + */ + public static readonly MAC_ADDRESS = new InformationTechnologyPIIType('MAC_ADDRESS'); + /** + * A unique identifier that's associated with a secret access key. You use + * the access key ID and secret access key to sign programmatic AWS requests + * cryptographically. + */ + public static readonly AWS_ACCESS_KEY = new InformationTechnologyPIIType('AWS_ACCESS_KEY'); + /** + * A unique identifier that's associated with a secret access key. You use + * the access key ID and secret access key to sign programmatic AWS requests + * cryptographically. + */ + public static readonly AWS_SECRET_KEY = new InformationTechnologyPIIType('AWS_SECRET_KEY'); + + private constructor(value: string) { super(value); } +} + +/** + * Types of PII specific to the USA. + */ +export class USASpecificPIIType extends PIIType { + /** + * A US bank account number, which is typically 10 to 12 digits long. + */ + public static readonly US_BANK_ACCOUNT_NUMBER = new USASpecificPIIType('US_BANK_ACCOUNT_NUMBER'); + /** + * A US bank account routing number. These are typically nine digits long. + */ + public static readonly US_BANK_ROUTING_NUMBER = new USASpecificPIIType('US_BANK_ROUTING_NUMBER'); + /** + * A US Individual Taxpayer Identification Number (ITIN) is a nine-digit number + * that starts with a "9" and contain a "7" or "8" as the fourth digit. + */ + public static readonly US_INDIVIDUAL_TAX_IDENTIFICATION_NUMBER = new USASpecificPIIType('US_INDIVIDUAL_TAX_IDENTIFICATION_NUMBER'); + /** + * A US passport number. Passport numbers range from six to nine alphanumeric characters. + */ + public static readonly US_PASSPORT_NUMBER = new USASpecificPIIType('US_PASSPORT_NUMBER'); + /** + * A US Social Security Number (SSN) is a nine-digit number that is issued to US citizens, + * permanent residents, and temporary working residents. + */ + public static readonly US_SOCIAL_SECURITY_NUMBER = new USASpecificPIIType('US_SOCIAL_SECURITY_NUMBER'); + + private constructor(value: string) { super(value); } +} + +/** + * Types of PII specific to Canada. + */ +export class CanadaSpecificPIIType extends PIIType { + /** + * A Canadian Health Service Number is a 10-digit unique identifier, + * required for individuals to access healthcare benefits. + */ + public static readonly CA_HEALTH_NUMBER = new CanadaSpecificPIIType('CA_HEALTH_NUMBER'); + /** + * A Canadian Social Insurance Number (SIN) is a nine-digit unique identifier, + * required for individuals to access government programs and benefits. + */ + public static readonly CA_SOCIAL_INSURANCE_NUMBER = new CanadaSpecificPIIType('CA_SOCIAL_INSURANCE_NUMBER'); + + private constructor(value: string) { super(value); } +} + +/** + * Types of PII specific to the United Kingdom (UK). + */ +export class UKSpecificPIIType extends PIIType { + /** + * A UK National Health Service Number is a 10-17 digit number, such as 485 777 3456. + */ + public static readonly UK_NATIONAL_HEALTH_SERVICE_NUMBER = new UKSpecificPIIType('UK_NATIONAL_HEALTH_SERVICE_NUMBER'); + /** + * A UK National Insurance Number (NINO) provides individuals with access to National + * Insurance (social security) benefits. It is also used for some purposes in the UK + * tax system. + */ + public static readonly UK_NATIONAL_INSURANCE_NUMBER = new UKSpecificPIIType('UK_NATIONAL_INSURANCE_NUMBER'); + /** + * A UK Unique Taxpayer Reference (UTR) is a 10-digit number that identifies a + * taxpayer or a business. + */ + public static readonly UK_UNIQUE_TAXPAYER_REFERENCE_NUMBER = new UKSpecificPIIType('UK_UNIQUE_TAXPAYER_REFERENCE_NUMBER'); + + private constructor(value: string) { super(value); } +} + +/** + * Interface to define a PII Filter. + */ +export interface PIIFilter { + /** + * The type of PII to filter. + */ + readonly type: PIIType; + /** + * The action to take when PII is detected. + */ + readonly action: GuardrailAction; + /** + * The action to take when PII is detected in the input. + * @default GuardrailAction.BLOCK + */ + readonly inputAction?: GuardrailAction; + /** + * Whether the PII filter is enabled for input. + * @default true + */ + readonly inputEnabled?: boolean; + /** + * The action to take when PII is detected in the output. + * @default GuardrailAction.BLOCK + */ + readonly outputAction?: GuardrailAction; + /** + * Whether the PII filter is enabled for output. + * @default true + */ + readonly outputEnabled?: boolean; +} + +/****************************************************************************** + * SENSITIVE INFORMATION FILTERS - REGEX + *****************************************************************************/ +/** + * A Regular expression (regex) filter for sensitive information. + * + */ +export interface RegexFilter { + /** + * The name of the regex filter. + */ + readonly name: string; + /** + * The description of the regex filter. + * @default - No description + */ + readonly description?: string; + /** + * The action to take when a regex match is detected. + */ + readonly action: GuardrailAction; + /** + * The action to take when a regex match is detected in the input. + * @default GuardrailAction.BLOCK + */ + readonly inputAction?: GuardrailAction; + /** + * Whether the regex filter is enabled for input. + * @default true + */ + readonly inputEnabled?: boolean; + /** + * The action to take when a regex match is detected in the output. + * @default GuardrailAction.BLOCK + */ + readonly outputAction?: GuardrailAction; + /** + * Whether the regex filter is enabled for output. + * @default true + */ + readonly outputEnabled?: boolean; + /** + * The regular expression pattern to match. + */ + readonly pattern: string; +} + +/****************************************************************************** + * CONTEXTUAL GROUNDING FILTERS + *****************************************************************************/ +/** + * The type of contextual grounding filter. + */ +export enum ContextualGroundingFilterType { + /** + * Grounding score represents the confidence that the model response is factually + * correct and grounded in the source. If the model response has a lower score than + * the defined threshold, the response will be blocked and the configured blocked + * message will be returned to the user. A higher threshold level blocks more responses. + */ + GROUNDING = 'GROUNDING', + /** + * Relevance score represents the confidence that the model response is relevant + * to the user's query. If the model response has a lower score than the defined + * threshold, the response will be blocked and the configured blocked message will + * be returned to the user. A higher threshold level blocks more responses. + */ + RELEVANCE = 'RELEVANCE', +} + +/** + * Interface to define a Contextual Grounding Filter. + */ +export interface ContextualGroundingFilter { + /** + * The type of contextual grounding filter. + */ + readonly type: ContextualGroundingFilterType; + /** + * The action to take when contextual grounding is detected. + * @default GuardrailAction.BLOCK + */ + readonly action?: GuardrailAction; + /** + * Whether the contextual grounding filter is enabled. + * @default true + */ + readonly enabled?: boolean; + /** + * The threshold for the contextual grounding filter. + * - `0` (blocks nothing) + * - `0.99` (blocks almost everything) + */ + readonly threshold: number; +} diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrail-version.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrail-version.ts new file mode 100644 index 0000000000000..877717f8da7cf --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrail-version.ts @@ -0,0 +1,135 @@ +import { IResource, Resource } from 'aws-cdk-lib'; +import { CfnGuardrailVersion } from 'aws-cdk-lib/aws-bedrock'; +import { md5hash } from 'aws-cdk-lib/core/lib/helpers-internal'; +import { Construct } from 'constructs'; +// Internal Libs +import { Guardrail, IGuardrail } from './guardrails'; + +/****************************************************************************** + * COMMON + *****************************************************************************/ +/** + * Represents a Guardrail Version, either created with CDK or imported. + */ +export interface IGuardrailVersion extends IResource { + /** + * The Guardrail to which this version belongs. + */ + readonly guardrail: IGuardrail; + + /** + * The ID of the guardrail version. + * @example "1" + */ + readonly guardrailVersion: string; +} + +/****************************************************************************** + * ABSTRACT BASE CLASS + *****************************************************************************/ + +/** + * Abstract base class for a Guardrail Version. + * Contains methods and attributes valid for Guardrail Versions either created + * with CDK or imported. + */ +export abstract class GuardrailVersionBase extends Resource implements IGuardrailVersion { + public abstract readonly guardrail: IGuardrail; + public abstract readonly guardrailVersion: string; + constructor(scope: Construct, id: string) { + super(scope, id); + } +} + +/****************************************************************************** + * PROPS FOR NEW CONSTRUCT + *****************************************************************************/ +/** + * Properties for creating a CDK-Managed Guardrail Version. + */ + +/** + * Properties for creating a Guardrail Version. + */ +export interface GuardrailVersionProps { + /** + * The guardrail to create a version for. + */ + readonly guardrail: IGuardrail; + /** + * The description of the guardrail version. + * + * @example "This is a description of the guardrail version." + * @default - No description is provided. + */ + readonly description?: string; +} + +/****************************************************************************** + * ATTRS FOR IMPORTED CONSTRUCT + *****************************************************************************/ +/** + * Attributes needed to create an import + */ +export interface GuardrailVersionAttributes { + /** + * The ARN of the guardrail. + */ + readonly guardrailArn: string; + /** + * The ID of the guardrail version. + * @example "1" + */ + readonly guardrailVersion: string; +} +/****************************************************************************** + * NEW CONSTRUCT DEFINITION + *****************************************************************************/ +/** + * Class to create a Guardrail Version with CDK. + * @cloudformationResource AWS::Bedrock::GuardrailVersion + */ +export class GuardrailVersion extends GuardrailVersionBase { + /** + * Import a Guardrail Version from its attributes. + */ + public static fromGuardrailVersionAttributes( + scope: Construct, + id: string, + attrs: GuardrailVersionAttributes, + ): IGuardrailVersion { + class Import extends GuardrailVersionBase { + public readonly guardrail = Guardrail.fromGuardrailAttributes(scope, `Guardrail-${id}`, { + guardrailArn: attrs.guardrailArn, + guardrailVersion: attrs.guardrailVersion, + }); + public readonly guardrailVersion = attrs.guardrailVersion; + } + return new Import(scope, id); + } + + public readonly guardrail: IGuardrail; + public readonly guardrailVersion: string; + /** + * The underlying CfnGuardrailVersion resource. + */ + private readonly _resource: CfnGuardrailVersion; + + /** + * + */ + constructor(scope: Construct, id: string, props: GuardrailVersionProps) { + super(scope, id); + this.guardrail = props.guardrail; + + // Compute hash from guardrail, to recreate the resource when guardrail has changed + const hash = md5hash(props.guardrail.lastUpdated ?? 'Default'); + + this._resource = new CfnGuardrailVersion(this, `GuardrailVersion-${hash.slice(0, 16)}`, { + guardrailIdentifier: this.guardrail.guardrailId, + description: props.description, + }); + + this.guardrailVersion = this._resource.attrVersion; + } +} diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrails.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrails.ts new file mode 100644 index 0000000000000..bb0daca7fa9bf --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/guardrails/guardrails.ts @@ -0,0 +1,1643 @@ +import * as fs from 'fs'; +import { Arn, ArnFormat, IResolvable, IResource, Lazy, Resource, Token, ValidationError } from 'aws-cdk-lib'; +import * as bedrock from 'aws-cdk-lib/aws-bedrock'; +import { Metric, MetricOptions, MetricProps } from 'aws-cdk-lib/aws-cloudwatch'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import { IKey, Key } from 'aws-cdk-lib/aws-kms'; +import { md5hash } from 'aws-cdk-lib/core/lib/helpers-internal'; +import { Construct } from 'constructs'; +// Internal Libs +import * as filters from './guardrail-filters'; +import { GuardrailVersion } from './guardrail-version'; + +/****************************************************************************** + * COMMON + *****************************************************************************/ +/** + * GuardrailCrossRegionConfigProperty + */ +export interface GuardrailCrossRegionConfigProperty { + /** + * The arn of thesystem-defined guardrail profile that you're using with your guardrail. + * Guardrail profiles define the destination AWS Regions where guardrail inference requests can be automatically routed. + * Using guardrail profiles helps maintain guardrail performance and reliability when demand increases. + * @default - No cross-region configuration + */ + readonly guardrailProfileArn: string; +} + +/** + * Represents a Guardrail, either created with CDK or imported. + */ +export interface IGuardrail extends IResource { + /** + * The ARN of the guardrail. + * @attribute + */ + readonly guardrailArn: string; + /** + * The ID of the guardrail. + * @attribute + */ + readonly guardrailId: string; + /** + * Optional KMS encryption key associated with this guardrail + */ + readonly kmsKey?: IKey; + /** + * When this guardrail was last updated. + */ + readonly lastUpdated?: string; + /** + * The version of the guardrail. If no explicit version is created, + * this will default to "DRAFT" + * @attribute + */ + readonly guardrailVersion: string; + + /** + * Grant the given principal identity permissions to perform actions on this guardrail. + */ + grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant; + /** + * Grant the given identity permissions to apply the guardrail. + */ + grantApply(grantee: iam.IGrantable): iam.Grant; + + /** + * Return the given named metric for this guardrail. + */ + metric(metricName: string, props?: MetricOptions): Metric; + + /** + * Return the invocations metric for this guardrail. + */ + metricInvocations(props?: MetricOptions): Metric; + + /** + * Return the invocation latency metric for this guardrail. + */ + metricInvocationLatency(props?: MetricOptions): Metric; + + /** + * Return the invocation client errors metric for this guardrail. + */ + metricInvocationClientErrors(props?: MetricOptions): Metric; + + /** + * Return the invocation server errors metric for this guardrail. + */ + metricInvocationServerErrors(props?: MetricOptions): Metric; + + /** + * Return the invocation throttles metric for this guardrail. + */ + metricInvocationThrottles(props?: MetricOptions): Metric; + + /** + * Return the text unit count metric for this guardrail. + */ + metricTextUnitCount(props?: MetricOptions): Metric; + + /** + * Return the invocations intervened metric for this guardrail. + */ + metricInvocationsIntervened(props?: MetricOptions): Metric; +} + +/** + * Abstract base class for a Guardrail. + * Contains methods and attributes valid for Guardrails either created with CDK or imported. + */ +export abstract class GuardrailBase extends Resource implements IGuardrail { + /** + * Return the given named metric for all guardrails. + * + * By default, the metric will be calculated as a sum over a period of 5 minutes. + * You can customize this by using the `statistic` and `period` properties. + */ + public static metricAll(metricName: string, props?: MetricOptions): Metric { + return new Metric({ + namespace: 'AWS/Bedrock/Guardrails', + dimensionsMap: { Operation: 'ApplyGuardrail' }, + metricName, + ...props, + }); + } + + /** + * Return the invocations metric for all guardrails. + */ + public static metricAllInvocations(props?: MetricOptions): Metric { + return this.metricAll('Invocations', props); + } + + /** + * Return the text unit count metric for all guardrails. + */ + public static metricAllTextUnitCount(props?: MetricOptions): Metric { + return this.metricAll('TextUnitCount', props); + } + + /** + * Return the invocations intervened metric for all guardrails. + */ + public static metricAllInvocationsIntervened(props?: MetricOptions): Metric { + return this.metricAll('InvocationsIntervened', props); + } + + /** + * Return the invocation latency metric for all guardrails. + */ + public static metricAllInvocationLatency(props?: MetricOptions): Metric { + return this.metricAll('InvocationLatency', props); + } + + private _version: string = 'DRAFT'; + + /** + * The ARN of the guardrail. + * @attribute + */ + public abstract readonly guardrailArn: string; + + /** + * The ID of the guardrail. + * @attribute + */ + public abstract readonly guardrailId: string; + + /** + * Optional KMS encryption key associated with this guardrail + */ + public abstract readonly kmsKey?: IKey; + + /** + * When this guardrail was last updated. + */ + public abstract readonly lastUpdated?: string; + + /** + * The version of the guardrail. + * @attribute + */ + public get guardrailVersion(): string { + return this._version; + } + + protected updateVersion(version: string) { + this._version = version; + } + + /** + * Grant the given principal identity permissions to perform actions on this guardrail. + */ + public grant(grantee: iam.IGrantable, ...actions: string[]) { + return iam.Grant.addToPrincipal({ + grantee, + actions, + resourceArns: [this.guardrailArn], + scope: this, + }); + } + + /** + * Grant the given identity permissions to apply the guardrail. + */ + public grantApply(grantee: iam.IGrantable): iam.Grant { + const baseGrant = this.grant(grantee, 'bedrock:ApplyGuardrail'); + + if (this.kmsKey) { + // If KMS key exists, create encryption grant and combine with base grant + const kmsGrant = this.kmsKey.grantEncryptDecrypt(grantee); + return kmsGrant.combine(baseGrant); + } else { + // If no KMS key exists, return only the base grant + return baseGrant; + } + } + + /** + * Return the given named metric for this guardrail. + * + * By default, the metric will be calculated as a sum over a period of 5 minutes. + * You can customize this by using the `statistic` and `period` properties. + */ + public metric(metricName: string, props?: MetricOptions): Metric { + const metricProps: MetricProps = { + namespace: 'AWS/Bedrock/Guardrails', + metricName, + dimensionsMap: { GuardrailArn: this.guardrailArn, GuardrailVersion: this.guardrailVersion }, + ...props, + }; + return this.configureMetric(metricProps); + } + + /** + * Return the invocations metric for this guardrail. + */ + public metricInvocations(props?: MetricOptions): Metric { + return this.metric('Invocations', props); + } + + /** + * Return the invocation latency metric for this guardrail. + */ + public metricInvocationLatency(props?: MetricOptions): Metric { + return this.metric('InvocationLatency', props); + } + + /** + * Return the invocation client errors metric for this guardrail. + */ + public metricInvocationClientErrors(props?: MetricOptions): Metric { + return this.metric('InvocationClientErrors', props); + } + + /** + * Return the invocation server errors metric for this guardrail. + */ + public metricInvocationServerErrors(props?: MetricOptions): Metric { + return this.metric('InvocationServerErrors', props); + } + + /** + * Return the invocation throttles metric for this guardrail. + */ + public metricInvocationThrottles(props?: MetricOptions): Metric { + return this.metric('InvocationThrottles', props); + } + + /** + * Return the text unit count metric for this guardrail. + */ + public metricTextUnitCount(props?: MetricOptions): Metric { + return this.metric('TextUnitCount', props); + } + + /** + * Return the invocations intervened metric for this guardrail. + */ + public metricInvocationsIntervened(props?: MetricOptions): Metric { + return this.metric('InvocationsIntervened', props); + } + + private configureMetric(props: MetricProps) { + return new Metric({ + ...props, + region: props?.region ?? this.stack.region, + account: props?.account ?? this.stack.account, + }); + } +} + +/****************************************************************************** + * PROPS FOR NEW CONSTRUCT + *****************************************************************************/ +/** + * Properties for creating a Guardrail. + */ +export interface GuardrailProps { + /** + * The name of the guardrail. + * This will be used as the physical name of the guardrail. + */ + readonly guardrailName: string; + /** + * The description of the guardrail. + * @default - No description + */ + readonly description?: string; + /** + * The message to return when the guardrail blocks a prompt. + * Must be between 1 and 500 characters. + * @default "Sorry, your query violates our usage policy." + */ + readonly blockedInputMessaging?: string; + /** + * The message to return when the guardrail blocks a model response. + * Must be between 1 and 500 characters. + * @default "Sorry, I am unable to answer your question because of our usage policy." + */ + readonly blockedOutputsMessaging?: string; + /** + * A custom KMS key to use for encrypting data. + * @default - Data is encrypted by default with a key that AWS owns and manages for you + */ + readonly kmsKey?: IKey; + /** + * The content filters to apply to the guardrail. + * @default [] + */ + readonly contentFilters?: filters.ContentFilter[]; + /** + * The tier configuration to apply to the guardrail. + * @default filters.TierConfig.CLASSIC + */ + readonly contentFiltersTierConfig?: filters.TierConfig; + /** + * A list of policies related to topics that the guardrail should deny. + * @default [] + */ + readonly deniedTopics?: filters.Topic[]; + /** + * The tier configuration to apply to the guardrail. + * @default filters.TierConfig.CLASSIC + */ + readonly topicsTierConfig?: filters.TierConfig; + /** + * The word filters to apply to the guardrail. + * @default [] + */ + readonly wordFilters?: filters.WordFilter[]; + /** + * The managed word filters to apply to the guardrail. + * @default [] + */ + readonly managedWordListFilters?: filters.ManagedWordFilter[]; + /** + * The PII filters to apply to the guardrail. + * @default [] + */ + readonly piiFilters?: filters.PIIFilter[]; + /** + * The regular expression (regex) filters to apply to the guardrail. + * @default [] + */ + readonly regexFilters?: filters.RegexFilter[]; + /** + * The contextual grounding filters to apply to the guardrail. + * @default [] + */ + readonly contextualGroundingFilters?: filters.ContextualGroundingFilter[]; + /** + * The cross-region configuration for the guardrail. + * This is optional and when provided, it should be of type GuardrailCrossRegionConfigProperty. + * @default - No cross-region configuration + */ + readonly crossRegionConfig?: GuardrailCrossRegionConfigProperty; +} + +/****************************************************************************** + * ATTRS FOR IMPORTED CONSTRUCT + *****************************************************************************/ +export interface GuardrailAttributes { + /** + * The ARN of the guardrail. At least one of guardrailArn or guardrailId must be + * defined in order to initialize a guardrail ref. + */ + readonly guardrailArn: string; + /** + * The KMS key of the guardrail if custom encryption is configured. + * + * @default undefined - Means data is encrypted by default with a AWS-managed key + */ + readonly kmsKey?: IKey; + /** + * The version of the guardrail. + * + * @default "DRAFT" + */ + readonly guardrailVersion?: string; +} + +/****************************************************************************** + * NEW CONSTRUCT DEFINITION + *****************************************************************************/ +/** + * Class to create a Guardrail with CDK. + * @cloudformationResource AWS::Bedrock::Guardrail + */ +export class Guardrail extends GuardrailBase { + /** + * Import a guardrail given its attributes + */ + public static fromGuardrailAttributes(scope: Construct, id: string, attrs: GuardrailAttributes): IGuardrail { + class Import extends GuardrailBase { + public readonly guardrailArn = attrs.guardrailArn; + public readonly guardrailId = Arn.split(attrs.guardrailArn, ArnFormat.SLASH_RESOURCE_NAME).resourceName!; + public readonly kmsKey = attrs.kmsKey; + public readonly lastUpdated = undefined; + + constructor() { + super(scope, id); + this.updateVersion(attrs.guardrailVersion ?? 'DRAFT'); + } + } + + return new Import(); + } + + /** + * Import a low-level L1 Cfn Guardrail + */ + public static fromCfnGuardrail(cfnGuardrail: bedrock.CfnGuardrail): IGuardrail { + return new (class extends GuardrailBase { + public readonly guardrailArn = cfnGuardrail.attrGuardrailArn; + public readonly guardrailId = cfnGuardrail.attrGuardrailId; + public readonly kmsKey = cfnGuardrail.kmsKeyArn + ? Key.fromKeyArn(this, '@FromCfnGuardrailKey', cfnGuardrail.kmsKeyArn) + : undefined; + public readonly lastUpdated = cfnGuardrail.attrUpdatedAt; + + constructor() { + super(cfnGuardrail, '@FromCfnGuardrail'); + this.updateVersion(cfnGuardrail.attrVersion); + } + })(); + } + + /** + * The ARN of the guardrail. + * @attribute + */ + public readonly guardrailArn: string; + /** + * The ID of the guardrail. + * @attribute + */ + public readonly guardrailId: string; + /** + * The name of the guardrail. + */ + public readonly name: string; + /** + * The KMS key used to encrypt data. + * + * @default undefined - "Data is encrypted by default with a key that AWS owns and manages for you" + */ + public readonly kmsKey?: IKey; + /** + * The content filters applied by the guardrail. + */ + public readonly contentFilters: filters.ContentFilter[]; + /** + * The PII filters applied by the guardrail. + */ + public readonly piiFilters: filters.PIIFilter[]; + /** + * The regex filters applied by the guardrail. + */ + public readonly regexFilters: filters.RegexFilter[]; + /** + * The denied topic filters applied by the guardrail. + */ + public readonly deniedTopics: filters.Topic[]; + /** + * The contextual grounding filters applied by the guardrail. + */ + public readonly contextualGroundingFilters: filters.ContextualGroundingFilter[]; + /** + * The word filters applied by the guardrail. + */ + public readonly wordFilters: filters.WordFilter[]; + /** + * The managed word list filters applied by the guardrail. + */ + public readonly managedWordListFilters: filters.ManagedWordFilter[]; + /** + * When this guardrail was last updated + */ + public readonly lastUpdated?: string; + /** + * The computed hash of the guardrail properties. + */ + public readonly hash: string; + /** + * The L1 representation of the guardrail + */ + private readonly __resource: bedrock.CfnGuardrail; + + /** + * The tier that your guardrail uses for denied topic filters. + * @default filters.TierConfig.CLASSIC + */ + public readonly topicsTierConfig: filters.TierConfig; + + /** + * The tier that your guardrail uses for content filters. + * Consider using a tier that balances performance, accuracy, and compatibility with your existing generative AI workflows. + * @default filters.TierConfig.CLASSIC + */ + public readonly contentFiltersTierConfig: filters.TierConfig; + /** + * The cross-region configuration for the guardrail. + */ + public readonly crossRegionConfig?: GuardrailCrossRegionConfigProperty; + + /** + * The message to return when the guardrail blocks a prompt. + * @default "Sorry, your query violates our usage policy." + */ + public readonly blockedInputMessaging: string; + + /** + * The message to return when the guardrail blocks a model response. + * @default "Sorry, I am unable to answer your question because of our usage policy." + */ + public readonly blockedOutputsMessaging: string; + + constructor(scope: Construct, id: string, props: GuardrailProps) { + super(scope, id, { + physicalName: props.guardrailName, + }); + + // ------------------------------------------------------ + // Set properties or defaults + // ------------------------------------------------------ + this.name = this.physicalName; + this.contentFilters = props.contentFilters ?? []; + this.piiFilters = props.piiFilters ?? []; + this.regexFilters = props.regexFilters ?? []; + this.deniedTopics = props.deniedTopics ?? []; + this.contextualGroundingFilters = props.contextualGroundingFilters ?? []; + this.wordFilters = props.wordFilters ?? []; + this.managedWordListFilters = props.managedWordListFilters ?? []; + this.topicsTierConfig = props.topicsTierConfig ?? filters.TierConfig.CLASSIC; + this.contentFiltersTierConfig = props.contentFiltersTierConfig ?? filters.TierConfig.CLASSIC; + this.crossRegionConfig = props.crossRegionConfig; + this.blockedInputMessaging = props.blockedInputMessaging ?? 'Sorry, your query violates our usage policy.'; + this.blockedOutputsMessaging = props.blockedOutputsMessaging ?? 'Sorry, I am unable to answer your question because of our usage policy.'; + + // ------------------------------------------------------ + // Validate all filter arrays + // ------------------------------------------------------ + this.validateContentFilters(this.contentFilters); + this.validatePiiFilters(this.piiFilters); + this.validateRegexFilters(this.regexFilters); + this.validateDeniedTopics(this.deniedTopics); + this.validateContextualGroundingFilters(this.contextualGroundingFilters); + this.validateWordFilters(this.wordFilters); + this.validateManagedWordListFilters(this.managedWordListFilters); + + // ------------------------------------------------------ + // Validate messaging properties + // ------------------------------------------------------ + this.validateMessagingProperty(this.blockedInputMessaging, 'blockedInputMessaging'); + this.validateMessagingProperty(this.blockedOutputsMessaging, 'blockedOutputsMessaging'); + + // ------------------------------------------------------ + // Validate tier configuration requirements + // ------------------------------------------------------ + this.validateTierConfiguration(props); + + // ------------------------------------------------------ + // CFN Props - With Lazy support + // ------------------------------------------------------ + let cfnProps: bedrock.CfnGuardrailProps = { + name: props.guardrailName, + description: props.description, + kmsKeyArn: props.kmsKey?.keyArn, + blockedInputMessaging: this.blockedInputMessaging, + blockedOutputsMessaging: this.blockedOutputsMessaging, + // Lazy props + crossRegionConfig: this.generateCfnCrossRegionConfig(), + contentPolicyConfig: this.generateCfnContentPolicyConfig(), + contextualGroundingPolicyConfig: this.generateCfnContextualPolicyConfig(), + topicPolicyConfig: this.generateCfnTopicPolicy(), + wordPolicyConfig: this.generateCfnWordPolicyConfig(), + sensitiveInformationPolicyConfig: this.generateCfnSensitiveInformationPolicyConfig(), + }; + + // Hash calculation useful for versioning of the guardrail + this.hash = md5hash(JSON.stringify(cfnProps)); + + // ------------------------------------------------------ + // L1 Instantiation + // ------------------------------------------------------ + this.__resource = new bedrock.CfnGuardrail(this, 'MyGuardrail', cfnProps); + + this.guardrailId = this.__resource.attrGuardrailId; + this.guardrailArn = this.__resource.attrGuardrailArn; + this.updateVersion(this.__resource.attrVersion); + this.lastUpdated = this.__resource.attrUpdatedAt; + } + + // ------------------------------------------------------ + // METHODS + // ------------------------------------------------------ + /** + * Adds a content filter to the guardrail. + * @param filter The content filter to add. + */ + public addContentFilter(filter: filters.ContentFilter): void { + this.validateSingleContentFilter(filter); + this.contentFilters.push(filter); + } + + /** + * Adds a PII filter to the guardrail. + * @param filter The PII filter to add. + */ + public addPIIFilter(filter: filters.PIIFilter): void { + this.validateSinglePiiFilter(filter); + this.piiFilters.push(filter); + } + + /** + * Adds a regex filter to the guardrail. + * @param filter The regex filter to add. + */ + public addRegexFilter(filter: filters.RegexFilter): void { + this.validateSingleRegexFilter(filter); + this.regexFilters.push(filter); + } + + /** + * Adds a denied topic filter to the guardrail. + * @param filter The denied topic filter to add. + */ + public addDeniedTopicFilter(filter: filters.Topic): void { + this.validateSingleDeniedTopic(filter); + this.deniedTopics.push(filter); + } + + /** + * Adds a contextual grounding filter to the guardrail. + * @param filter The contextual grounding filter to add. + */ + public addContextualGroundingFilter(filter: filters.ContextualGroundingFilter): void { + this.validateSingleContextualGroundingFilter(filter); + this.contextualGroundingFilters.push(filter); + } + + /** + * Adds a word filter to the guardrail. + * @param filter The word filter to add. + */ + public addWordFilter(filter: filters.WordFilter): void { + this.validateSingleWordFilter(filter); + this.wordFilters.push(filter); + } + + /** + * Adds a word filter to the guardrail. + * @param filePath The location of the word filter file. + */ + public addWordFilterFromFile(filePath: string, + inputAction?: filters.GuardrailAction, + outputAction?: filters.GuardrailAction, + inputEnabled?: boolean, + outputEnabled?: boolean): void { + const fileContents = fs.readFileSync(filePath, 'utf8'); + const words = fileContents.trim().split(','); + for (const word of words) this.addWordFilter({ text: word, inputAction, outputAction, inputEnabled, outputEnabled }); + } + + /** + * Adds a managed word list filter to the guardrail. + * @param filter The managed word list filter to add. + */ + public addManagedWordListFilter(filter: filters.ManagedWordFilter): void { + this.validateSingleManagedWordListFilter(filter); + this.managedWordListFilters.push(filter); + } + + /** + * Create a version for the guardrail. + * @param description The description of the version. + * @returns The guardrail version. + */ + public createVersion(description?: string): string { + const cfnVersion = new GuardrailVersion(this, `GuardrailVersion-${this.hash.slice(0, 16)}`, { + description: description, + guardrail: this, + }); + + this.updateVersion(cfnVersion.guardrailVersion); + return this.guardrailVersion; + } + + // ------------------------------------------------------ + // CFN Generators + // ------------------------------------------------------ + /** + * Returns the content filters applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnContentPolicyConfig(): IResolvable { + return Lazy.any({ + produce: () => { + if (this.contentFilters.length > 0) { + const contentPolicyConfig: bedrock.CfnGuardrail.ContentPolicyConfigProperty = { + filtersConfig: this.contentFilters, + ...(this.contentFiltersTierConfig && { + contentFiltersTierConfig: { + tierName: this.contentFiltersTierConfig, + } as bedrock.CfnGuardrail.ContentFiltersTierConfigProperty, + }), + }; + + return contentPolicyConfig; + } else { + return undefined; + } + }, + }); + } + + /** + * Returns the topic filters applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnTopicPolicy(): IResolvable { + return Lazy.any({ + produce: () => { + if (this.deniedTopics.length > 0) { + const topicPolicyConfig: bedrock.CfnGuardrail.TopicPolicyConfigProperty = { + topicsConfig: this.deniedTopics.flatMap((topic: filters.Topic) => { + return { + definition: topic.definition, + name: topic.name, + examples: topic.examples, + type: 'DENY', + inputAction: topic.inputAction, + inputEnabled: topic.inputEnabled, + outputAction: topic.outputAction, + outputEnabled: topic.outputEnabled, + } as bedrock.CfnGuardrail.TopicConfigProperty; + }), + ...(this.topicsTierConfig && { + topicsTierConfig: { + tierName: this.topicsTierConfig, + } as bedrock.CfnGuardrail.TopicsTierConfigProperty, + }), + }; + + return topicPolicyConfig; + } else { + return undefined; + } + }, + }); + } + + /** + * Returns the contectual filters applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnContextualPolicyConfig(): IResolvable { + return Lazy.any({ + produce: () => { + if (this.contextualGroundingFilters.length > 0) { + return { + filtersConfig: this.contextualGroundingFilters.flatMap((filter: filters.ContextualGroundingFilter) => { + return { + type: filter.type, + threshold: filter.threshold, + action: filter.action, + enabled: filter.enabled, + } as bedrock.CfnGuardrail.ContextualGroundingFilterConfigProperty; + }), + }; + } else { + return undefined; + } + }, + }); + } + + /** + * Returns the word config applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnWordPolicyConfig(): IResolvable { + return Lazy.any({ + produce: () => { + if (this.wordFilters.length > 0 || this.managedWordListFilters.length > 0) { + return { + wordsConfig: this.generateCfnWordConfig(), + managedWordListsConfig: this.generateCfnManagedWordListsConfig(), + } as bedrock.CfnGuardrail.WordPolicyConfigProperty; + } else { + return undefined; + } + }, + }); + } + + /** + * Returns the word filters applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnWordConfig(): IResolvable { + return Lazy.any( + { + produce: () => { + return this.wordFilters.flatMap((word: filters.WordFilter) => { + return { + text: word.text, + inputAction: word.inputAction, + inputEnabled: word.inputEnabled, + outputAction: word.outputAction, + outputEnabled: word.outputEnabled, + } as bedrock.CfnGuardrail.WordConfigProperty; + }); + }, + }, + { omitEmptyArray: true }, + ); + } + + /** + * Returns the word filters applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnManagedWordListsConfig(): IResolvable { + return Lazy.any( + { + produce: () => { + return this.managedWordListFilters.flatMap((filter: filters.ManagedWordFilter) => { + return { + type: filter.type, + inputAction: filter.inputAction, + inputEnabled: filter.inputEnabled, + outputAction: filter.outputAction, + outputEnabled: filter.outputEnabled, + } as bedrock.CfnGuardrail.ManagedWordsConfigProperty; + }); + }, + }, + { omitEmptyArray: true }, + ); + } + + /** + * Returns the sensitive information config applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnSensitiveInformationPolicyConfig(): IResolvable { + return Lazy.any( + { + produce: () => { + if (this.regexFilters.length > 0 || this.piiFilters.length > 0) { + return { + regexesConfig: this.generateCfnRegexesConfig(), + piiEntitiesConfig: this.generateCfnPiiEntitiesConfig(), + }; + } else { + return undefined; + } + }, + }, + { omitEmptyArray: true }, + ); + } + + /** + * Returns the regex filters applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnRegexesConfig(): IResolvable { + return Lazy.any( + { + produce: () => { + return this.regexFilters.flatMap((regex: filters.RegexFilter) => { + return { + name: regex.name, + description: regex.description, + pattern: regex.pattern, + action: regex.action, + inputAction: regex.inputAction, + inputEnabled: regex.inputEnabled, + outputAction: regex.outputAction, + outputEnabled: regex.outputEnabled, + } as bedrock.CfnGuardrail.RegexConfigProperty; + }); + }, + }, + { omitEmptyArray: true }, + ); + } + + /** + * Returns the Pii filters applied to the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnPiiEntitiesConfig(): IResolvable { + return Lazy.any( + { + produce: () => { + return this.piiFilters.flatMap((filter: filters.PIIFilter) => { + return { + type: filter.type.value, + action: filter.action, + inputAction: filter.inputAction, + inputEnabled: filter.inputEnabled, + outputAction: filter.outputAction, + outputEnabled: filter.outputEnabled, + } as bedrock.CfnGuardrail.PiiEntityConfigProperty; + }); + }, + }, + { omitEmptyArray: true }, + ); + } + + /** + * Returns the cross-region configuration for the guardrail. This method defers the computation + * to synth time. + */ + private generateCfnCrossRegionConfig(): IResolvable { + return Lazy.any({ + produce: () => { + if (this.crossRegionConfig) { + return { + guardrailProfileArn: this.crossRegionConfig.guardrailProfileArn, + } as bedrock.CfnGuardrail.GuardrailCrossRegionConfigProperty; + } else { + return undefined; + } + }, + }); + } + + /** + * Validates a RegexFilter object and applies default values for optional properties. + * @param filter The regex filter to validate. + * @param index Optional index for error messages when validating arrays. + */ + private validateRegexFilter(filter: filters.RegexFilter, index?: number): void { + const prefix = index !== undefined ? `Invalid RegexFilter at index ${index}` : 'Invalid RegexFilter'; + + // Validate name: between 1 and 100 characters + if (filter.name !== undefined && !Token.isUnresolved(filter.name)) { + if (filter.name.length < 1) { + throw new ValidationError(`${prefix}: The field name is ${filter.name.length} characters long but must be at least 1 characters`, this); + } + if (filter.name.length > 100) { + throw new ValidationError(`${prefix}: The field name is ${filter.name.length} characters long but must be less than or equal to 100 characters`, this); + } + } + + // Validate description: between 1 and 1000 characters (if provided) + if (filter.description !== undefined && !Token.isUnresolved(filter.description)) { + if (filter.description.length < 1) { + throw new ValidationError(`${prefix}: The field description is ${filter.description.length} characters long but must be at least 1 characters`, this); + } + if (filter.description.length > 1000) { + throw new ValidationError(`${prefix}: The field description is ${filter.description.length} characters long but must be less than or equal to 1000 characters`, this); + } + } + + // Validate pattern: at least one character + if (filter.pattern !== undefined && !Token.isUnresolved(filter.pattern)) { + if (filter.pattern.length < 1) { + throw new ValidationError(`${prefix}: The field pattern is ${filter.pattern.length} characters long but must be at least 1 characters`, this); + } + } + + // Validate action: must be a valid GuardrailAction value + if (filter.action !== undefined && !Token.isUnresolved(filter.action) && !Object.values(filters.GuardrailAction).includes(filter.action)) { + throw new ValidationError(`${prefix}: action must be a valid GuardrailAction value`, this); + } + + // Validate inputAction: must be a valid GuardrailAction value (if provided) + if (filter.inputAction !== undefined && + !Token.isUnresolved(filter.inputAction) && + !Object.values(filters.GuardrailAction).includes(filter.inputAction)) { + throw new ValidationError(`${prefix}: inputAction must be a valid GuardrailAction value`, this); + } + + // Validate outputAction: must be a valid GuardrailAction value (if provided) + if (filter.outputAction !== undefined && + !Token.isUnresolved(filter.outputAction) && + !Object.values(filters.GuardrailAction).includes(filter.outputAction)) { + throw new ValidationError(`${prefix}: outputAction must be a valid GuardrailAction value`, this); + } + + // Validate inputEnabled: must be a boolean (if provided) + if (filter.inputEnabled !== undefined && + !Token.isUnresolved(filter.inputEnabled) && + typeof filter.inputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: inputEnabled must be a boolean value`, this); + } + + // Validate outputEnabled: must be a boolean (if provided) + if (filter.outputEnabled !== undefined && + !Token.isUnresolved(filter.outputEnabled) && + typeof filter.outputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: outputEnabled must be a boolean value`, this); + } + + // Apply default values for optional properties if not provided + if (filter.inputAction === undefined) { + (filter as any).inputAction = filters.GuardrailAction.BLOCK; + } + if (filter.inputEnabled === undefined) { + (filter as any).inputEnabled = true; + } + if (filter.outputAction === undefined) { + (filter as any).outputAction = filters.GuardrailAction.BLOCK; + } + if (filter.outputEnabled === undefined) { + (filter as any).outputEnabled = true; + } + } + + /** + * Validates a messaging property (blockedInputMessaging or blockedOutputsMessaging). + * @param value The messaging value to validate. + * @param propertyName The name of the property being validated. + */ + private validateMessagingProperty(value: string | undefined, propertyName: string): void { + if (value !== undefined && !Token.isUnresolved(value)) { + if (value.length < 1) { + throw new ValidationError(`Invalid ${propertyName}: The field ${propertyName} is ${value.length} characters long but must be at least 1 characters`, this); + } + if (value.length > 500) { + throw new ValidationError(`Invalid ${propertyName}: The field ${propertyName} is ${value.length} characters long but must be less than or equal to 500 characters`, this); + } + } + } + + /** + * Validates that cross-region configuration is provided when STANDARD tier is used. + * @param props The guardrail properties to validate. + */ + private validateTierConfiguration(props: GuardrailProps): void { + const contentTierConfig = props.contentFiltersTierConfig ?? filters.TierConfig.CLASSIC; + const topicsTierConfig = props.topicsTierConfig ?? filters.TierConfig.CLASSIC; + const hasCrossRegionConfig = props.crossRegionConfig !== undefined; + + // Check if STANDARD tier is used for content filters + if (contentTierConfig === filters.TierConfig.STANDARD && !hasCrossRegionConfig) { + throw new ValidationError( + 'Cross-region configuration is required when using STANDARD tier for content filters. ' + + 'Please provide a crossRegionConfig property with a valid guardrailProfileArn.', + this, + ); + } + + // Check if STANDARD tier is used for topic filters + if (topicsTierConfig === filters.TierConfig.STANDARD && !hasCrossRegionConfig) { + throw new ValidationError( + 'Cross-region configuration is required when using STANDARD tier for topic filters. ' + + 'Please provide a crossRegionConfig property with a valid guardrailProfileArn.', + this, + ); + } + } + + /** + * Validates content filters array and applies default values for optional properties. + * @param contentFilters The content filters to validate. + */ + private validateContentFilters(contentFilters?: filters.ContentFilter[]): void { + if (!contentFilters) return; + + contentFilters.forEach((filter, index) => { + const prefix = `Invalid ContentFilter at index ${index}`; + + // Validate that the filter has required properties + if (!filter.type) { + throw new ValidationError(`${prefix}: type is required`, this); + } + + // Validate input strength + if (filter.inputStrength !== undefined && !Token.isUnresolved(filter.inputStrength)) { + if (!Object.values(filters.ContentFilterStrength).includes(filter.inputStrength)) { + throw new ValidationError(`${prefix}: inputStrength must be a valid ContentFilterStrength value`, this); + } + } + + // Validate output strength + if (filter.outputStrength !== undefined && !Token.isUnresolved(filter.outputStrength)) { + if (!Object.values(filters.ContentFilterStrength).includes(filter.outputStrength)) { + throw new ValidationError(`${prefix}: outputStrength must be a valid ContentFilterStrength value`, this); + } + } + + // Validate input modalities + if (filter.inputModalities) { + filter.inputModalities.forEach((modality, modalityIndex) => { + if (!Object.values(filters.ModalityType).includes(modality)) { + throw new ValidationError(`${prefix}: inputModalities[${modalityIndex}] must be a valid ModalityType value`, this); + } + }); + } + + // Validate output modalities + if (filter.outputModalities) { + filter.outputModalities.forEach((modality, modalityIndex) => { + if (!Object.values(filters.ModalityType).includes(modality)) { + throw new ValidationError(`${prefix}: outputModalities[${modalityIndex}] must be a valid ModalityType value`, this); + } + }); + } + + // Validate inputAction: must be a valid GuardrailAction value (if provided) + if (filter.inputAction !== undefined && + !Token.isUnresolved(filter.inputAction) && + !Object.values(filters.GuardrailAction).includes(filter.inputAction)) { + throw new ValidationError(`${prefix}: inputAction must be a valid GuardrailAction value`, this); + } + + // Validate outputAction: must be a valid GuardrailAction value (if provided) + if (filter.outputAction !== undefined && + !Token.isUnresolved(filter.outputAction) && + !Object.values(filters.GuardrailAction).includes(filter.outputAction)) { + throw new ValidationError(`${prefix}: outputAction must be a valid GuardrailAction value`, this); + } + + // Validate inputEnabled: must be a boolean (if provided) + if (filter.inputEnabled !== undefined && + !Token.isUnresolved(filter.inputEnabled) && + typeof filter.inputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: inputEnabled must be a boolean value`, this); + } + + // Validate outputEnabled: must be a boolean (if provided) + if (filter.outputEnabled !== undefined && + !Token.isUnresolved(filter.outputEnabled) && + typeof filter.outputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: outputEnabled must be a boolean value`, this); + } + + // Apply default values for optional properties if not provided + if (filter.inputAction === undefined) { + (filter as any).inputAction = filters.GuardrailAction.BLOCK; + } + if (filter.inputEnabled === undefined) { + (filter as any).inputEnabled = true; + } + if (filter.outputAction === undefined) { + (filter as any).outputAction = filters.GuardrailAction.BLOCK; + } + if (filter.outputEnabled === undefined) { + (filter as any).outputEnabled = true; + } + }); + } + + /** + * Validates PII filters array and applies default values for optional properties. + * @param piiFilters The PII filters to validate. + */ + private validatePiiFilters(piiFilters?: filters.PIIFilter[]): void { + if (!piiFilters) return; + + piiFilters.forEach((filter, index) => { + const prefix = `Invalid PIIFilter at index ${index}`; + + // Validate that the filter has required properties + if (!filter.type) { + throw new ValidationError(`${prefix}: type is required`, this); + } + + if (!filter.action) { + throw new ValidationError(`${prefix}: action is required`, this); + } + + // Validate action values + if (!Token.isUnresolved(filter.action) && !Object.values(filters.GuardrailAction).includes(filter.action)) { + throw new ValidationError(`${prefix}: action must be a valid GuardrailAction value`, this); + } + + if (filter.inputAction && + !Token.isUnresolved(filter.inputAction) && + !Object.values(filters.GuardrailAction).includes(filter.inputAction)) { + throw new ValidationError(`${prefix}: inputAction must be a valid GuardrailAction value`, this); + } + + if (filter.outputAction && + !Token.isUnresolved(filter.outputAction) && + !Object.values(filters.GuardrailAction).includes(filter.outputAction)) { + throw new ValidationError(`${prefix}: outputAction must be a valid GuardrailAction value`, this); + } + + // Validate inputEnabled: must be a boolean (if provided) + if (filter.inputEnabled !== undefined && + !Token.isUnresolved(filter.inputEnabled) && + typeof filter.inputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: inputEnabled must be a boolean value`, this); + } + + // Validate outputEnabled: must be a boolean (if provided) + if (filter.outputEnabled !== undefined && + !Token.isUnresolved(filter.outputEnabled) && + typeof filter.outputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: outputEnabled must be a boolean value`, this); + } + + // Apply default values for optional properties if not provided + if (filter.inputAction === undefined) { + (filter as any).inputAction = filters.GuardrailAction.BLOCK; + } + if (filter.inputEnabled === undefined) { + (filter as any).inputEnabled = true; + } + if (filter.outputAction === undefined) { + (filter as any).outputAction = filters.GuardrailAction.BLOCK; + } + if (filter.outputEnabled === undefined) { + (filter as any).outputEnabled = true; + } + }); + } + + /** + * Validates regex filters array. + * @param regexFilters The regex filters to validate. + */ + private validateRegexFilters(regexFilters?: filters.RegexFilter[]): void { + if (!regexFilters) return; + + regexFilters.forEach((filter, index) => { + this.validateRegexFilter(filter, index); + }); + } + + /** + * Validates denied topics array and applies default values for optional properties. + * @param deniedTopics The denied topics to validate. + */ + private validateDeniedTopics(deniedTopics?: filters.Topic[]): void { + if (!deniedTopics) return; + + deniedTopics.forEach((topic, index) => { + const prefix = `Invalid Topic at index ${index}`; + + // Validate that the topic has required properties + if (!topic.name) { + throw new ValidationError(`${prefix}: name is required`, this); + } + + if (!topic.definition) { + throw new ValidationError(`${prefix}: definition is required`, this); + } + + // Validate name length + if (!Token.isUnresolved(topic.name) && topic.name.length > 100) { + throw new ValidationError(`${prefix}: name must be 100 characters or less`, this); + } + + // Validate definition length + if (!Token.isUnresolved(topic.definition) && topic.definition.length > 1000) { + throw new ValidationError(`${prefix}: definition must be 1000 characters or less`, this); + } + + // Validate examples if provided + if (topic.examples) { + if (topic.examples.length > 100) { + throw new ValidationError(`${prefix}: examples array cannot contain more than 100 examples`, this); + } + + topic.examples.forEach((example, exampleIndex) => { + if (!Token.isUnresolved(example) && example.length > 100) { + throw new ValidationError(`${prefix}: examples[${exampleIndex}] must be 100 characters or less`, this); + } + }); + } + + // Validate inputAction: must be a valid GuardrailAction value (if provided) + if (topic.inputAction !== undefined && + !Token.isUnresolved(topic.inputAction) && + !Object.values(filters.GuardrailAction).includes(topic.inputAction)) { + throw new ValidationError(`${prefix}: inputAction must be a valid GuardrailAction value`, this); + } + + // Validate outputAction: must be a valid GuardrailAction value (if provided) + if (topic.outputAction !== undefined && + !Token.isUnresolved(topic.outputAction) && + !Object.values(filters.GuardrailAction).includes(topic.outputAction)) { + throw new ValidationError(`${prefix}: outputAction must be a valid GuardrailAction value`, this); + } + + // Validate inputEnabled: must be a boolean (if provided) + if (topic.inputEnabled !== undefined && + !Token.isUnresolved(topic.inputEnabled) && + typeof topic.inputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: inputEnabled must be a boolean value`, this); + } + + // Validate outputEnabled: must be a boolean (if provided) + if (topic.outputEnabled !== undefined && + !Token.isUnresolved(topic.outputEnabled) && + typeof topic.outputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: outputEnabled must be a boolean value`, this); + } + + // Apply default values for optional properties if not provided + if (topic.inputAction === undefined) { + (topic as any).inputAction = filters.GuardrailAction.BLOCK; + } + if (topic.inputEnabled === undefined) { + (topic as any).inputEnabled = true; + } + if (topic.outputAction === undefined) { + (topic as any).outputAction = filters.GuardrailAction.BLOCK; + } + if (topic.outputEnabled === undefined) { + (topic as any).outputEnabled = true; + } + }); + } + + /** + * Validates contextual grounding filters array and applies default values for optional properties. + * @param contextualGroundingFilters The contextual grounding filters to validate. + */ + private validateContextualGroundingFilters(contextualGroundingFilters?: filters.ContextualGroundingFilter[]): void { + if (!contextualGroundingFilters) return; + + contextualGroundingFilters.forEach((filter, index) => { + const prefix = `Invalid ContextualGroundingFilter at index ${index}`; + + // Validate that the filter has required properties + if (!filter.type) { + throw new ValidationError(`${prefix}: type is required`, this); + } + + if (filter.threshold === undefined) { + throw new ValidationError(`${prefix}: threshold is required`, this); + } + + // Validate type + if (!Object.values(filters.ContextualGroundingFilterType).includes(filter.type)) { + throw new ValidationError(`${prefix}: type must be a valid ContextualGroundingFilterType value`, this); + } + + // Validate threshold range + if (!Token.isUnresolved(filter.threshold)) { + if (filter.threshold < 0 || filter.threshold > 0.99) { + throw new ValidationError(`${prefix}: threshold must be between 0 and 0.99`, this); + } + } + + // Validate action: must be a valid GuardrailAction value (if provided) + if (filter.action !== undefined && + !Token.isUnresolved(filter.action) && + !Object.values(filters.GuardrailAction).includes(filter.action)) { + throw new ValidationError(`${prefix}: action must be a valid GuardrailAction value`, this); + } + + // Validate enabled: must be a boolean (if provided) + if (filter.enabled !== undefined && + !Token.isUnresolved(filter.enabled) && + typeof filter.enabled !== 'boolean') { + throw new ValidationError(`${prefix}: enabled must be a boolean value`, this); + } + + // Apply default values for optional properties if not provided + if (filter.action === undefined) { + (filter as any).action = filters.GuardrailAction.BLOCK; + } + if (filter.enabled === undefined) { + (filter as any).enabled = true; + } + }); + } + + /** + * Validates word filters array and applies default values for optional properties. + * @param wordFilters The word filters to validate. + */ + private validateWordFilters(wordFilters?: filters.WordFilter[]): void { + if (!wordFilters) return; + + wordFilters.forEach((filter, index) => { + const prefix = `Invalid WordFilter at index ${index}`; + + // Validate that the filter has required properties + if (!filter.text) { + throw new ValidationError(`${prefix}: text is required`, this); + } + + // Validate text length + if (!Token.isUnresolved(filter.text) && filter.text.length > 100) { + throw new ValidationError(`${prefix}: text must be 100 characters or less`, this); + } + + // Validate inputAction: must be a valid GuardrailAction value (if provided) + if (filter.inputAction !== undefined && + !Token.isUnresolved(filter.inputAction) && + !Object.values(filters.GuardrailAction).includes(filter.inputAction)) { + throw new ValidationError(`${prefix}: inputAction must be a valid GuardrailAction value`, this); + } + + // Validate outputAction: must be a valid GuardrailAction value (if provided) + if (filter.outputAction !== undefined && + !Token.isUnresolved(filter.outputAction) && + !Object.values(filters.GuardrailAction).includes(filter.outputAction)) { + throw new ValidationError(`${prefix}: outputAction must be a valid GuardrailAction value`, this); + } + + // Validate inputEnabled: must be a boolean (if provided) + if (filter.inputEnabled !== undefined && + !Token.isUnresolved(filter.inputEnabled) && + typeof filter.inputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: inputEnabled must be a boolean value`, this); + } + + // Validate outputEnabled: must be a boolean (if provided) + if (filter.outputEnabled !== undefined && + !Token.isUnresolved(filter.outputEnabled) && + typeof filter.outputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: outputEnabled must be a boolean value`, this); + } + + // Apply default values for optional properties if not provided + if (filter.inputAction === undefined) { + (filter as any).inputAction = filters.GuardrailAction.BLOCK; + } + if (filter.inputEnabled === undefined) { + (filter as any).inputEnabled = true; + } + if (filter.outputAction === undefined) { + (filter as any).outputAction = filters.GuardrailAction.BLOCK; + } + if (filter.outputEnabled === undefined) { + (filter as any).outputEnabled = true; + } + }); + } + + /** + * Validates managed word list filters array and applies default values for optional properties. + * @param managedWordListFilters The managed word list filters to validate. + */ + private validateManagedWordListFilters(managedWordListFilters?: filters.ManagedWordFilter[]): void { + if (!managedWordListFilters) return; + + managedWordListFilters.forEach((filter, index) => { + const prefix = `Invalid ManagedWordFilter at index ${index}`; + + // Validate type: must be a valid ManagedWordFilterType value (if provided) + if (filter.type !== undefined && !Object.values(filters.ManagedWordFilterType).includes(filter.type)) { + throw new ValidationError(`${prefix}: type must be a valid ManagedWordFilterType value`, this); + } + + // Validate inputAction: must be a valid GuardrailAction value (if provided) + if (filter.inputAction !== undefined && + !Token.isUnresolved(filter.inputAction) && + !Object.values(filters.GuardrailAction).includes(filter.inputAction)) { + throw new ValidationError(`${prefix}: inputAction must be a valid GuardrailAction value`, this); + } + + // Validate outputAction: must be a valid GuardrailAction value (if provided) + if (filter.outputAction !== undefined && + !Token.isUnresolved(filter.outputAction) && + !Object.values(filters.GuardrailAction).includes(filter.outputAction)) { + throw new ValidationError(`${prefix}: outputAction must be a valid GuardrailAction value`, this); + } + + // Validate inputEnabled: must be a boolean (if provided) + if (filter.inputEnabled !== undefined && + !Token.isUnresolved(filter.inputEnabled) && + typeof filter.inputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: inputEnabled must be a boolean value`, this); + } + + // Validate outputEnabled: must be a boolean (if provided) + if (filter.outputEnabled !== undefined && + !Token.isUnresolved(filter.outputEnabled) && + typeof filter.outputEnabled !== 'boolean') { + throw new ValidationError(`${prefix}: outputEnabled must be a boolean value`, this); + } + + // Apply default values for optional properties if not provided + if (filter.type === undefined) { + (filter as any).type = filters.ManagedWordFilterType.PROFANITY; + } + if (filter.inputAction === undefined) { + (filter as any).inputAction = filters.GuardrailAction.BLOCK; + } + if (filter.inputEnabled === undefined) { + (filter as any).inputEnabled = true; + } + if (filter.outputAction === undefined) { + (filter as any).outputAction = filters.GuardrailAction.BLOCK; + } + if (filter.outputEnabled === undefined) { + (filter as any).outputEnabled = true; + } + }); + } + + /** + * Validates a single content filter and applies default values for optional properties. + * @param filter The content filter to validate. + */ + private validateSingleContentFilter(filter: filters.ContentFilter): void { + // Use the existing validation logic but catch and re-throw with a clearer error message + try { + this.validateContentFilters([filter]); + } catch (error) { + if (error instanceof ValidationError) { + // Replace "at index 0" with a clearer message for single filter validation + const message = error.message.replace(' at index 0', ''); + throw new ValidationError(message, this); + } + throw error; + } + } + + /** + * Validates a single PII filter and applies default values for optional properties. + * @param filter The PII filter to validate. + */ + private validateSinglePiiFilter(filter: filters.PIIFilter): void { + // Use the existing validation logic but catch and re-throw with a clearer error message + try { + this.validatePiiFilters([filter]); + } catch (error) { + if (error instanceof ValidationError) { + // Replace "at index 0" with a clearer message for single filter validation + const message = error.message.replace(' at index 0', ''); + throw new ValidationError(message, this); + } + throw error; + } + } + + /** + * Validates a single regex filter and applies default values for optional properties. + * @param filter The regex filter to validate. + */ + private validateSingleRegexFilter(filter: filters.RegexFilter): void { + // Use the existing validation logic but catch and re-throw with a clearer error message + try { + this.validateRegexFilters([filter]); + } catch (error) { + if (error instanceof ValidationError) { + // Replace "at index 0" with a clearer message for single filter validation + const message = error.message.replace(' at index 0', ''); + throw new ValidationError(message, this); + } + throw error; + } + } + + /** + * Validates a single denied topic and applies default values for optional properties. + * @param filter The denied topic to validate. + */ + private validateSingleDeniedTopic(filter: filters.Topic): void { + // Use the existing validation logic but catch and re-throw with a clearer error message + try { + this.validateDeniedTopics([filter]); + } catch (error) { + if (error instanceof ValidationError) { + // Replace "at index 0" with a clearer message for single filter validation + const message = error.message.replace(' at index 0', ''); + throw new ValidationError(message, this); + } + throw error; + } + } + + /** + * Validates a single contextual grounding filter and applies default values for optional properties. + * @param filter The contextual grounding filter to validate. + */ + private validateSingleContextualGroundingFilter(filter: filters.ContextualGroundingFilter): void { + // Use the existing validation logic but catch and re-throw with a clearer error message + try { + this.validateContextualGroundingFilters([filter]); + } catch (error) { + if (error instanceof ValidationError) { + // Replace "at index 0" with a clearer message for single filter validation + const message = error.message.replace(' at index 0', ''); + throw new ValidationError(message, this); + } + throw error; + } + } + + /** + * Validates a single word filter and applies default values for optional properties. + * @param filter The word filter to validate. + */ + private validateSingleWordFilter(filter: filters.WordFilter): void { + // Use the existing validation logic but catch and re-throw with a clearer error message + try { + this.validateWordFilters([filter]); + } catch (error) { + if (error instanceof ValidationError) { + // Replace "at index 0" with a clearer message for single filter validation + const message = error.message.replace(' at index 0', ''); + throw new ValidationError(message, this); + } + throw error; + } + } + + /** + * Validates a single managed word list filter and applies default values for optional properties. + * @param filter The managed word list filter to validate. + */ + private validateSingleManagedWordListFilter(filter: filters.ManagedWordFilter): void { + // Use the existing validation logic but catch and re-throw with a clearer error message + try { + this.validateManagedWordListFilters([filter]); + } catch (error) { + if (error instanceof ValidationError) { + // Replace "at index 0" with a clearer message for single filter validation + const message = error.message.replace(' at index 0', ''); + throw new ValidationError(message, this); + } + throw error; + } + } +} diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts index 89112e13b8afd..904cb6fb397a0 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts @@ -13,6 +13,12 @@ export * from './agents/agent-collaboration'; export * from './agents/orchestration-executor'; export * from './agents/function-schema'; +// =================================== +// Guardrails +// =================================== +export * from './guardrails/guardrail-filters'; +export * from './guardrails/guardrails'; + // =================================== // Prompts // =================================== diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/agent.test.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/agent.test.ts index 5ab6851b367b7..9c5c4a289c8b8 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/agent.test.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/agent.test.ts @@ -768,5 +768,72 @@ describe('Agent', () => { ], }); }); + + test('creates agent with guardrail', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'Test guardrail for agent', + blockedInputMessaging: 'Input blocked by guardrail', + blockedOutputsMessaging: 'Output blocked by guardrail', + }); + + guardrail.addContentFilter({ + type: bedrock.ContentFilterType.HATE, + inputStrength: bedrock.ContentFilterStrength.HIGH, + outputStrength: bedrock.ContentFilterStrength.HIGH, + inputAction: bedrock.GuardrailAction.BLOCK, + outputAction: bedrock.GuardrailAction.BLOCK, + }); + + new bedrock.Agent(stack, 'TestAgent', { + instruction: 'This is a test instruction that must be at least 40 characters long to be valid', + foundationModel, + guardrail, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + GuardrailConfiguration: { + GuardrailIdentifier: { + 'Fn::GetAtt': [Match.stringLikeRegexp('TestGuardrail.*'), 'GuardrailId'], + }, + GuardrailVersion: { + 'Fn::GetAtt': [Match.stringLikeRegexp('TestGuardrail.*'), 'Version'], + }, + }, + }); + }); + + test('adds guardrail to existing agent', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'Test guardrail for agent', + }); + + guardrail.addContentFilter({ + type: bedrock.ContentFilterType.HATE, + inputStrength: bedrock.ContentFilterStrength.HIGH, + outputStrength: bedrock.ContentFilterStrength.HIGH, + inputAction: bedrock.GuardrailAction.BLOCK, + outputAction: bedrock.GuardrailAction.BLOCK, + }); + + const agent = new bedrock.Agent(stack, 'TestAgent', { + instruction: 'This is a test instruction that must be at least 40 characters long to be valid', + foundationModel, + }); + + agent.addGuardrail(guardrail); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + GuardrailConfiguration: { + GuardrailIdentifier: { + 'Fn::GetAtt': [Match.stringLikeRegexp('TestGuardrail.*'), 'GuardrailId'], + }, + GuardrailVersion: { + 'Fn::GetAtt': [Match.stringLikeRegexp('TestGuardrail.*'), 'Version'], + }, + }, + }); + }); }); }); diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.assets.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.assets.json new file mode 100644 index 0000000000000..3e2ec8234d8ee --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7 Template", + "source": { + "path": "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.template.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/aws-cdk-bedrock-agent-guardrail-1.assets.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/aws-cdk-bedrock-agent-guardrail-1.assets.json new file mode 100644 index 0000000000000..8ccd14342e73d --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/aws-cdk-bedrock-agent-guardrail-1.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "3db36909f81d26e100a03505fa9d1b3c2933133f9274a15842c8ce601a66e55c": { + "displayName": "aws-cdk-bedrock-agent-guardrail-1 Template", + "source": { + "path": "aws-cdk-bedrock-agent-guardrail-1.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-0ce7e581": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "3db36909f81d26e100a03505fa9d1b3c2933133f9274a15842c8ce601a66e55c.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/aws-cdk-bedrock-agent-guardrail-1.template.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/aws-cdk-bedrock-agent-guardrail-1.template.json new file mode 100644 index 0000000000000..1871c687f97eb --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/aws-cdk-bedrock-agent-guardrail-1.template.json @@ -0,0 +1,223 @@ +{ + "Resources": { + "GuardrailMyGuardrailF9A5588E": { + "Type": "AWS::Bedrock::Guardrail", + "Properties": { + "BlockedInputMessaging": "Sorry, your query violates our usage policy.", + "BlockedOutputsMessaging": "Sorry, I am unable to answer your question because of our usage policy.", + "ContentPolicyConfig": { + "ContentFiltersTierConfig": { + "TierName": "CLASSIC" + }, + "FiltersConfig": [ + { + "InputAction": "BLOCK", + "InputEnabled": true, + "InputStrength": "HIGH", + "OutputAction": "BLOCK", + "OutputEnabled": true, + "OutputStrength": "MEDIUM", + "Type": "SEXUAL" + } + ] + }, + "Description": "This is a guardrail with at least 40 characters of description", + "Name": "guardrail" + } + }, + "AgentWithGuardrailRole7432C16A": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "aws:SourceAccount": { + "Ref": "AWS::AccountId" + } + }, + "ArnLike": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":agent/*" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "bedrock.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "RoleName": "agent-awscdkbedrockagentgentwithguardrail-83bb6b17-bedrockagent" + } + }, + "AgentWithGuardrailRoleDefaultPolicyB902C056": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "bedrock:ApplyGuardrail", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "GuardrailMyGuardrailF9A5588E", + "GuardrailArn" + ] + } + }, + { + "Action": [ + "bedrock:GetFoundationModel", + "bedrock:InvokeModel*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + "::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AgentWithGuardrailRoleDefaultPolicyB902C056", + "Roles": [ + { + "Ref": "AgentWithGuardrailRole7432C16A" + } + ] + } + }, + "AgentWithGuardrail931B1A0C": { + "Type": "AWS::Bedrock::Agent", + "Properties": { + "ActionGroups": [ + { + "ActionGroupName": "UserInputAction", + "ActionGroupState": "DISABLED", + "ParentActionGroupSignature": "AMAZON.UserInput", + "SkipResourceInUseCheckOnDelete": false + }, + { + "ActionGroupName": "CodeInterpreterAction", + "ActionGroupState": "DISABLED", + "ParentActionGroupSignature": "AMAZON.CodeInterpreter", + "SkipResourceInUseCheckOnDelete": false + } + ], + "AgentName": "agent-with-guardrail", + "AgentResourceRoleArn": { + "Fn::GetAtt": [ + "AgentWithGuardrailRole7432C16A", + "Arn" + ] + }, + "AutoPrepare": false, + "FoundationModel": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + "::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0" + ] + ] + }, + "GuardrailConfiguration": { + "GuardrailIdentifier": { + "Fn::GetAtt": [ + "GuardrailMyGuardrailF9A5588E", + "GuardrailId" + ] + }, + "GuardrailVersion": { + "Fn::GetAtt": [ + "GuardrailMyGuardrailF9A5588E", + "Version" + ] + } + }, + "IdleSessionTTLInSeconds": 600, + "Instruction": "You are a helpful and friendly agent that answers questions about literature.", + "OrchestrationType": "DEFAULT", + "SkipResourceInUseCheckOnDelete": true + }, + "DependsOn": [ + "AgentWithGuardrailRoleDefaultPolicyB902C056" + ] + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/cdk.out b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/cdk.out new file mode 100644 index 0000000000000..523a9aac37cbf --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"48.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/integ.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/integ.json new file mode 100644 index 0000000000000..b03c7bf6edfaa --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "48.0.0", + "testCases": { + "BedrockAgentGuardrail/DefaultTest": { + "stacks": [ + "aws-cdk-bedrock-agent-guardrail-1" + ], + "assertionStack": "BedrockAgentGuardrail/DefaultTest/DeployAssert", + "assertionStackName": "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7" + } + }, + "minimumCliVersion": "2.1023.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/manifest.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/manifest.json new file mode 100644 index 0000000000000..1b2ba1eea26ab --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/manifest.json @@ -0,0 +1,712 @@ +{ + "version": "48.0.0", + "artifacts": { + "aws-cdk-bedrock-agent-guardrail-1.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-bedrock-agent-guardrail-1.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-bedrock-agent-guardrail-1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-bedrock-agent-guardrail-1.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3db36909f81d26e100a03505fa9d1b3c2933133f9274a15842c8ce601a66e55c.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-bedrock-agent-guardrail-1.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-bedrock-agent-guardrail-1.assets" + ], + "metadata": { + "/aws-cdk-bedrock-agent-guardrail-1/Guardrail/MyGuardrail": [ + { + "type": "aws:cdk:logicalId", + "data": "GuardrailMyGuardrailF9A5588E" + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "roleName": "*", + "assumedBy": { + "principalAccount": "*", + "assumeRoleAction": "*" + } + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachInlinePolicy": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addToPrincipalPolicy": [ + {} + ] + } + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/ImportRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithGuardrailRole7432C16A" + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/DefaultPolicy": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "attachToRole": [ + "*" + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + }, + { + "type": "aws:cdk:analytics:method", + "data": { + "addStatements": [ + {} + ] + } + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithGuardrailRoleDefaultPolicyB902C056" + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithGuardrail931B1A0C" + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-bedrock-agent-guardrail-1/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-bedrock-agent-guardrail-1" + }, + "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "BedrockAgentGuardrailDefaultTestDeployAssert799FCED7.assets" + ], + "metadata": { + "/BedrockAgentGuardrail/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/BedrockAgentGuardrail/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "BedrockAgentGuardrail/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + } + } + } + } + }, + "minimumCliVersion": "2.1023.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/tree.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/tree.json new file mode 100644 index 0000000000000..803063a5e023a --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-cdk-bedrock-agent-guardrail-1":{"id":"aws-cdk-bedrock-agent-guardrail-1","path":"aws-cdk-bedrock-agent-guardrail-1","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Guardrail":{"id":"Guardrail","path":"aws-cdk-bedrock-agent-guardrail-1/Guardrail","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.Guardrail","version":"0.0.0","metadata":[]},"children":{"MyGuardrail":{"id":"MyGuardrail","path":"aws-cdk-bedrock-agent-guardrail-1/Guardrail/MyGuardrail","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrock.CfnGuardrail","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Bedrock::Guardrail","aws:cdk:cloudformation:props":{"blockedInputMessaging":"Sorry, your query violates our usage policy.","blockedOutputsMessaging":"Sorry, I am unable to answer your question because of our usage policy.","contentPolicyConfig":{"filtersConfig":[{"type":"SEXUAL","inputStrength":"HIGH","outputStrength":"MEDIUM","inputAction":"BLOCK","inputEnabled":true,"outputAction":"BLOCK","outputEnabled":true}],"contentFiltersTierConfig":{"tierName":"CLASSIC"}},"description":"This is a guardrail with at least 40 characters of description","name":"guardrail"}}}}},"AgentWithGuardrail":{"id":"AgentWithGuardrail","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.Agent","version":"0.0.0","metadata":["*","*","*","*"]},"children":{"Role":{"id":"Role","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Role","version":"0.0.0","metadata":[{"roleName":"*","assumedBy":{"principalAccount":"*","assumeRoleAction":"*"}},{"addToPrincipalPolicy":[{}]},{"attachInlinePolicy":["*"]},{"attachInlinePolicy":["*"]},{"addToPrincipalPolicy":[{}]}]},"children":{"ImportRole":{"id":"ImportRole","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/ImportRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnRole","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Role","aws:cdk:cloudformation:props":{"assumeRolePolicyDocument":{"Statement":[{"Action":"sts:AssumeRole","Condition":{"StringEquals":{"aws:SourceAccount":{"Ref":"AWS::AccountId"}},"ArnLike":{"aws:SourceArn":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},":",{"Ref":"AWS::AccountId"},":agent/*"]]}}},"Effect":"Allow","Principal":{"Service":"bedrock.amazonaws.com"}}],"Version":"2012-10-17"},"roleName":"agent-awscdkbedrockagentgentwithguardrail-83bb6b17-bedrockagent"}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/DefaultPolicy","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.Policy","version":"0.0.0","metadata":["*",{"attachToRole":["*"]},{"attachToRole":["*"]},{"addStatements":[{}]},{"addStatements":[{}]}]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Role/DefaultPolicy/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_iam.CfnPolicy","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::IAM::Policy","aws:cdk:cloudformation:props":{"policyDocument":{"Statement":[{"Action":"bedrock:ApplyGuardrail","Effect":"Allow","Resource":{"Fn::GetAtt":["GuardrailMyGuardrailF9A5588E","GuardrailArn"]}},{"Action":["bedrock:GetFoundationModel","bedrock:InvokeModel*"],"Effect":"Allow","Resource":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},"::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0"]]}}],"Version":"2012-10-17"},"policyName":"AgentWithGuardrailRoleDefaultPolicyB902C056","roles":[{"Ref":"AgentWithGuardrailRole7432C16A"}]}}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrock.CfnAgent","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Bedrock::Agent","aws:cdk:cloudformation:props":{"actionGroups":[{"actionGroupName":"UserInputAction","actionGroupState":"DISABLED","parentActionGroupSignature":"AMAZON.UserInput","skipResourceInUseCheckOnDelete":false},{"actionGroupName":"CodeInterpreterAction","actionGroupState":"DISABLED","parentActionGroupSignature":"AMAZON.CodeInterpreter","skipResourceInUseCheckOnDelete":false}],"agentName":"agent-with-guardrail","agentResourceRoleArn":{"Fn::GetAtt":["AgentWithGuardrailRole7432C16A","Arn"]},"autoPrepare":false,"foundationModel":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},"::foundation-model/anthropic.claude-3-5-sonnet-20241022-v2:0"]]},"guardrailConfiguration":{"guardrailIdentifier":{"Fn::GetAtt":["GuardrailMyGuardrailF9A5588E","GuardrailId"]},"guardrailVersion":{"Fn::GetAtt":["GuardrailMyGuardrailF9A5588E","Version"]}},"idleSessionTtlInSeconds":600,"instruction":"You are a helpful and friendly agent that answers questions about literature.","orchestrationType":"DEFAULT","skipResourceInUseCheckOnDelete":true}}},"DefaultAlias":{"id":"DefaultAlias","path":"aws-cdk-bedrock-agent-guardrail-1/AgentWithGuardrail/DefaultAlias","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.AgentAliasBase","version":"0.0.0","metadata":[]}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-cdk-bedrock-agent-guardrail-1/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-cdk-bedrock-agent-guardrail-1/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"BedrockAgentGuardrail":{"id":"BedrockAgentGuardrail","path":"BedrockAgentGuardrail","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"BedrockAgentGuardrail/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"BedrockAgentGuardrail/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"BedrockAgentGuardrail/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"BedrockAgentGuardrail/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"BedrockAgentGuardrail/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.ts new file mode 100644 index 0000000000000..9d2a273dcf217 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/agents/integ.agent-guardrail.ts @@ -0,0 +1,40 @@ +/* + * Integration test for Bedrock Agent with Guardrail construct + */ + +/// !cdk-integ aws-cdk-bedrock-agent-guardrail-1 + +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as bedrock from '../../../bedrock'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-bedrock-agent-guardrail-1'); + +// Create a guardrail +const guardrail = new bedrock.Guardrail(stack, 'Guardrail', { + guardrailName: 'guardrail', + description: 'This is a guardrail with at least 40 characters of description', +}); + +guardrail.addContentFilter({ + type: bedrock.ContentFilterType.SEXUAL, + inputStrength: bedrock.ContentFilterStrength.HIGH, + outputStrength: bedrock.ContentFilterStrength.MEDIUM, +}); + +// Create an agent with a guardrail +new bedrock.Agent(stack, 'AgentWithGuardrail', { + agentName: 'agent-with-guardrail', + instruction: 'You are a helpful and friendly agent that answers questions about literature.', + foundationModel: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V2_0, + guardrail: guardrail, + forceDelete: true, +}); + +new integ.IntegTest(app, 'BedrockAgentGuardrail', { + testCases: [stack], +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/guardrails.test.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/guardrails.test.ts new file mode 100644 index 0000000000000..2991f1dfd8ed4 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/guardrails.test.ts @@ -0,0 +1,2846 @@ +import { App } from 'aws-cdk-lib/core'; +import * as core from 'aws-cdk-lib/core'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import * as bedrock from '../../../bedrock'; +import * as kms from 'aws-cdk-lib/aws-kms'; + +describe('CDK-Created-Guardrail', () => { + let stack: core.Stack; + + beforeEach(() => { + const app = new App(); + stack = new core.Stack(app, 'test-stack'); + }); + + test('Basic Creation', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + // test defaults + BlockedInputMessaging: 'Sorry, your query violates our usage policy.', + BlockedOutputsMessaging: 'Sorry, I am unable to answer your question because of our usage policy.', + // ensure others are undefined + TopicPolicyConfig: Match.absent(), + ContextualGroundingPolicyConfig: Match.absent(), + ContentPolicyConfig: Match.absent(), + WordPolicyConfig: Match.absent(), + SensitiveInformationPolicyConfig: Match.absent(), + }); + + guardrail.name; + }); + + test('Custom Messaging Creation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail with custom messaging', + blockedInputMessaging: 'Custom input blocked message', + blockedOutputsMessaging: 'Custom output blocked message', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail with custom messaging', + BlockedInputMessaging: 'Custom input blocked message', + BlockedOutputsMessaging: 'Custom output blocked message', + // ensure others are undefined + TopicPolicyConfig: Match.absent(), + ContextualGroundingPolicyConfig: Match.absent(), + ContentPolicyConfig: Match.absent(), + WordPolicyConfig: Match.absent(), + SensitiveInformationPolicyConfig: Match.absent(), + }); + }); + + test('Basic Creation + KMS Key', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + // test defaults" + kmsKey: kms.Key.fromKeyArn( + stack, + 'importedKey', + 'arn:aws:kms:eu-central-1:123456789012:key/06484191-7d55-49fb-9be7-0baaf7fe8418', + ), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + KmsKeyArn: 'arn:aws:kms:eu-central-1:123456789012:key/06484191-7d55-49fb-9be7-0baaf7fe8418', + }); + }); + + test('Topic Filter - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + }, + }); + }); + + test('Topic Filter - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addDeniedTopicFilter(bedrock.Topic.FINANCIAL_ADVICE); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + }, + }); + }); + + describe('Topic validation', () => { + test('validates inputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [ + { + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Example 1'], + inputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid Topic at index 0: inputAction must be a valid GuardrailAction value/); + }); + + test('validates outputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [ + { + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Example 1'], + outputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid Topic at index 0: outputAction must be a valid GuardrailAction value/); + }); + + test('validates inputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [ + { + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Example 1'], + inputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid Topic at index 0: inputEnabled must be a boolean value/); + }); + + test('validates outputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [ + { + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Example 1'], + outputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid Topic at index 0: outputEnabled must be a boolean value/); + }); + + test('accepts Topic with all optional properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [ + { + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Example 1'], + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: false, + }, + ], + }); + }).not.toThrow(); + }); + + test('applies default values for optional Topic properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [ + { + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Example 1'], + // Note: Not providing optional properties to test defaults + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + TopicPolicyConfig: { + TopicsConfig: [ + { + Name: 'TestTopic', + Definition: 'A test topic definition', + Examples: ['Example 1'], + Type: 'DENY', + InputAction: 'BLOCK', + InputEnabled: true, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + + test('applies custom values for optional Topic properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [ + { + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Example 1'], + inputAction: bedrock.GuardrailAction.NONE, + inputEnabled: false, + outputAction: bedrock.GuardrailAction.BLOCK, + outputEnabled: true, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + TopicPolicyConfig: { + TopicsConfig: [ + { + Name: 'TestTopic', + Definition: 'A test topic definition', + Examples: ['Example 1'], + Type: 'DENY', + InputAction: 'NONE', + InputEnabled: false, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + }); + + test('Content Filter - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + }, + }); + }); + + test('Content Filter - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addContentFilter({ + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + }, + }); + }); + + describe('ContentFilter validation', () => { + test('validates inputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + inputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid ContentFilter at index 0: inputAction must be a valid GuardrailAction value/); + }); + + test('validates outputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + outputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid ContentFilter at index 0: outputAction must be a valid GuardrailAction value/); + }); + + test('validates inputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + inputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid ContentFilter at index 0: inputEnabled must be a boolean value/); + }); + + test('validates outputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + outputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid ContentFilter at index 0: outputEnabled must be a boolean value/); + }); + + test('accepts ContentFilter with all optional properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: false, + }, + ], + }); + }).not.toThrow(); + }); + + test('applies default values for optional ContentFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + // Note: Not providing optional properties to test defaults + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + InputAction: 'BLOCK', + InputEnabled: true, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + + test('applies custom values for optional ContentFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + inputAction: bedrock.GuardrailAction.NONE, + inputEnabled: false, + outputAction: bedrock.GuardrailAction.BLOCK, + outputEnabled: true, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + InputAction: 'NONE', + InputEnabled: false, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + + test('validates addContentFilter with clear error message', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + }); + + expect(() => { + guardrail.addContentFilter({ + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + inputAction: 'INVALID_ACTION' as any, + }); + }).toThrow(/Invalid ContentFilter: inputAction must be a valid GuardrailAction value/); + }); + + test('validates all add filter methods with clear error messages', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + }); + + // Test addPIIFilter + expect(() => { + guardrail.addPIIFilter({ + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.BLOCK, + inputAction: 'INVALID_ACTION' as any, + }); + }).toThrow(/Invalid PIIFilter: inputAction must be a valid GuardrailAction value/); + + // Test addRegexFilter + expect(() => { + guardrail.addRegexFilter({ + name: 'test', + pattern: 'test', + action: bedrock.GuardrailAction.BLOCK, + inputAction: 'INVALID_ACTION' as any, + }); + }).toThrow(/Invalid RegexFilter: inputAction must be a valid GuardrailAction value/); + + // Test addWordFilter + expect(() => { + guardrail.addWordFilter({ + text: 'test', + inputAction: 'INVALID_ACTION' as any, + }); + }).toThrow(/Invalid WordFilter: inputAction must be a valid GuardrailAction value/); + + // Test addManagedWordListFilter + expect(() => { + guardrail.addManagedWordListFilter({ + inputAction: 'INVALID_ACTION' as any, + }); + }).toThrow(/Invalid ManagedWordFilter: inputAction must be a valid GuardrailAction value/); + }); + }); + + test('Contextual Grounding Filter - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + ContextualGroundingPolicyConfig: { + FiltersConfig: [ + { + Threshold: 0.99, + Type: 'GROUNDING', + }, + ], + }, + }); + }); + + test('Contextual Grounding Filter - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addContextualGroundingFilter({ + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + ContextualGroundingPolicyConfig: { + FiltersConfig: [ + { + Threshold: 0.99, + Type: 'GROUNDING', + }, + ], + }, + }); + }); + + describe('ContextualGroundingFilter validation', () => { + test('validates action is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + action: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid ContextualGroundingFilter at index 0: action must be a valid GuardrailAction value/); + }); + + test('validates enabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + enabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid ContextualGroundingFilter at index 0: enabled must be a boolean value/); + }); + + test('accepts ContextualGroundingFilter with all optional properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + action: bedrock.GuardrailAction.BLOCK, + enabled: true, + }, + ], + }); + }).not.toThrow(); + }); + + test('applies default values for optional ContextualGroundingFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + // Note: Not providing optional properties to test defaults + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + ContextualGroundingPolicyConfig: { + FiltersConfig: [ + { + Threshold: 0.99, + Type: 'GROUNDING', + Action: 'BLOCK', + Enabled: true, + }, + ], + }, + }); + }); + + test('applies custom values for optional ContextualGroundingFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + action: bedrock.GuardrailAction.NONE, + enabled: false, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + ContextualGroundingPolicyConfig: { + FiltersConfig: [ + { + Threshold: 0.99, + Type: 'GROUNDING', + Action: 'NONE', + Enabled: false, + }, + ], + }, + }); + }); + }); + + test('PII Filter - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + SensitiveInformationPolicyConfig: { + PiiEntitiesConfig: [ + { + Action: 'ANONYMIZE', + Type: 'ADDRESS', + }, + ], + }, + }); + }); + + test('PII Filter - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addPIIFilter({ + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + SensitiveInformationPolicyConfig: { + PiiEntitiesConfig: [ + { + Action: 'ANONYMIZE', + Type: 'ADDRESS', + }, + ], + }, + }); + }); + + describe('PIIFilter validation', () => { + test('validates action is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid PIIFilter at index 0: action must be a valid GuardrailAction value/); + }); + + test('validates inputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + inputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid PIIFilter at index 0: inputAction must be a valid GuardrailAction value/); + }); + + test('validates outputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + outputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid PIIFilter at index 0: outputAction must be a valid GuardrailAction value/); + }); + + test('validates inputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + inputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid PIIFilter at index 0: inputEnabled must be a boolean value/); + }); + + test('validates outputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + outputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid PIIFilter at index 0: outputEnabled must be a boolean value/); + }); + + test('accepts PIIFilter with all optional properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: false, + }, + ], + }); + }).not.toThrow(); + }); + + test('applies default values for optional PIIFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + // Note: Not providing optional properties to test defaults + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + SensitiveInformationPolicyConfig: { + PiiEntitiesConfig: [ + { + Action: 'ANONYMIZE', + Type: 'ADDRESS', + InputAction: 'BLOCK', + InputEnabled: true, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + + test('applies custom values for optional PIIFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + inputAction: bedrock.GuardrailAction.NONE, + inputEnabled: false, + outputAction: bedrock.GuardrailAction.BLOCK, + outputEnabled: true, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + SensitiveInformationPolicyConfig: { + PiiEntitiesConfig: [ + { + Action: 'ANONYMIZE', + Type: 'ADDRESS', + InputAction: 'NONE', + InputEnabled: false, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + }); + + test('Regex Filter - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + description: 'This is a test regex filter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + SensitiveInformationPolicyConfig: { + RegexesConfig: [ + { + Action: 'ANONYMIZE', + Name: 'TestRegexFilter', + Pattern: '/^[A-Z]{2}d{6}$/', + Description: 'This is a test regex filter', + }, + ], + }, + }); + }); + + test('Regex Filter - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addRegexFilter({ + name: 'TestRegexFilter', + description: 'This is a test regex filter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + SensitiveInformationPolicyConfig: { + RegexesConfig: [ + { + Action: 'ANONYMIZE', + Name: 'TestRegexFilter', + Pattern: '/^[A-Z]{2}d{6}$/', + Description: 'This is a test regex filter', + }, + ], + }, + }); + }); + + describe('RegexFilter validation', () => { + test('validates name length - too short', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: '', // Empty name + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: The field name is 0 characters long but must be at least 1 characters/); + }); + + test('validates name length - too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'a'.repeat(101), // 101 characters + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: The field name is 101 characters long but must be less than or equal to 100 characters/); + }); + + test('validates description length - too short', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + description: '', // Empty description + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: The field description is 0 characters long but must be at least 1 characters/); + }); + + test('validates description length - too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + description: 'a'.repeat(1001), // 1001 characters + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: The field description is 1001 characters long but must be less than or equal to 1000 characters/); + }); + + test('validates pattern length - too short', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '', // Empty pattern + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: The field pattern is 0 characters long but must be at least 1 characters/); + }); + + test('validates addRegexFilter method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + }); + + expect(() => { + guardrail.addRegexFilter({ + name: '', // Empty name + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }); + }).toThrow(/Invalid RegexFilter: The field name is 0 characters long but must be at least 1 characters/); + }); + + test('accepts valid RegexFilter', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + description: 'A valid description', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + }); + }).not.toThrow(); + }); + + test('validates action is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '/^[A-Z]{2}d{6}$/', + action: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: action must be a valid GuardrailAction value/); + }); + + test('validates inputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + inputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: inputAction must be a valid GuardrailAction value/); + }); + + test('validates outputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + outputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: outputAction must be a valid GuardrailAction value/); + }); + + test('validates inputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + inputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: inputEnabled must be a boolean value/); + }); + + test('validates outputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + outputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid RegexFilter at index 0: outputEnabled must be a boolean value/); + }); + + test('accepts RegexFilter with all optional properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + description: 'A valid description', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: false, + }, + ], + }); + }).not.toThrow(); + }); + + test('applies default values for optional RegexFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + // Note: Not providing optional properties to test defaults + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + SensitiveInformationPolicyConfig: { + RegexesConfig: [ + { + Action: 'ANONYMIZE', + Name: 'TestRegexFilter', + Pattern: '/^[A-Z]{2}d{6}$/', + InputAction: 'BLOCK', + InputEnabled: true, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + + test('applies custom values for optional RegexFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [ + { + name: 'TestRegexFilter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + inputAction: bedrock.GuardrailAction.NONE, + inputEnabled: false, + outputAction: bedrock.GuardrailAction.BLOCK, + outputEnabled: true, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + SensitiveInformationPolicyConfig: { + RegexesConfig: [ + { + Action: 'ANONYMIZE', + Name: 'TestRegexFilter', + Pattern: '/^[A-Z]{2}d{6}$/', + InputAction: 'NONE', + InputEnabled: false, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + }); + + test('Word Filter - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + wordFilters: [ + { + text: 'reggaeton', + }, + { + text: 'alcohol', + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + WordPolicyConfig: { + WordsConfig: [ + { + Text: 'reggaeton', + }, + { + Text: 'alcohol', + }, + ], + }, + }); + }); + + test('Word Filter - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addWordFilter({ + text: 'reggaeton', + }); + guardrail.addWordFilter({ + text: 'alcohol', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + WordPolicyConfig: { + WordsConfig: [ + { + Text: 'reggaeton', + }, + { + Text: 'alcohol', + }, + ], + }, + }); + }); + + describe('WordFilter validation', () => { + test('validates inputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [ + { + text: 'test', + inputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid WordFilter at index 0: inputAction must be a valid GuardrailAction value/); + }); + + test('validates outputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [ + { + text: 'test', + outputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid WordFilter at index 0: outputAction must be a valid GuardrailAction value/); + }); + + test('validates inputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [ + { + text: 'test', + inputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid WordFilter at index 0: inputEnabled must be a boolean value/); + }); + + test('validates outputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [ + { + text: 'test', + outputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid WordFilter at index 0: outputEnabled must be a boolean value/); + }); + + test('accepts WordFilter with all optional properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [ + { + text: 'test', + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: false, + }, + ], + }); + }).not.toThrow(); + }); + + test('applies default values for optional WordFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [ + { + text: 'test', + // Note: Not providing optional properties to test defaults + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + WordPolicyConfig: { + WordsConfig: [ + { + Text: 'test', + InputAction: 'BLOCK', + InputEnabled: true, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + + test('applies custom values for optional WordFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [ + { + text: 'test', + inputAction: bedrock.GuardrailAction.NONE, + inputEnabled: false, + outputAction: bedrock.GuardrailAction.BLOCK, + outputEnabled: true, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + WordPolicyConfig: { + WordsConfig: [ + { + Text: 'test', + InputAction: 'NONE', + InputEnabled: false, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + }); + + test('Managed Word Filter - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + managedWordListFilters: [ + { + type: bedrock.ManagedWordFilterType.PROFANITY, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + WordPolicyConfig: { + ManagedWordListsConfig: [ + { + Type: 'PROFANITY', + }, + ], + }, + }); + }); + + test('Managed Word Filter - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addManagedWordListFilter({ + type: bedrock.ManagedWordFilterType.PROFANITY, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + WordPolicyConfig: { + ManagedWordListsConfig: [ + { + Type: 'PROFANITY', + }, + ], + }, + }); + }); + + describe('ManagedWordFilter validation', () => { + test('validates type is a valid ManagedWordFilterType value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + type: 'INVALID_TYPE' as any, + }, + ], + }); + }).toThrow(/Invalid ManagedWordFilter at index 0: type must be a valid ManagedWordFilterType value/); + }); + + test('validates inputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + inputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid ManagedWordFilter at index 0: inputAction must be a valid GuardrailAction value/); + }); + + test('validates outputAction is a valid GuardrailAction value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + outputAction: 'INVALID_ACTION' as any, + }, + ], + }); + }).toThrow(/Invalid ManagedWordFilter at index 0: outputAction must be a valid GuardrailAction value/); + }); + + test('validates inputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + inputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid ManagedWordFilter at index 0: inputEnabled must be a boolean value/); + }); + + test('validates outputEnabled is a boolean value', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + outputEnabled: 'not a boolean' as any, + }, + ], + }); + }).toThrow(/Invalid ManagedWordFilter at index 0: outputEnabled must be a boolean value/); + }); + + test('accepts ManagedWordFilter with all optional properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + type: bedrock.ManagedWordFilterType.PROFANITY, + inputAction: bedrock.GuardrailAction.BLOCK, + inputEnabled: true, + outputAction: bedrock.GuardrailAction.NONE, + outputEnabled: false, + }, + ], + }); + }).not.toThrow(); + }); + + test('applies default values for optional ManagedWordFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + // Note: Not providing optional properties to test defaults + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + WordPolicyConfig: { + ManagedWordListsConfig: [ + { + Type: 'PROFANITY', + InputAction: 'BLOCK', + InputEnabled: true, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + + test('applies custom values for optional ManagedWordFilter properties in CloudFormation', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [ + { + type: bedrock.ManagedWordFilterType.PROFANITY, + inputAction: bedrock.GuardrailAction.NONE, + inputEnabled: false, + outputAction: bedrock.GuardrailAction.BLOCK, + outputEnabled: true, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + WordPolicyConfig: { + ManagedWordListsConfig: [ + { + Type: 'PROFANITY', + InputAction: 'NONE', + InputEnabled: false, + OutputAction: 'BLOCK', + OutputEnabled: true, + }, + ], + }, + }); + }); + }); + + test('All filters - Props', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + }, + ], + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + regexFilters: [ + { + name: 'TestRegexFilter', + description: 'This is a test regex filter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + wordFilters: [ + { + text: 'reggaeton', + }, + { + text: 'alcohol', + }, + ], + managedWordListFilters: [ + { + type: bedrock.ManagedWordFilterType.PROFANITY, + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + }, + ContextualGroundingPolicyConfig: { + FiltersConfig: [ + { + Threshold: 0.99, + Type: 'GROUNDING', + }, + ], + }, + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + }, + WordPolicyConfig: { + WordsConfig: [ + { + Text: 'reggaeton', + }, + { + Text: 'alcohol', + }, + ], + ManagedWordListsConfig: [ + { + Type: 'PROFANITY', + }, + ], + }, + SensitiveInformationPolicyConfig: { + RegexesConfig: [ + { + Action: 'ANONYMIZE', + Name: 'TestRegexFilter', + Pattern: '/^[A-Z]{2}d{6}$/', + Description: 'This is a test regex filter', + }, + ], + PiiEntitiesConfig: [ + { + Action: 'ANONYMIZE', + Type: 'ADDRESS', + }, + ], + }, + }); + }); + + test('All filters - Method', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addDeniedTopicFilter(bedrock.Topic.FINANCIAL_ADVICE); + + guardrail.addContentFilter({ + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }); + + guardrail.addContextualGroundingFilter({ + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + }); + + guardrail.addWordFilter({ + text: 'reggaeton', + }); + guardrail.addWordFilter({ + text: 'alcohol', + }); + + guardrail.addManagedWordListFilter({ + type: bedrock.ManagedWordFilterType.PROFANITY, + }); + + guardrail.addPIIFilter({ + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + }); + guardrail.addRegexFilter({ + name: 'TestRegexFilter', + description: 'This is a test regex filter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + }, + ContextualGroundingPolicyConfig: { + FiltersConfig: [ + { + Threshold: 0.99, + Type: 'GROUNDING', + }, + ], + }, + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + }, + WordPolicyConfig: { + WordsConfig: [ + { + Text: 'reggaeton', + }, + { + Text: 'alcohol', + }, + ], + ManagedWordListsConfig: [ + { + Type: 'PROFANITY', + }, + ], + }, + SensitiveInformationPolicyConfig: { + RegexesConfig: [ + { + Action: 'ANONYMIZE', + Name: 'TestRegexFilter', + Pattern: '/^[A-Z]{2}d{6}$/', + Description: 'This is a test regex filter', + }, + ], + PiiEntitiesConfig: [ + { + Action: 'ANONYMIZE', + Type: 'ADDRESS', + }, + ], + }, + }); + }); + + test('Versioning', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + }); + + guardrail.addDeniedTopicFilter(bedrock.Topic.FINANCIAL_ADVICE); + + guardrail.createVersion(); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::GuardrailVersion', { + GuardrailIdentifier: { + 'Fn::GetAtt': [Match.anyValue(), 'GuardrailId'], + }, + }); + }); + + test('Content Filter with Tier Configuration - CLASSIC', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + contentFiltersTierConfig: bedrock.TierConfig.CLASSIC, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + ContentFiltersTierConfig: { + TierName: 'CLASSIC', + }, + }, + }); + }); + + test('Content Filter with Tier Configuration - STANDARD', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/test-profile', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + CrossRegionConfig: { + GuardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/test-profile', + }, + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + ContentFiltersTierConfig: { + TierName: 'STANDARD', + }, + }, + }); + }); + + test('Topic Filter with Tier Configuration - CLASSIC', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + topicsTierConfig: bedrock.TierConfig.CLASSIC, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + TopicsTierConfig: { + TierName: 'CLASSIC', + }, + }, + }); + }); + + test('Topic Filter with Tier Configuration - STANDARD', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + topicsTierConfig: bedrock.TierConfig.STANDARD, + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/test-profile', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + CrossRegionConfig: { + GuardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/test-profile', + }, + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + TopicsTierConfig: { + TierName: 'STANDARD', + }, + }, + }); + }); + + test('Both Content and Topic Filters with Tier Configuration', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + topicsTierConfig: bedrock.TierConfig.CLASSIC, + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/test-profile', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + CrossRegionConfig: { + GuardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/test-profile', + }, + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + ContentFiltersTierConfig: { + TierName: 'STANDARD', + }, + }, + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + TopicsTierConfig: { + TierName: 'CLASSIC', + }, + }, + }); + }); + + test('Default Tier Configuration - CLASSIC', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail', + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + ContentFiltersTierConfig: { + TierName: 'CLASSIC', + }, + }, + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + TopicsTierConfig: { + TierName: 'CLASSIC', + }, + }, + }); + }); + + describe('Messaging validation', () => { + test('validates blockedInputMessaging length - too short', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedInputMessaging: '', // Empty message + }); + }).toThrow(/Invalid blockedInputMessaging: The field blockedInputMessaging is 0 characters long but must be at least 1 characters/); + }); + + test('validates blockedInputMessaging length - too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedInputMessaging: 'a'.repeat(501), // 501 characters + }); + }).toThrow(/Invalid blockedInputMessaging: The field blockedInputMessaging is 501 characters long but must be less than or equal to 500 characters/); + }); + + test('validates blockedOutputsMessaging length - too short', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedOutputsMessaging: '', // Empty message + }); + }).toThrow(/Invalid blockedOutputsMessaging: The field blockedOutputsMessaging is 0 characters long but must be at least 1 characters/); + }); + + test('validates blockedOutputsMessaging length - too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedOutputsMessaging: 'a'.repeat(501), // 501 characters + }); + }).toThrow(/Invalid blockedOutputsMessaging: The field blockedOutputsMessaging is 501 characters long but must be less than or equal to 500 characters/); + }); + + test('accepts valid blockedInputMessaging', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedInputMessaging: 'This is a valid custom message for blocked input.', + }); + }).not.toThrow(); + }); + + test('accepts valid blockedOutputsMessaging', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedOutputsMessaging: 'This is a valid custom message for blocked output.', + }); + }).not.toThrow(); + }); + + test('accepts minimum length messaging', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedInputMessaging: 'A', // 1 character + blockedOutputsMessaging: 'B', // 1 character + }); + }).not.toThrow(); + }); + + test('accepts maximum length messaging', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedInputMessaging: 'a'.repeat(500), // Exactly 500 characters + blockedOutputsMessaging: 'b'.repeat(500), // Exactly 500 characters + }); + }).not.toThrow(); + }); + + test('accepts undefined messaging (uses defaults)', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + // No messaging properties - should use defaults + }); + }).not.toThrow(); + }); + }); + + test('Custom Topic Validation - Examples Field Constraints', () => { + // Test that creating a custom topic with no examples throws an error + expect(() => { + bedrock.Topic.custom({ + name: 'TestTopic', + definition: 'A test topic definition', + examples: [], + }); + }).toThrow('examples field must contain at least 1 example'); + + // Test that creating a custom topic with more than 100 examples throws an error + expect(() => { + const tooManyExamples = Array.from({ length: 101 }, (_, i) => `Example ${i + 1}`); + bedrock.Topic.custom({ + name: 'TestTopic', + definition: 'A test topic definition', + examples: tooManyExamples, + }); + }).toThrow('examples field cannot contain more than 100 examples'); + + // Test that creating a custom topic with valid examples (1-100) works + expect(() => { + bedrock.Topic.custom({ + name: 'TestTopic', + definition: 'A test topic definition', + examples: ['Valid example'], + }); + }).not.toThrow(); + + // Test that creating a custom topic with exactly 100 examples works + expect(() => { + const exactly100Examples = Array.from({ length: 100 }, (_, i) => `Example ${i + 1}`); + bedrock.Topic.custom({ + name: 'TestTopic', + definition: 'A test topic definition', + examples: exactly100Examples, + }); + }).not.toThrow(); + }); + + test('Cross Region Config - With Configuration', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail with cross-region config', + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/my-profile', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail with cross-region config', + CrossRegionConfig: { + GuardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/my-profile', + }, + // ensure others are undefined + TopicPolicyConfig: Match.absent(), + ContextualGroundingPolicyConfig: Match.absent(), + ContentPolicyConfig: Match.absent(), + WordPolicyConfig: Match.absent(), + SensitiveInformationPolicyConfig: Match.absent(), + }); + }); + + test('Cross Region Config - Without Configuration', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail without cross-region config', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail without cross-region config', + // ensure CrossRegionConfig is absent when not provided + CrossRegionConfig: Match.absent(), + // ensure others are undefined + TopicPolicyConfig: Match.absent(), + ContextualGroundingPolicyConfig: Match.absent(), + ContentPolicyConfig: Match.absent(), + WordPolicyConfig: Match.absent(), + SensitiveInformationPolicyConfig: Match.absent(), + }); + }); + + test('Cross Region Config - With Other Filters', () => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail with cross-region config and other filters', + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-west-2:123456789012:guardrail-profile/another-profile', + }, + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Guardrail', { + Name: 'TestGuardrail', + Description: 'This is a test guardrail with cross-region config and other filters', + CrossRegionConfig: { + GuardrailProfileArn: 'arn:aws:bedrock:us-west-2:123456789012:guardrail-profile/another-profile', + }, + ContentPolicyConfig: { + FiltersConfig: [ + { + InputStrength: 'LOW', + OutputStrength: 'LOW', + Type: 'MISCONDUCT', + }, + ], + }, + TopicPolicyConfig: { + TopicsConfig: [ + { + Definition: + "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + Examples: [ + 'Can you suggest some good stocks to invest in right now?', + "What's the best way to save for retirement?", + 'Should I put my money in a high-risk investment?', + 'How can I maximize my returns on investments?', + 'Is it a good time to buy real estate?', + ], + Name: 'Financial_Advice', + Type: 'DENY', + }, + ], + }, + }); + }); + + test('Cross Region Config - Property Access', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail with cross-region config', + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/my-profile', + }, + }); + + // Test that the crossRegionConfig property is accessible + expect(guardrail.crossRegionConfig).toBeDefined(); + expect(guardrail.crossRegionConfig!.guardrailProfileArn).toBe('arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/my-profile'); + }); + + test('Cross Region Config - Property Access Without Config', () => { + const guardrail = new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail without cross-region config', + }); + + // Test that the crossRegionConfig property is undefined when not provided + expect(guardrail.crossRegionConfig).toBeUndefined(); + }); + + describe('Tier Configuration Validation', () => { + test('throws error when STANDARD content tier is used without cross-region config', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, + }); + }).toThrow(/Cross-region configuration is required when using STANDARD tier for content filters/); + }); + + test('throws error when STANDARD topic tier is used without cross-region config', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + topicsTierConfig: bedrock.TierConfig.STANDARD, + }); + }).toThrow(/Cross-region configuration is required when using STANDARD tier for topic filters/); + }); + + test('throws error when both STANDARD tiers are used without cross-region config', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, + topicsTierConfig: bedrock.TierConfig.STANDARD, + }); + }).toThrow(/Cross-region configuration is required when using STANDARD tier for content filters/); + }); + + test('allows STANDARD content tier with cross-region config', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFiltersTierConfig: bedrock.TierConfig.STANDARD, + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/my-profile', + }, + }); + }).not.toThrow(); + }); + + test('allows STANDARD topic tier with cross-region config', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + topicsTierConfig: bedrock.TierConfig.STANDARD, + crossRegionConfig: { + guardrailProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail-profile/my-profile', + }, + }); + }).not.toThrow(); + }); + + test('allows CLASSIC tiers without cross-region config', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFiltersTierConfig: bedrock.TierConfig.CLASSIC, + topicsTierConfig: bedrock.TierConfig.CLASSIC, + }); + }).not.toThrow(); + }); + + test('allows default tiers without cross-region config', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + // No tier config specified - defaults to CLASSIC + }); + }).not.toThrow(); + }); + }); + + describe('Validation Tests', () => { + describe('Content Filters Validation', () => { + test('validates content filter with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [{ + type: bedrock.ContentFilterType.SEXUAL, + inputStrength: bedrock.ContentFilterStrength.MEDIUM, + outputStrength: bedrock.ContentFilterStrength.HIGH, + }], + }); + }).not.toThrow(); + }); + + test('throws error for invalid content filter strength', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [{ + type: bedrock.ContentFilterType.SEXUAL, + inputStrength: 'INVALID' as any, + outputStrength: bedrock.ContentFilterStrength.HIGH, + }], + }); + }).toThrow(/inputStrength must be a valid ContentFilterStrength value/); + }); + + test('throws error for invalid modality type', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contentFilters: [{ + type: bedrock.ContentFilterType.SEXUAL, + inputStrength: bedrock.ContentFilterStrength.MEDIUM, + outputStrength: bedrock.ContentFilterStrength.HIGH, + inputModalities: ['INVALID' as any], + }], + }); + }).toThrow(/inputModalities\[0\] must be a valid ModalityType value/); + }); + }); + + describe('PII Filters Validation', () => { + test('validates PII filter with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [{ + type: bedrock.GeneralPIIType.EMAIL, + action: bedrock.GuardrailAction.BLOCK, + }], + }); + }).not.toThrow(); + }); + + test('throws error for missing required PII filter properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [{ + type: bedrock.GeneralPIIType.EMAIL, + // Missing action + } as any], + }); + }).toThrow(/action is required/); + }); + + test('throws error for invalid PII filter action', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + piiFilters: [{ + type: bedrock.GeneralPIIType.EMAIL, + action: 'INVALID' as any, + }], + }); + }).toThrow(/action must be a valid GuardrailAction value/); + }); + }); + + describe('Regex Filters Validation', () => { + test('validates regex filter with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [{ + name: 'test-regex', + pattern: 'test-pattern', + action: bedrock.GuardrailAction.BLOCK, + }], + }); + }).not.toThrow(); + }); + + test('throws error for regex filter with empty name', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [{ + name: '', + pattern: 'test-pattern', + action: bedrock.GuardrailAction.BLOCK, + }], + }); + }).toThrow(/name is 0 characters long but must be at least 1 characters/); + }); + + test('throws error for regex filter with name too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + regexFilters: [{ + name: 'a'.repeat(101), + pattern: 'test-pattern', + action: bedrock.GuardrailAction.BLOCK, + }], + }); + }).toThrow(/name is 101 characters long but must be less than or equal to 100 characters/); + }); + }); + + describe('Denied Topics Validation', () => { + test('validates denied topics with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + }); + }).not.toThrow(); + }); + + test('validates custom topic with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [bedrock.Topic.custom({ + name: 'test-topic', + definition: 'test definition', + examples: ['example 1', 'example 2'], + })], + }); + }).not.toThrow(); + }); + + test('throws error for custom topic with name too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [bedrock.Topic.custom({ + name: 'a'.repeat(101), + definition: 'test definition', + examples: ['example 1'], + })], + }); + }).toThrow(/name must be 100 characters or less/); + }); + + test('throws error for custom topic with definition too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [bedrock.Topic.custom({ + name: 'test-topic', + definition: 'a'.repeat(1001), + examples: ['example 1'], + })], + }); + }).toThrow(/definition must be 1000 characters or less/); + }); + + test('throws error for custom topic with too many examples', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [bedrock.Topic.custom({ + name: 'test-topic', + definition: 'test definition', + examples: Array(101).fill('example'), + })], + }); + }).toThrow(/examples field cannot contain more than 100 examples/); + }); + + test('throws error for custom topic with example too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + deniedTopics: [bedrock.Topic.custom({ + name: 'test-topic', + definition: 'test definition', + examples: ['a'.repeat(101)], + })], + }); + }).toThrow(/examples\[0\] must be 100 characters or less/); + }); + }); + + describe('Contextual Grounding Filters Validation', () => { + test('validates contextual grounding filter with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [{ + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.5, + }], + }); + }).not.toThrow(); + }); + + test('throws error for contextual grounding filter with threshold too low', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [{ + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: -0.1, + }], + }); + }).toThrow(/threshold must be between 0 and 0.99/); + }); + + test('throws error for contextual grounding filter with threshold too high', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [{ + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 1.0, + }], + }); + }).toThrow(/threshold must be between 0 and 0.99/); + }); + + test('throws error for invalid contextual grounding filter type', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + contextualGroundingFilters: [{ + type: 'INVALID' as any, + threshold: 0.5, + }], + }); + }).toThrow(/type must be a valid ContextualGroundingFilterType value/); + }); + }); + + describe('Word Filters Validation', () => { + test('validates word filter with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [{ + text: 'test-word', + }], + }); + }).not.toThrow(); + }); + + test('throws error for word filter with text too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [{ + text: 'a'.repeat(101), + }], + }); + }).toThrow(/text must be 100 characters or less/); + }); + + test('throws error for word filter with invalid action', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + wordFilters: [{ + text: 'test-word', + inputAction: 'INVALID' as any, + }], + }); + }).toThrow(/inputAction must be a valid GuardrailAction value/); + }); + }); + + describe('Managed Word List Filters Validation', () => { + test('validates managed word list filter with valid properties', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [{ + type: bedrock.ManagedWordFilterType.PROFANITY, + }], + }); + }).not.toThrow(); + }); + + test('throws error for managed word list filter with invalid type', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [{ + type: 'INVALID' as any, + }], + }); + }).toThrow(/type must be a valid ManagedWordFilterType value/); + }); + + test('throws error for managed word list filter with invalid action', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + managedWordListFilters: [{ + type: bedrock.ManagedWordFilterType.PROFANITY, + inputAction: 'INVALID' as any, + }], + }); + }).toThrow(/inputAction must be a valid GuardrailAction value/); + }); + }); + + describe('Messaging Validation', () => { + test('throws error for blocked input messaging too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedInputMessaging: 'a'.repeat(501), + }); + }).toThrow(/blockedInputMessaging is 501 characters long but must be less than or equal to 500 characters/); + }); + + test('throws error for blocked outputs messaging too long', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedOutputsMessaging: 'a'.repeat(501), + }); + }).toThrow(/blockedOutputsMessaging is 501 characters long but must be less than or equal to 500 characters/); + }); + + test('throws error for empty blocked input messaging', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedInputMessaging: '', + }); + }).toThrow(/blockedInputMessaging is 0 characters long but must be at least 1 characters/); + }); + + test('throws error for empty blocked outputs messaging', () => { + expect(() => { + new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + blockedOutputsMessaging: '', + }); + }).toThrow(/blockedOutputsMessaging is 0 characters long but must be at least 1 characters/); + }); + }); + }); +}); + +describe('Imported-Guardrail', () => { + let stack: core.Stack; + + beforeEach(() => { + const app = new App(); + stack = new core.Stack(app, 'TestStack2', { + env: { + account: '123456789012', + region: 'us-east-1', + }, + }); + }); + + test('Basic Import - ARN', () => { + const guardrail = bedrock.Guardrail.fromGuardrailAttributes(stack, 'TestGuardrail', { + guardrailArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail/oygh3o8g7rtl', + }); + + expect(guardrail.guardrailArn).toBe('arn:aws:bedrock:us-east-1:123456789012:guardrail/oygh3o8g7rtl'); + expect(guardrail.guardrailId).toBe('oygh3o8g7rtl'); + expect(guardrail.guardrailVersion).toBe('DRAFT'); + expect(guardrail.kmsKey).toBeUndefined(); + }); + + test('Basic Import - ARN + KMS + Version', () => { + const kmsKey = kms.Key.fromKeyArn( + stack, + 'importedKey', + 'arn:aws:kms:eu-central-1:123456789012:key/06484191-7d55-49fb-9be7-0baaf7fe8418', + ); + const guardrail = bedrock.Guardrail.fromGuardrailAttributes(stack, 'TestGuardrail', { + guardrailArn: 'arn:aws:bedrock:us-east-1:123456789012:guardrail/oygh3o8g7rtl', + guardrailVersion: '1', + kmsKey: kmsKey, + }); + + expect(guardrail.guardrailArn).toBe('arn:aws:bedrock:us-east-1:123456789012:guardrail/oygh3o8g7rtl'); + expect(guardrail.guardrailId).toBe('oygh3o8g7rtl'); + expect(guardrail.guardrailVersion).toBe('1'); + expect(guardrail.kmsKey!.keyArn).toBe( + 'arn:aws:kms:eu-central-1:123456789012:key/06484191-7d55-49fb-9be7-0baaf7fe8418', + ); + }); +}); diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/BedrockGuardrailDefaultTestDeployAssert40827DC2.assets.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/BedrockGuardrailDefaultTestDeployAssert40827DC2.assets.json new file mode 100644 index 0000000000000..9b443a9d50b7b --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/BedrockGuardrailDefaultTestDeployAssert40827DC2.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "BedrockGuardrailDefaultTestDeployAssert40827DC2 Template", + "source": { + "path": "BedrockGuardrailDefaultTestDeployAssert40827DC2.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-d8d86b35": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/BedrockGuardrailDefaultTestDeployAssert40827DC2.template.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/BedrockGuardrailDefaultTestDeployAssert40827DC2.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/BedrockGuardrailDefaultTestDeployAssert40827DC2.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/aws-cdk-bedrock-guardrail-1.assets.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/aws-cdk-bedrock-guardrail-1.assets.json new file mode 100644 index 0000000000000..77638dd5e17e4 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/aws-cdk-bedrock-guardrail-1.assets.json @@ -0,0 +1,20 @@ +{ + "version": "48.0.0", + "files": { + "2e459fe406678fc01e585bd34b66846670e381a667ca648aca78906b48d1fc8e": { + "displayName": "aws-cdk-bedrock-guardrail-1 Template", + "source": { + "path": "aws-cdk-bedrock-guardrail-1.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region-93e98892": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "2e459fe406678fc01e585bd34b66846670e381a667ca648aca78906b48d1fc8e.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/aws-cdk-bedrock-guardrail-1.template.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/aws-cdk-bedrock-guardrail-1.template.json new file mode 100644 index 0000000000000..49ea32a39aa61 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/aws-cdk-bedrock-guardrail-1.template.json @@ -0,0 +1,140 @@ +{ + "Resources": { + "TestGuardrailMyGuardrail0493303A": { + "Type": "AWS::Bedrock::Guardrail", + "Properties": { + "BlockedInputMessaging": "Sorry, your query violates our usage policy.", + "BlockedOutputsMessaging": "Sorry, I am unable to answer your question because of our usage policy.", + "ContentPolicyConfig": { + "ContentFiltersTierConfig": { + "TierName": "CLASSIC" + }, + "FiltersConfig": [ + { + "InputAction": "BLOCK", + "InputEnabled": true, + "InputStrength": "LOW", + "OutputAction": "BLOCK", + "OutputEnabled": true, + "OutputStrength": "LOW", + "Type": "MISCONDUCT" + } + ] + }, + "ContextualGroundingPolicyConfig": { + "FiltersConfig": [ + { + "Action": "BLOCK", + "Enabled": true, + "Threshold": 0.99, + "Type": "GROUNDING" + } + ] + }, + "Description": "This is a test guardrail", + "Name": "TestGuardrail", + "SensitiveInformationPolicyConfig": { + "PiiEntitiesConfig": [ + { + "Action": "ANONYMIZE", + "InputAction": "BLOCK", + "InputEnabled": true, + "OutputAction": "BLOCK", + "OutputEnabled": true, + "Type": "ADDRESS" + } + ], + "RegexesConfig": [ + { + "Action": "ANONYMIZE", + "Description": "This is a test regex filter", + "InputAction": "BLOCK", + "InputEnabled": true, + "Name": "TestRegexFilter", + "OutputAction": "BLOCK", + "OutputEnabled": true, + "Pattern": "/^[A-Z]{2}d{6}$/" + } + ] + }, + "TopicPolicyConfig": { + "TopicsConfig": [ + { + "Definition": "'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.", + "Examples": [ + "Can you suggest some good stocks to invest in right now?", + "What's the best way to save for retirement?", + "Should I put my money in a high-risk investment?", + "How can I maximize my returns on investments?", + "Is it a good time to buy real estate?" + ], + "InputAction": "BLOCK", + "InputEnabled": true, + "Name": "Financial_Advice", + "OutputAction": "BLOCK", + "OutputEnabled": true, + "Type": "DENY" + } + ], + "TopicsTierConfig": { + "TierName": "CLASSIC" + } + }, + "WordPolicyConfig": { + "ManagedWordListsConfig": [ + { + "InputAction": "BLOCK", + "InputEnabled": true, + "OutputAction": "NONE", + "OutputEnabled": true, + "Type": "PROFANITY" + } + ], + "WordsConfig": [ + { + "InputAction": "BLOCK", + "InputEnabled": true, + "OutputAction": "NONE", + "OutputEnabled": true, + "Text": "reggaeton" + } + ] + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/cdk.out b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/cdk.out new file mode 100644 index 0000000000000..523a9aac37cbf --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"48.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/integ.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/integ.json new file mode 100644 index 0000000000000..08d8bf3496707 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/integ.json @@ -0,0 +1,13 @@ +{ + "version": "48.0.0", + "testCases": { + "BedrockGuardrail/DefaultTest": { + "stacks": [ + "aws-cdk-bedrock-guardrail-1" + ], + "assertionStack": "BedrockGuardrail/DefaultTest/DeployAssert", + "assertionStackName": "BedrockGuardrailDefaultTestDeployAssert40827DC2" + } + }, + "minimumCliVersion": "2.1023.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/manifest.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/manifest.json new file mode 100644 index 0000000000000..2738fbf8abbb7 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/manifest.json @@ -0,0 +1,588 @@ +{ + "version": "48.0.0", + "artifacts": { + "aws-cdk-bedrock-guardrail-1.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-bedrock-guardrail-1.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-bedrock-guardrail-1": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-cdk-bedrock-guardrail-1.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/2e459fe406678fc01e585bd34b66846670e381a667ca648aca78906b48d1fc8e.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-bedrock-guardrail-1.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-bedrock-guardrail-1.assets" + ], + "metadata": { + "/aws-cdk-bedrock-guardrail-1/TestGuardrail/MyGuardrail": [ + { + "type": "aws:cdk:logicalId", + "data": "TestGuardrailMyGuardrail0493303A" + } + ], + "/aws-cdk-bedrock-guardrail-1/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-bedrock-guardrail-1/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-bedrock-guardrail-1" + }, + "BedrockGuardrailDefaultTestDeployAssert40827DC2.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "BedrockGuardrailDefaultTestDeployAssert40827DC2.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "BedrockGuardrailDefaultTestDeployAssert40827DC2": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "BedrockGuardrailDefaultTestDeployAssert40827DC2.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "BedrockGuardrailDefaultTestDeployAssert40827DC2.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "BedrockGuardrailDefaultTestDeployAssert40827DC2.assets" + ], + "metadata": { + "/BedrockGuardrail/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/BedrockGuardrail/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "BedrockGuardrail/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to by default create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + } + } + } + } + }, + "minimumCliVersion": "2.1023.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/tree.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/tree.json new file mode 100644 index 0000000000000..a9473419a87a4 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.js.snapshot/tree.json @@ -0,0 +1 @@ +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"aws-cdk-bedrock-guardrail-1":{"id":"aws-cdk-bedrock-guardrail-1","path":"aws-cdk-bedrock-guardrail-1","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"TestGuardrail":{"id":"TestGuardrail","path":"aws-cdk-bedrock-guardrail-1/TestGuardrail","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.Guardrail","version":"0.0.0","metadata":[]},"children":{"MyGuardrail":{"id":"MyGuardrail","path":"aws-cdk-bedrock-guardrail-1/TestGuardrail/MyGuardrail","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrock.CfnGuardrail","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Bedrock::Guardrail","aws:cdk:cloudformation:props":{"blockedInputMessaging":"Sorry, your query violates our usage policy.","blockedOutputsMessaging":"Sorry, I am unable to answer your question because of our usage policy.","contentPolicyConfig":{"filtersConfig":[{"type":"MISCONDUCT","inputStrength":"LOW","outputStrength":"LOW","inputAction":"BLOCK","inputEnabled":true,"outputAction":"BLOCK","outputEnabled":true}],"contentFiltersTierConfig":{"tierName":"CLASSIC"}},"contextualGroundingPolicyConfig":{"filtersConfig":[{"type":"GROUNDING","threshold":0.99,"action":"BLOCK","enabled":true}]},"description":"This is a test guardrail","name":"TestGuardrail","sensitiveInformationPolicyConfig":{"regexesConfig":[{"name":"TestRegexFilter","description":"This is a test regex filter","pattern":"/^[A-Z]{2}d{6}$/","action":"ANONYMIZE","inputAction":"BLOCK","inputEnabled":true,"outputAction":"BLOCK","outputEnabled":true}],"piiEntitiesConfig":[{"type":"ADDRESS","action":"ANONYMIZE","inputAction":"BLOCK","inputEnabled":true,"outputAction":"BLOCK","outputEnabled":true}]},"topicPolicyConfig":{"topicsConfig":[{"definition":"'Discussions that involve providing guidance, recommendations, or suggestions related to managing, investing, or handling finances, investments, or assets.","name":"Financial_Advice","examples":["Can you suggest some good stocks to invest in right now?","What's the best way to save for retirement?","Should I put my money in a high-risk investment?","How can I maximize my returns on investments?","Is it a good time to buy real estate?"],"type":"DENY","inputAction":"BLOCK","inputEnabled":true,"outputAction":"BLOCK","outputEnabled":true}],"topicsTierConfig":{"tierName":"CLASSIC"}},"wordPolicyConfig":{"wordsConfig":[{"text":"reggaeton","inputAction":"BLOCK","inputEnabled":true,"outputAction":"NONE","outputEnabled":true}],"managedWordListsConfig":[{"type":"PROFANITY","inputAction":"BLOCK","inputEnabled":true,"outputAction":"NONE","outputEnabled":true}]}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-cdk-bedrock-guardrail-1/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-cdk-bedrock-guardrail-1/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"BedrockGuardrail":{"id":"BedrockGuardrail","path":"BedrockGuardrail","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"BedrockGuardrail/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"BedrockGuardrail/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"BedrockGuardrail/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"BedrockGuardrail/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"BedrockGuardrail/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.ts new file mode 100644 index 0000000000000..a01faba0d1397 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/guardrails/integ.guardrails.ts @@ -0,0 +1,67 @@ +/* + * Integration test for Bedrock Guardrail construct + */ + +/// !cdk-integ aws-cdk-bedrock-guardrail-1 + +import * as cdk from 'aws-cdk-lib'; +import * as integ from '@aws-cdk/integ-tests-alpha'; +import * as bedrock from '../../../bedrock'; + +const app = new cdk.App(); + +const stack = new cdk.Stack(app, 'aws-cdk-bedrock-guardrail-1'); + +// Create a guardrail +new bedrock.Guardrail(stack, 'TestGuardrail', { + guardrailName: 'TestGuardrail', + description: 'This is a test guardrail', + deniedTopics: [bedrock.Topic.FINANCIAL_ADVICE], + contentFilters: [ + { + type: bedrock.ContentFilterType.MISCONDUCT, + inputStrength: bedrock.ContentFilterStrength.LOW, + outputStrength: bedrock.ContentFilterStrength.LOW, + }, + ], + contextualGroundingFilters: [ + { + type: bedrock.ContextualGroundingFilterType.GROUNDING, + threshold: 0.99, + }, + ], + piiFilters: [ + { + type: bedrock.GeneralPIIType.ADDRESS, + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + regexFilters: [ + { + name: 'TestRegexFilter', + description: 'This is a test regex filter', + pattern: '/^[A-Z]{2}d{6}$/', + action: bedrock.GuardrailAction.ANONYMIZE, + }, + ], + wordFilters: [ + { + text: 'reggaeton', + inputAction: bedrock.GuardrailAction.BLOCK, + outputAction: bedrock.GuardrailAction.NONE, + }, + ], + managedWordListFilters: [ + { + type: bedrock.ManagedWordFilterType.PROFANITY, + inputAction: bedrock.GuardrailAction.BLOCK, + outputAction: bedrock.GuardrailAction.NONE, + }, + ], +}); + +new integ.IntegTest(app, 'BedrockGuardrail', { + testCases: [stack], +}); + +app.synth();