From 62f7cea916fbf2c83cd606b7ebd6fa6ebecfdf18 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Wed, 23 Jul 2025 12:29:10 -0400 Subject: [PATCH 01/10] feat(inferenceprofiles): add inference and cross-region inference profiles --- packages/@aws-cdk/aws-bedrock-alpha/README.md | 190 ++++ .../aws-bedrock-alpha/bedrock/index.ts | 5 + .../application-inference-profile.ts | 290 ++++++ .../cross-region-inference-profile.ts | 214 +++++ .../bedrock/inference-profiles/index.ts | 32 + .../inference-profiles/inference-profile.ts | 92 ++ .../inference-profiles/prompt-router.ts | 210 +++++ .../application-inference-profile.test.ts | 329 +++++++ .../cross-region-inference-profile.test.ts | 208 +++++ ...efaultTestDeployAssert4CC2EFAB.assets.json | 20 + ...aultTestDeployAssert4CC2EFAB.template.json | 36 + ...drock-inference-profiles-integ.assets.json | 21 + ...ock-inference-profiles-integ.template.json | 494 ++++++++++ .../cdk.out | 1 + .../integ.json | 20 + .../manifest.json | 872 ++++++++++++++++++ .../tree.json | 1 + .../integ.inference-profiles.ts | 117 +++ .../inference-profiles/prompt-router.test.ts | 223 +++++ 19 files changed, 3375 insertions(+) create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/cross-region-inference-profile.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/cross-region-inference-profile.test.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.assets.json create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.template.json create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.assets.json create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.template.json create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.ts create mode 100644 packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/prompt-router.test.ts diff --git a/packages/@aws-cdk/aws-bedrock-alpha/README.md b/packages/@aws-cdk/aws-bedrock-alpha/README.md index 6f5ec6bc2c75d..b4d336bdfcf14 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/README.md +++ b/packages/@aws-cdk/aws-bedrock-alpha/README.md @@ -43,6 +43,12 @@ This construct library facilitates the deployment of Bedrock Agents, enabling yo - [Prompt Properties](#prompt-properties) - [Prompt Version](#prompt-version) - [Import Methods](#import-methods) +- [Inference Profiles](#inference-profiles) + - [Using Inference Profiles](#using-inference-profiles) + - [Types of Inference Profiles](#types-of-inference-profiles) + - [Prompt Routers](#prompt-routers) + - [Inference Profile Permissions](#inference-profile-permissions) + - [Inference Profiles Import Methods](#inference-profiles-import-methods) ## Agents @@ -807,3 +813,187 @@ const importedPrompt = bedrock.Prompt.fromPromptAttributes(this, 'ImportedPrompt promptVersion: '1', // optional, defaults to 'DRAFT' }); ``` + +## Inference Profiles + +Amazon Bedrock Inference Profiles provide a way to manage and optimize inference configurations for your foundation models. They allow you to define reusable configurations that can be applied across different prompts and agents. + +### Using Inference Profiles + +Inference profiles can be used with prompts and agents to maintain consistent inference configurations across your application. + +#### With Agents + +```ts fixture=default +// Create a cross-region inference profile +const crossRegionProfile = bedrock.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrock.CrossRegionInferenceProfileRegion.US, + model: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, +}); + +// Use the cross-region profile with an agent +const agent = new bedrock.Agent(this, 'Agent', { + foundationModel: crossRegionProfile, + instruction: 'You are a helpful and friendly agent that answers questions about agriculture.', +}); +``` + +#### With Prompts + +```ts fixture=default +// Create a prompt router for intelligent model selection +const promptRouter = bedrock.PromptRouter.fromDefaultId( + bedrock.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1' +); + +// Use the prompt router with a prompt variant +const variant = bedrock.PromptVariant.text({ + variantName: 'variant1', + promptText: 'What is the capital of France?', + model: promptRouter, +}); + +new bedrock.Prompt(this, 'Prompt', { + promptName: 'prompt-router-test', + variants: [variant], +}); +``` + +### Types of Inference Profiles + +Amazon Bedrock offers two types of inference profiles: + +#### Application Inference Profiles + +Application inference profiles are user-defined profiles that help you track costs and model usage. They can be created for a single region or for multiple regions using a cross-region inference profile. + +##### Single Region Application Profile + +```ts fixture=default +// Create an application inference profile for one Region +const appProfile = new bedrock.ApplicationInferenceProfile(this, 'MyApplicationProfile', { + applicationInferenceProfileName: 'claude-3-sonnet-v1', + modelSource: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_SONNET_V1_0, + description: 'Application profile for cost tracking', + tags: { + Environment: 'Production', + }, +}); +``` + +##### Multi-Region Application Profile + +```ts fixture=default +// Create a cross-region inference profile +const crossRegionProfile = bedrock.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrock.CrossRegionInferenceProfileRegion.US, + model: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V2_0, +}); + +// Create an application inference profile across regions +const appProfile = new bedrock.ApplicationInferenceProfile(this, 'MyMultiRegionProfile', { + applicationInferenceProfileName: 'claude-35-sonnet-v2-multi-region', + modelSource: crossRegionProfile, + description: 'Multi-region application profile for cost tracking', +}); +``` + +#### System Defined Inference Profiles + +Cross-region inference enables you to seamlessly manage unplanned traffic bursts by utilizing compute across different AWS Regions. With cross-region inference, you can distribute traffic across multiple AWS Regions, enabling higher throughput and enhanced resilience during periods of peak demands. + +Before using a CrossRegionInferenceProfile, ensure that you have access to the models and regions defined in the inference profiles. For instance, if you use the system defined inference profile "us.anthropic.claude-3-5-sonnet-20241022-v2:0", inference requests will be routed to US East (Virginia) us-east-1, US East (Ohio) us-east-2 and US West (Oregon) us-west-2. Thus, you need to have model access enabled in those regions for the model anthropic.claude-3-5-sonnet-20241022-v2:0. + +##### System Defined Profile Configuration + +```ts fixture=default +const crossRegionProfile = bedrock.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrock.CrossRegionInferenceProfileRegion.US, + model: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V2_0, +}); +``` + +### Prompt Routers + +Amazon Bedrock intelligent prompt routing provides a single serverless endpoint for efficiently routing requests between different foundational models within the same model family. It can help you optimize for response quality and cost. They offer a comprehensive solution for managing multiple AI models through a single serverless endpoint, simplifying the process for you. Intelligent prompt routing predicts the performance of each model for each request, and dynamically routes each request to the model that it predicts is most likely to give the desired response at the lowest cost. + +#### Default and Custom Prompt Routers + +```ts fixture=default +// Use a default prompt router +const variant = bedrock.PromptVariant.text({ + variantName: 'variant1', + promptText: 'What is the capital of France?', + model: bedrock.PromptRouter.fromDefaultId( + bedrock.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1' + ), +}); + +new bedrock.Prompt(this, 'Prompt', { + promptName: 'prompt-router-test', + variants: [variant], +}); +``` + +### Inference Profile Permissions + +Use the `grantProfileUsage` method to grant appropriate permissions to resources that need to use the inference profile. + +#### Granting Profile Usage Permissions + +```ts fixture=default +// Create an application inference profile +const profile = new bedrock.ApplicationInferenceProfile(this, 'MyProfile', { + applicationInferenceProfileName: 'my-profile', + modelSource: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, +}); + +// Create a Lambda function +const lambdaFunction = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.PYTHON_3_11, + handler: 'index.handler', + code: lambda.Code.fromInline('def handler(event, context): return "Hello"'), +}); + +// Grant the Lambda function permission to use the inference profile +profile.grantProfileUsage(lambdaFunction); + +// Use a system defined inference profile +const crossRegionProfile = bedrock.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrock.CrossRegionInferenceProfileRegion.US, + model: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, +}); + +// Grant permissions to use the cross-region inference profile +crossRegionProfile.grantProfileUsage(lambdaFunction); +``` + +The `grantProfileUsage` method adds the necessary IAM permissions to the resource, allowing it to use the inference profile. This includes permissions to call `bedrock:GetInferenceProfile` and `bedrock:ListInferenceProfiles` actions on the inference profile resource. + +### Inference Profiles Import Methods + +You can import existing application inference profiles using the following methods: + +```ts fixture=default +// Import an inference profile through attributes +const importedProfile = bedrock.ApplicationInferenceProfile.fromApplicationInferenceProfileAttributes( + this, + 'ImportedProfile', + { + inferenceProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/my-profile-id', + inferenceProfileIdentifier: 'my-profile-id', + } +); + +// Import a Cfn L1 construct created application inference profile +const cfnProfile = new bedrock.CfnApplicationInferenceProfile(this, 'CfnProfile', { + inferenceProfileName: 'mytest', + modelSource: { + copyFrom: 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0', + }, +}); + +const importedFromCfn = bedrock.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); +``` diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts index 3c0419f36bf38..de3ea73ce401c 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts @@ -27,6 +27,11 @@ export * from './prompts/prompt-inference-configuration'; export * from './prompts/prompt-template-configuration'; export * from './prompts/prompt-genai-resource'; +// =================================== +// Inference Profiles +// =================================== +export * from './inference-profiles'; + // =================================== // Models // =================================== diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts new file mode 100644 index 0000000000000..333114fddfe29 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts @@ -0,0 +1,290 @@ +import { Arn, ArnFormat, ValidationError } from 'aws-cdk-lib'; +import * as bedrock from 'aws-cdk-lib/aws-bedrock'; +import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam'; +import { Construct } from 'constructs'; +import { IInferenceProfile, InferenceProfileBase, InferenceProfileType } from './inference-profile'; +import { IBedrockInvokable } from '../models'; + +/****************************************************************************** + * PROPS FOR NEW CONSTRUCT + *****************************************************************************/ +/** + * Properties for creating an Application Inference Profile. + */ +export interface ApplicationInferenceProfileProps { + /** + * The name of the application inference profile. + * This name will be used to identify the inference profile in the AWS console and APIs. + * If not provided, a name will be generated. + * + * @default - A name will be generated by CloudFormation + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-bedrock-applicationinferenceprofile.html#cfn-bedrock-applicationinferenceprofile-inferenceprofilename + */ + readonly applicationInferenceProfileName: string; + + /** + * Description of the inference profile. + * Provides additional context about the purpose and usage of this inference profile. + * + * @default - No description is provided + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-bedrock-applicationinferenceprofile.html#cfn-bedrock-applicationinferenceprofile-description + */ + readonly description?: string; + + /** + * The model source for this inference profile. + * + * To create an application inference profile for one Region, specify a foundation model. + * Usage and costs for requests made to that Region with that model will be tracked. + * + * To create an application inference profile for multiple Regions, + * specify a cross region (system-defined) inference profile. + * The inference profile will route requests to the Regions defined in + * the cross region (system-defined) inference profile that you choose. + * Usage and costs for requests made to the Regions in the inference profile will be tracked. + */ + readonly modelSource: IBedrockInvokable; + /** + * A list of tags associated with the inference profile. + * Tags help you organize and categorize your AWS resources. + * + * @default - No tags are applied + */ + readonly tags?: { [key: string]: string }; +} + +/****************************************************************************** + * ATTRS FOR IMPORTED CONSTRUCT + *****************************************************************************/ +/** + * Attributes for specifying an imported Application Inference Profile. + */ +export interface ApplicationInferenceProfileAttributes { + /** + * The ARN of the application inference profile. + * @attribute + */ + readonly inferenceProfileArn: string; + + /** + * The ID or Amazon Resource Name (ARN) of the inference profile. + * This can be either the profile ID or the full ARN. + * @attribute + */ + readonly inferenceProfileIdentifier: string; +} + +/****************************************************************************** + * NEW CONSTRUCT DEFINITION + *****************************************************************************/ +/** + * Class to create an Application Inference Profile with CDK. + * These are inference profiles created by users (user defined). + * This helps to track costs and model usage. + * + * Application inference profiles are user-defined profiles that help you track costs and model usage. + * They can be created for a single region or for multiple regions using a cross-region inference profile. + * + * @cloudformationResource AWS::Bedrock::ApplicationInferenceProfile + * @see https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html + */ +export class ApplicationInferenceProfile extends InferenceProfileBase implements IBedrockInvokable { + /** + * Import an Application Inference Profile given its attributes. + * + * @param scope - The construct scope + * @param id - Identifier of the construct + * @param attrs - Attributes of the existing application inference profile + * @returns An IInferenceProfile reference to the existing application inference profile + */ + public static fromApplicationInferenceProfileAttributes( + scope: Construct, + id: string, + attrs: ApplicationInferenceProfileAttributes, + ): IInferenceProfile { + class Import extends InferenceProfileBase { + public readonly inferenceProfileArn = attrs.inferenceProfileArn; + public readonly inferenceProfileId = Arn.split(attrs.inferenceProfileArn, ArnFormat.SLASH_RESOURCE_NAME) + .resourceName!; + public readonly type = InferenceProfileType.APPLICATION; + } + + return new Import(scope, id); + } + + /** + * Import a low-level L1 Cfn Application Inference Profile. + * + * @param cfnApplicationInferenceProfile - The L1 CfnApplicationInferenceProfile to import + * @returns An IInferenceProfile reference to the imported application inference profile + */ + public static fromCfnApplicationInferenceProfile( + cfnApplicationInferenceProfile: bedrock.CfnApplicationInferenceProfile, + ): IInferenceProfile { + return new (class extends InferenceProfileBase { + public readonly inferenceProfileArn = cfnApplicationInferenceProfile.attrInferenceProfileArn; + public readonly inferenceProfileId = cfnApplicationInferenceProfile.attrInferenceProfileId; + public readonly type = InferenceProfileType.APPLICATION; + })(cfnApplicationInferenceProfile, '@FromCfnApplicationInferenceProfile'); + } + + // ------------------------------------------------------ + // Base attributes + // ------------------------------------------------------ + /** + * The name of the application inference profile. + */ + public readonly inferenceProfileName: string; + + /** + * The ARN of the application inference profile. + * @attribute + */ + public readonly inferenceProfileArn: string; + + /** + * The unique identifier of the application inference profile. + * @attribute + */ + public readonly inferenceProfileId: string; + + /** + * The underlying model/cross-region model used by the application inference profile. + */ + public readonly inferenceProfileModel: IBedrockInvokable; + + /** + * The status of the application inference profile. ACTIVE means that the inference profile is ready to be used. + * @attribute + */ + public readonly status: string; + + /** + * The type of the inference profile. Always APPLICATION for application inference profiles. + */ + public readonly type: InferenceProfileType; + + /** + * Time Stamp for Application Inference Profile creation. + * @attribute + */ + public readonly createdAt: string; + + /** + * Time Stamp for Application Inference Profile update. + * @attribute + */ + public readonly updatedAt: string; + + /** + * The ARN used for invoking this inference profile. + * This equals to the inferenceProfileArn property, useful for implementing IBedrockInvokable interface. + */ + public readonly invokableArn: string; + + // ------------------------------------------------------ + // Internal Only + // ------------------------------------------------------ + /** + * Instance of CfnApplicationInferenceProfile. + * @internal + */ + private readonly __resource: bedrock.CfnApplicationInferenceProfile; + + // ------------------------------------------------------ + // CONSTRUCTOR + // ------------------------------------------------------ + constructor(scope: Construct, id: string, props: ApplicationInferenceProfileProps) { + super(scope, id); + + // ------------------------------------------------------ + // Validate props + // ------------------------------------------------------ + if (!props.applicationInferenceProfileName || props.applicationInferenceProfileName.trim() === '') { + throw new ValidationError('applicationInferenceProfileName is required and cannot be empty', this); + } + + if (props.applicationInferenceProfileName.length > 64) { + throw new ValidationError('applicationInferenceProfileName cannot exceed 64 characters', this); + } + + if (!props.modelSource) { + throw new ValidationError('modelSource is required', this); + } + + if (props.description !== undefined && props.description.length > 200) { + throw new ValidationError('description cannot exceed 200 characters', this); + } + + // ------------------------------------------------------ + // Set properties + // ------------------------------------------------------ + this.inferenceProfileModel = props.modelSource; + this.type = InferenceProfileType.APPLICATION; + + // ------------------------------------------------------ + // L1 instantiation + // ------------------------------------------------------ + this.__resource = new bedrock.CfnApplicationInferenceProfile(this, 'Resource', { + description: props.description, + inferenceProfileName: props.applicationInferenceProfileName, + modelSource: { + copyFrom: props.modelSource.invokableArn, + }, + tags: props.tags ? Object.entries(props.tags).map(([key, value]) => ({ key, value })) : undefined, + }); + + // ------------------------------------------------------ + // Build attributes + // ------------------------------------------------------ + this.inferenceProfileArn = this.__resource.attrInferenceProfileArn; + this.inferenceProfileId = this.__resource.attrInferenceProfileId; + this.inferenceProfileName = this.__resource.inferenceProfileName!; + this.status = this.__resource.attrStatus; + this.createdAt = this.__resource.attrCreatedAt; + this.updatedAt = this.__resource.attrUpdatedAt; + this.invokableArn = this.inferenceProfileArn; + } + + // ------------------------------------------------------ + // METHODS + // ------------------------------------------------------ + + /** + * Gives the appropriate policies to invoke and use the application inference profile. + * This method ensures the appropriate permissions are given to use either the inference profile + * or the underlying foundation model/cross-region profile. + * + * @param grantee - The IAM principal to grant permissions to + * @returns An IAM Grant object representing the granted permissions + */ + public grantInvoke(grantee: IGrantable): Grant { + // This method ensures the appropriate permissions are given + // to use either the inference profile or the vanilla foundation model + this.inferenceProfileModel.grantInvoke(grantee); + + // Plus we add permissions to now invoke the application inference profile itself + return this.grantProfileUsage(grantee); + } + + /** + * Grants appropriate permissions to use the application inference profile (AIP). + * This method adds the necessary IAM permissions to allow the grantee to: + * - Get inference profile details (bedrock:GetInferenceProfile) + * - Invoke the model through the inference profile (bedrock:InvokeModel) + * + * Note: This does not grant permissions to use the underlying model/cross-region profile in the AIP. + * For comprehensive permissions, use grantInvoke() instead. + * + * @param grantee - The IAM principal to grant permissions to + * @returns An IAM Grant object representing the granted permissions + */ + public grantProfileUsage(grantee: IGrantable): Grant { + return Grant.addToPrincipal({ + grantee: grantee, + actions: ['bedrock:GetInferenceProfile', 'bedrock:InvokeModel'], + resourceArns: [this.inferenceProfileArn], + scope: this, + }); + } +} diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/cross-region-inference-profile.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/cross-region-inference-profile.ts new file mode 100644 index 0000000000000..2a587050629e0 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/cross-region-inference-profile.ts @@ -0,0 +1,214 @@ +import { Arn, ArnFormat, Aws } from 'aws-cdk-lib'; +import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam'; +import { BedrockFoundationModel, IBedrockInvokable } from '../models'; +import { IInferenceProfile, InferenceProfileType } from './inference-profile'; + +/** + * Error thrown when cross-region inference profile validation fails. + */ +class CrossRegionInferenceProfileError extends Error { + constructor(message: string) { + super(message); + this.name = 'CrossRegionInferenceProfileError'; + } +} + +/** + * Geographic regions supported for cross-region inference profiles. + * These regions help distribute traffic across multiple AWS regions for better + * throughput and resilience during peak demands. + */ +export enum CrossRegionInferenceProfileRegion { + /** + * Cross-region Inference Identifier for the European area. + * According to the model chosen, this might include: + * - Frankfurt (`eu-central-1`) + * - Ireland (`eu-west-1`) + * - Paris (`eu-west-3`) + */ + EU = 'eu', + /** + * Cross-region Inference Identifier for the United States area. + * According to the model chosen, this might include: + * - N. Virginia (`us-east-1`) + * - Oregon (`us-west-2`) + * - Ohio (`us-east-2`) + */ + US = 'us', + /** + * Cross-region Inference Identifier for the Asia-Pacific area. + * According to the model chosen, this might include: + * - Tokyo (`ap-northeast-1`) + * - Seoul (`ap-northeast-2`) + * - Mumbai (`ap-south-1`) + * - Singapore (`ap-southeast-1`) + * - Sydney (`ap-southeast-2`) + */ + APAC = 'apac', +} + +/** + * Mapping of AWS regions to their corresponding geographic areas for cross-region inference. + * This mapping is used to determine which cross-region inference profile to use based on the current region. + */ +export const REGION_TO_GEO_AREA: { [key: string]: CrossRegionInferenceProfileRegion } = { + // US Regions + 'us-east-1': CrossRegionInferenceProfileRegion.US, // N. Virginia + 'us-east-2': CrossRegionInferenceProfileRegion.US, // Ohio + 'us-west-2': CrossRegionInferenceProfileRegion.US, // Oregon + + // EU Regions + 'eu-central-1': CrossRegionInferenceProfileRegion.EU, // Frankfurt + 'eu-west-1': CrossRegionInferenceProfileRegion.EU, // Ireland + 'eu-west-3': CrossRegionInferenceProfileRegion.EU, // Paris + + // APAC Regions + 'ap-northeast-1': CrossRegionInferenceProfileRegion.APAC, // Tokyo + 'ap-northeast-2': CrossRegionInferenceProfileRegion.APAC, // Seoul + 'ap-south-1': CrossRegionInferenceProfileRegion.APAC, // Mumbai + 'ap-southeast-1': CrossRegionInferenceProfileRegion.APAC, // Singapore + 'ap-southeast-2': CrossRegionInferenceProfileRegion.APAC, // Sydney +}; + +/****************************************************************************** + * PROPS FOR NEW CONSTRUCT + *****************************************************************************/ +/** + * Properties for creating a Cross-Region Inference Profile. + */ +export interface CrossRegionInferenceProfileProps { + /** + * The geographic region where the traffic is going to be distributed. Routing + * factors in user traffic, demand and utilization of resources. + */ + readonly geoRegion: CrossRegionInferenceProfileRegion; + /** + * A foundation model supporting cross-region inference. + * The model must have cross-region support enabled. + * @see https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference-support.html + */ + readonly model: BedrockFoundationModel; +} + +/****************************************************************************** + * NEW CONSTRUCT DEFINITION + *****************************************************************************/ +/** + * Cross-region inference enables you to seamlessly manage unplanned traffic + * bursts by utilizing compute across different AWS Regions. With cross-region + * inference, you can distribute traffic across multiple AWS Regions, enabling + * higher throughput and enhanced resilience during periods of peak demands. + * + * This construct represents a system-defined inference profile that routes + * requests across multiple regions based on availability and demand. + * + * @see https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html + */ +export class CrossRegionInferenceProfile implements IBedrockInvokable, IInferenceProfile { + /** + * Creates a Cross-Region Inference Profile from the provided configuration. + * + * @param config - Configuration for the cross-region inference profile + * @returns A new CrossRegionInferenceProfile instance + * @throws ValidationError if the model doesn't support cross-region inference + */ + public static fromConfig(config: CrossRegionInferenceProfileProps): CrossRegionInferenceProfile { + return new CrossRegionInferenceProfile(config); + } + + /** + * The unique identifier of the inference profile. + * Format: {geoRegion}.{modelId} + */ + public readonly inferenceProfileId: string; + + /** + * The ARN of the inference profile. + * @attribute + */ + public readonly inferenceProfileArn: string; + + /** + * The type of inference profile. Always SYSTEM_DEFINED for cross-region profiles. + */ + public readonly type: InferenceProfileType; + + /** + * The underlying foundation model supporting cross-region inference. + */ + public readonly inferenceProfileModel: BedrockFoundationModel; + + /** + * The ARN used for invoking this inference profile. + * This equals to the inferenceProfileArn property, useful for implementing IBedrockInvokable interface. + */ + public readonly invokableArn: string; + + private constructor(props: CrossRegionInferenceProfileProps) { + // Validate required properties + if (!props.geoRegion) { + throw new CrossRegionInferenceProfileError('geoRegion is required'); + } + + if (!props.model) { + throw new CrossRegionInferenceProfileError('model is required'); + } + + // Validate that the model supports cross-region inference + if (!props.model.supportsCrossRegion) { + throw new CrossRegionInferenceProfileError(`Model ${props.model.modelId} does not support cross-region inference`); + } + + this.type = InferenceProfileType.SYSTEM_DEFINED; + this.inferenceProfileModel = props.model; + this.inferenceProfileId = `${props.geoRegion}.${props.model.modelId}`; + this.inferenceProfileArn = Arn.format({ + partition: Aws.PARTITION, + service: 'bedrock', + account: Aws.ACCOUNT_ID, + region: Aws.REGION, + resource: 'inference-profile', + resourceName: this.inferenceProfileId, + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, + }); + this.invokableArn = this.inferenceProfileArn; + } + + /** + * Gives the appropriate policies to invoke and use the Foundation Model. + * For cross-region inference profiles, this method grants permissions to: + * - Invoke the model in all regions where the inference profile can route requests + * - Use the inference profile itself + * + * @param grantee - The IAM principal to grant permissions to + * @returns An IAM Grant object representing the granted permissions + */ + public grantInvoke(grantee: IGrantable): Grant { + // For cross-region inference profiles, we need to provide permissions to invoke the model in all regions + // where the inference profile can route requests + this.inferenceProfileModel.grantInvokeAllRegions(grantee); + + // And we need to provide permissions to invoke the inference profile itself + return this.grantProfileUsage(grantee); + } + + /** + * Grants appropriate permissions to use the cross-region inference profile. + * This method adds the necessary IAM permissions to allow the grantee to: + * - Get inference profile details (bedrock:GetInferenceProfile) + * - Invoke the model through the inference profile (bedrock:InvokeModel*) + * + * Note: This does not grant permissions to use the underlying model directly. + * For comprehensive permissions, use grantInvoke() instead. + * + * @param grantee - The IAM principal to grant permissions to + * @returns An IAM Grant object representing the granted permissions + */ + public grantProfileUsage(grantee: IGrantable): Grant { + return Grant.addToPrincipal({ + grantee: grantee, + actions: ['bedrock:GetInferenceProfile', 'bedrock:InvokeModel*'], + resourceArns: [this.inferenceProfileArn], + }); + } +} diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts new file mode 100644 index 0000000000000..cbaf69f4338d0 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts @@ -0,0 +1,32 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// =================================== +// Common Interfaces and Base Classes +// =================================== +export * from './inference-profile'; + +// =================================== +// Application Inference Profiles +// =================================== +export * from './application-inference-profile'; + +// =================================== +// Cross-Region Inference Profiles +// =================================== +export * from './cross-region-inference-profile'; + +// =================================== +// Prompt Routers +// =================================== +export * from './prompt-router'; diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts new file mode 100644 index 0000000000000..ef56d2fd71e83 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts @@ -0,0 +1,92 @@ +import { Resource } from 'aws-cdk-lib'; +import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam'; + +/** + * These are the values used by the API when using aws bedrock get-inference-profile --inference-profile-identifier XXXXXXX + */ +export enum InferenceProfileType { + /** + * An inference profile that is created by AWS. These are profiles such as cross-region + * which help you distribute traffic across a geographic region. + */ + SYSTEM_DEFINED = 'SYSTEM_DEFINED', + /** + * An inference profile that is user-created. These are profiles that help + * you track costs or metrics. + */ + APPLICATION = 'APPLICATION', +} + +/****************************************************************************** + * COMMON + *****************************************************************************/ +/** + * Represents an Inference Profile, either created with CDK or imported. + */ +export interface IInferenceProfile { + /** + * The ARN of the inference profile. + * @attribute + */ + readonly inferenceProfileArn: string; + /** + * The unique identifier of the inference profile. + * @attribute + */ + readonly inferenceProfileId: string; + /** + * The type of inference profile. + */ + readonly type: InferenceProfileType; + + /** + * Grants appropriate permissions to use the inference profile. + * This includes permissions to call bedrock:GetInferenceProfile and bedrock:ListInferenceProfiles. + * + * @param grantee - The IAM principal to grant permissions to + * @returns An IAM Grant object representing the granted permissions + */ + grantProfileUsage(grantee: IGrantable): Grant; +} + +/** + * Abstract base class for an Inference Profile. + * Contains methods and attributes valid for Inference Profiles either created with CDK or imported. + */ +export abstract class InferenceProfileBase extends Resource implements IInferenceProfile { + /** + * The ARN of the inference profile. + * @attribute + */ + public abstract readonly inferenceProfileArn: string; + /** + * The unique identifier of the inference profile. + * @attribute + */ + public abstract readonly inferenceProfileId: string; + /** + * The type of inference profile (SYSTEM_DEFINED or APPLICATION). + */ + public abstract readonly type: InferenceProfileType; + + /** + * Grants appropriate permissions to use the inference profile. + * This method adds the necessary IAM permissions to allow the grantee to: + * - Get inference profile details (bedrock:GetInferenceProfile) + * - List available inference profiles (bedrock:ListInferenceProfiles) + * + * Note: This does not grant permissions to use the underlying model in the profile. + * For model invocation permissions, use the model's grantInvoke method separately. + * + * @param grantee - The IAM principal to grant permissions to + * @returns An IAM Grant object representing the granted permissions + */ + public grantProfileUsage(grantee: IGrantable): Grant { + return Grant.addToPrincipal({ + grantee: grantee, + actions: ['bedrock:GetInferenceProfile', 'bedrock:ListInferenceProfiles'], + resourceArns: [this.inferenceProfileArn], + scope: this, + }); + } +} diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts new file mode 100644 index 0000000000000..53109a83218a2 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts @@ -0,0 +1,210 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import { Arn, ArnFormat, Aws } from 'aws-cdk-lib'; +import { IGrantable, Grant } from 'aws-cdk-lib/aws-iam'; +import { BedrockFoundationModel, IBedrockInvokable } from '../models'; +import { + CrossRegionInferenceProfile, + REGION_TO_GEO_AREA, +} from './cross-region-inference-profile'; + +/** + * Represents a Prompt Router, which provides intelligent routing between different models. + */ +export interface IPromptRouter { + /** + * The ARN of the prompt router. + * @attribute + */ + readonly promptRouterArn: string; + + /** + * The ID of the prompt router. + * @attribute + */ + readonly promptRouterId: string; + + /** + * The foundation models / profiles this router will route to. + */ + readonly routingEndpoints: IBedrockInvokable[]; +} + +/** + * Properties for configuring a Prompt Router. + */ +export interface PromptRouterProps { + /** + * Prompt Router ID that identifies the routing configuration. + */ + readonly promptRouterId: string; + + /** + * The foundation models this router will route to. + * The router will intelligently select between these models based on the request. + */ + readonly routingModels: BedrockFoundationModel[]; +} + +/** + * Represents identifiers for default prompt routers in Bedrock. + * These are pre-configured routers provided by AWS that route between + * different models in the same family for optimal performance and cost. + */ +export class DefaultPromptRouterIdentifier { + /** + * Anthropic Claude V1 router configuration. + * Routes between Claude Haiku and Claude 3.5 Sonnet models for optimal + * balance between performance and cost. + */ + public static readonly ANTHROPIC_CLAUDE_V1 = new DefaultPromptRouterIdentifier({ + promptRouterId: 'anthropic.claude:1', + routingModels: [ + BedrockFoundationModel.ANTHROPIC_CLAUDE_HAIKU_V1_0, + BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + ], + }); + + /** + * Meta Llama 3.1 router configuration. + * Routes between different sizes of Llama 3.1 models (8B and 70B) + * for optimal performance based on request complexity. + */ + public static readonly META_LLAMA_3_1 = new DefaultPromptRouterIdentifier({ + promptRouterId: 'meta.llama:1', + routingModels: [ + BedrockFoundationModel.META_LLAMA_3_1_8B_INSTRUCT_V1, + BedrockFoundationModel.META_LLAMA_3_1_70B_INSTRUCT_V1, + ], + }); + + /** + * The unique identifier for this prompt router. + */ + public readonly promptRouterId: string; + + /** + * The foundation models that this router can route between. + */ + public readonly routingModels: BedrockFoundationModel[]; + + private constructor(props: PromptRouterProps) { + this.promptRouterId = props.promptRouterId; + this.routingModels = props.routingModels; + } +} + +/** + * Amazon Bedrock intelligent prompt routing provides a single serverless endpoint + * for efficiently routing requests between different foundational models within + * the same model family. It can help you optimize for response quality and cost. + * + * Intelligent prompt routing predicts the performance of each model for each request, + * and dynamically routes each request to the model that it predicts is most likely + * to give the desired response at the lowest cost. + * + * @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-routing.html + */ +export class PromptRouter implements IBedrockInvokable, IPromptRouter { + /** + * Creates a PromptRouter from a default router identifier. + * + * @param defaultRouter - The default router configuration to use + * @param region - The AWS region where the router will be used + * @returns A new PromptRouter instance configured with the default settings + */ + public static fromDefaultId(defaultRouter: DefaultPromptRouterIdentifier, region: string): PromptRouter { + return new PromptRouter(defaultRouter, region); + } + + /** + * The ARN of the prompt router. + * @attribute + */ + public readonly promptRouterArn: string; + + /** + * The ID of the prompt router. + * @attribute + */ + public readonly promptRouterId: string; + + /** + * The ARN used for invoking this prompt router. + * This equals to the promptRouterArn property, useful for implementing IBedrockInvokable interface. + */ + public readonly invokableArn: string; + + /** + * The inference endpoints (cross-region profiles) that this router will route to. + * These are created automatically based on the routing models and region. + */ + public readonly routingEndpoints: IBedrockInvokable[]; + + constructor(props: PromptRouterProps, region: string) { + this.promptRouterId = props.promptRouterId; + this.promptRouterArn = Arn.format({ + partition: Aws.PARTITION, + service: 'bedrock', + region: region, + account: Aws.ACCOUNT_ID, + resource: 'default-prompt-router', + resourceName: this.promptRouterId, + arnFormat: ArnFormat.SLASH_RESOURCE_NAME, + }); + + // Needed to implement IBedrockInvokable + this.invokableArn = this.promptRouterArn; + + // Build inference profiles from routing endpoints + // Each routing model is wrapped in a cross-region inference profile + // to enable routing across regions for better availability + this.routingEndpoints = props.routingModels.flatMap(model => { + const geoRegion = REGION_TO_GEO_AREA[region]; + if (geoRegion) { + return CrossRegionInferenceProfile.fromConfig({ + model: model, + geoRegion: geoRegion, + }); + } else { + // For unknown regions, fall back to using the model directly + return model; + } + }); + } + + /** + * Grants the necessary permissions to invoke this prompt router and all its routing endpoints. + * This method grants permissions to: + * - Get prompt router details (bedrock:GetPromptRouter) + * - Invoke models through the router (bedrock:InvokeModel) + * - Use all underlying models and cross-region profiles + * + * @param grantee - The IAM principal to grant permissions to + * @returns An IAM Grant object representing the granted permissions + */ + public grantInvoke(grantee: IGrantable): Grant { + // Grant invoke permissions on every model endpoint of the router + this.routingEndpoints.forEach(model => { + model.grantInvoke(grantee); + }); + + // Grant invoke permissions to the prompt router itself + return Grant.addToPrincipal({ + grantee, + actions: ['bedrock:GetPromptRouter', 'bedrock:InvokeModel'], + resourceArns: [this.promptRouterArn], + }); + } +} diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts new file mode 100644 index 0000000000000..365cb950b3fe1 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts @@ -0,0 +1,329 @@ +import { App } from 'aws-cdk-lib/core'; +import * as core from 'aws-cdk-lib/core'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as bedrock from 'aws-cdk-lib/aws-bedrock'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import * as bedrockAlpha from '../../../bedrock'; + +describe('ApplicationInferenceProfile', () => { + let stack: core.Stack; + let foundationModel: bedrockAlpha.IBedrockInvokable; + + beforeEach(() => { + const app = new App(); + stack = new core.Stack(app, 'test-stack'); + foundationModel = bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0; + }); + + test('creates application inference profile with foundation model', () => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + description: 'Test application inference profile', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::ApplicationInferenceProfile', { + InferenceProfileName: 'test-profile', + Description: 'Test application inference profile', + ModelSource: { + CopyFrom: Match.anyValue(), + }, + }); + }); + + test('creates application inference profile with cross-region profile', () => { + const crossRegionProfile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: crossRegionProfile, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::ApplicationInferenceProfile', { + InferenceProfileName: 'test-profile', + ModelSource: { + CopyFrom: Match.anyValue(), + }, + }); + }); + + test('creates application inference profile with tags', () => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + tags: { + Environment: 'Test', + Project: 'CDK', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::ApplicationInferenceProfile', { + Tags: [ + { Key: 'Environment', Value: 'Test' }, + { Key: 'Project', Value: 'CDK' }, + ], + }); + }); + + describe('validation', () => { + test('throws error when inference profile name is empty', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: '', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName is required and cannot be empty'); + }); + + test('throws error when inference profile name is only whitespace', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: ' ', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName is required and cannot be empty'); + }); + + test('throws error when inference profile name is undefined', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: undefined as any, + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName is required and cannot be empty'); + }); + + test('throws error when inference profile name exceeds 64 characters', () => { + const longName = 'a'.repeat(65); // 65 characters + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: longName, + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName cannot exceed 64 characters'); + }); + + test('accepts inference profile name with exactly 64 characters', () => { + const maxLengthName = 'a'.repeat(64); // 64 characters + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: maxLengthName, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + + test('throws error when model source is not provided', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: undefined as any, + }); + }).toThrow('modelSource is required'); + }); + + test('throws error when model source is null', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: null as any, + }); + }).toThrow('modelSource is required'); + }); + + test('throws error when description exceeds 200 characters', () => { + const longDescription = 'a'.repeat(201); // 201 characters + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + description: longDescription, + }); + }).toThrow('description cannot exceed 200 characters'); + }); + + test('accepts description with exactly 200 characters', () => { + const maxLengthDescription = 'a'.repeat(200); // 200 characters + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + description: maxLengthDescription, + }); + }).not.toThrow(); + }); + + test('accepts undefined description', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + description: undefined, + }); + }).not.toThrow(); + }); + + test('accepts empty description', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + description: '', + }); + }).not.toThrow(); + }); + + test('validation order - checks required fields first', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: undefined as any, + modelSource: undefined as any, + }); + }).toThrow('applicationInferenceProfileName is required and cannot be empty'); + }); + }); + + test('grantInvoke adds correct permissions', () => { + const profile = new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + }); + + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + profile.grantInvoke(role); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: ['bedrock:InvokeModel*', 'bedrock:GetFoundationModel'], + Effect: 'Allow', + Resource: Match.anyValue(), + }), + Match.objectLike({ + Action: ['bedrock:GetInferenceProfile', 'bedrock:InvokeModel'], + Effect: 'Allow', + Resource: Match.anyValue(), + }), + ]), + }, + }); + }); + + test('grantProfileUsage adds correct permissions', () => { + const profile = new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + }); + + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + profile.grantProfileUsage(role); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + Match.objectLike({ + Action: ['bedrock:GetInferenceProfile', 'bedrock:InvokeModel'], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [Match.stringLikeRegexp('TestProfile[A-Z0-9]+'), 'InferenceProfileArn'], + }, + }), + ]), + }, + }); + }); + + describe('static methods', () => { + test('fromApplicationInferenceProfileAttributes creates profile from attributes', () => { + const importedProfile = bedrockAlpha.ApplicationInferenceProfile.fromApplicationInferenceProfileAttributes( + stack, + 'ImportedProfile', + { + inferenceProfileArn: 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/test-profile-id', + inferenceProfileIdentifier: 'test-profile-id', + }, + ); + + expect(importedProfile.inferenceProfileArn).toBe( + 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/test-profile-id', + ); + expect(importedProfile.inferenceProfileId).toBe('test-profile-id'); + expect(importedProfile.type).toBe(bedrockAlpha.InferenceProfileType.APPLICATION); + }); + + test('fromCfnApplicationInferenceProfile creates profile from L1 construct', () => { + const cfnProfile = new bedrock.CfnApplicationInferenceProfile(stack, 'CfnProfile', { + inferenceProfileName: 'test-profile', + modelSource: { + copyFrom: foundationModel.invokableArn, + }, + }); + + const importedProfile = bedrockAlpha.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); + + expect(importedProfile.inferenceProfileArn).toBe(cfnProfile.attrInferenceProfileArn); + expect(importedProfile.inferenceProfileId).toBe(cfnProfile.attrInferenceProfileId); + expect(importedProfile.type).toBe(bedrockAlpha.InferenceProfileType.APPLICATION); + }); + }); + + describe('attributes', () => { + test('exposes correct attributes', () => { + const profile = new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + description: 'Test profile', + }); + + // The ARN contains CloudFormation tokens, so we check it's defined + expect(profile.inferenceProfileArn).toBeDefined(); + expect(profile.inferenceProfileModel).toBe(foundationModel); + expect(profile.type).toBe(bedrockAlpha.InferenceProfileType.APPLICATION); + expect(profile.invokableArn).toBe(profile.inferenceProfileArn); + }); + + test('CloudFormation attributes are accessible', () => { + const profile = new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + }); + + // These should be accessible without throwing errors + expect(profile.inferenceProfileArn).toBeDefined(); + expect(profile.inferenceProfileId).toBeDefined(); + expect(profile.status).toBeDefined(); + expect(profile.createdAt).toBeDefined(); + expect(profile.updatedAt).toBeDefined(); + }); + }); + + describe('integration with agents', () => { + test('can be used as foundation model for agent', () => { + const profile = new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: foundationModel, + }); + + new bedrockAlpha.Agent(stack, 'TestAgent', { + instruction: 'You are a helpful assistant that uses an application inference profile for cost tracking.', + foundationModel: profile, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + FoundationModel: { + 'Fn::GetAtt': [Match.stringLikeRegexp('TestProfile[A-Z0-9]+'), 'InferenceProfileArn'], + }, + }); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/cross-region-inference-profile.test.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/cross-region-inference-profile.test.ts new file mode 100644 index 0000000000000..1f2412dce6049 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/cross-region-inference-profile.test.ts @@ -0,0 +1,208 @@ +import { App } from 'aws-cdk-lib/core'; +import * as core from 'aws-cdk-lib/core'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import * as bedrockAlpha from '../../../bedrock'; + +describe('CrossRegionInferenceProfile', () => { + let stack: core.Stack; + + beforeEach(() => { + const app = new App(); + stack = new core.Stack(app, 'test-stack', { + env: { region: 'us-east-1' }, + }); + }); + + test('creates cross-region inference profile with US region', () => { + const profile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + expect(profile.inferenceProfileId).toBe('us.anthropic.claude-3-5-sonnet-20240620-v1:0'); + expect(profile.type).toBe(bedrockAlpha.InferenceProfileType.SYSTEM_DEFINED); + expect(profile.inferenceProfileModel).toBe(bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0); + expect(profile.invokableArn).toBe(profile.inferenceProfileArn); + }); + + test('creates cross-region inference profile with EU region', () => { + const profile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.EU, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + expect(profile.inferenceProfileId).toBe('eu.anthropic.claude-3-5-sonnet-20240620-v1:0'); + expect(profile.type).toBe(bedrockAlpha.InferenceProfileType.SYSTEM_DEFINED); + }); + + test('creates cross-region inference profile with APAC region', () => { + const profile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.APAC, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + expect(profile.inferenceProfileId).toBe('apac.anthropic.claude-3-5-sonnet-20240620-v1:0'); + expect(profile.type).toBe(bedrockAlpha.InferenceProfileType.SYSTEM_DEFINED); + }); + + test('generates correct ARN format', () => { + const profile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + // The ARN contains CloudFormation tokens, so we check the structure + expect(profile.inferenceProfileArn).toContain('arn:'); + expect(profile.inferenceProfileArn).toContain('bedrock'); + expect(profile.inferenceProfileArn).toContain('inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0'); + }); + + test('throws error when model does not support cross-region', () => { + const nonCrossRegionModel = new bedrockAlpha.BedrockFoundationModel('test.model-v1:0', { + supportsCrossRegion: false, + }); + + expect(() => { + bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: nonCrossRegionModel, + }); + }).toThrow('Model test.model-v1:0 does not support cross-region inference'); + }); + + describe('validation', () => { + test('throws error when geoRegion is not provided', () => { + expect(() => { + bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: undefined as any, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + }).toThrow('geoRegion is required'); + }); + + test('throws error when geoRegion is null', () => { + expect(() => { + bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: null as any, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + }).toThrow('geoRegion is required'); + }); + + test('throws error when model is not provided', () => { + expect(() => { + bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: undefined as any, + }); + }).toThrow('model is required'); + }); + + test('throws error when model is null', () => { + expect(() => { + bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: null as any, + }); + }).toThrow('model is required'); + }); + + test('throws error when both geoRegion and model are missing', () => { + expect(() => { + bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: undefined as any, + model: undefined as any, + }); + }).toThrow('geoRegion is required'); + }); + }); + + test('grantInvoke adds correct permissions', () => { + const profile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + profile.grantInvoke(role); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: Match.arrayWith([ + // Should grant permissions to invoke model in all regions + Match.objectLike({ + Action: ['bedrock:InvokeModel*', 'bedrock:GetFoundationModel'], + Effect: 'Allow', + Resource: Match.anyValue(), + }), + // Should grant permissions to use the inference profile + Match.objectLike({ + Action: ['bedrock:GetInferenceProfile', 'bedrock:InvokeModel*'], + Effect: 'Allow', + Resource: Match.anyValue(), + }), + ]), + }, + }); + }); + + describe('region mapping', () => { + test('REGION_TO_GEO_AREA contains correct mappings', () => { + expect(bedrockAlpha.REGION_TO_GEO_AREA['us-east-1']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.US); + expect(bedrockAlpha.REGION_TO_GEO_AREA['us-east-2']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.US); + expect(bedrockAlpha.REGION_TO_GEO_AREA['us-west-2']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.US); + + expect(bedrockAlpha.REGION_TO_GEO_AREA['eu-central-1']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.EU); + expect(bedrockAlpha.REGION_TO_GEO_AREA['eu-west-1']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.EU); + expect(bedrockAlpha.REGION_TO_GEO_AREA['eu-west-3']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.EU); + + expect(bedrockAlpha.REGION_TO_GEO_AREA['ap-northeast-1']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.APAC); + expect(bedrockAlpha.REGION_TO_GEO_AREA['ap-northeast-2']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.APAC); + expect(bedrockAlpha.REGION_TO_GEO_AREA['ap-south-1']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.APAC); + expect(bedrockAlpha.REGION_TO_GEO_AREA['ap-southeast-1']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.APAC); + expect(bedrockAlpha.REGION_TO_GEO_AREA['ap-southeast-2']).toBe(bedrockAlpha.CrossRegionInferenceProfileRegion.APAC); + }); + }); + + describe('integration with application inference profiles', () => { + test('can be used as model source for application inference profile', () => { + const crossRegionProfile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: 'test-profile', + modelSource: crossRegionProfile, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::ApplicationInferenceProfile', { + ModelSource: { + CopyFrom: Match.anyValue(), + }, + }); + }); + }); + + describe('integration with agents', () => { + test('can be used as foundation model for agent', () => { + const profile = bedrockAlpha.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrockAlpha.CrossRegionInferenceProfileRegion.US, + model: bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + }); + + new bedrockAlpha.Agent(stack, 'TestAgent', { + instruction: 'You are a helpful assistant that uses cross-region inference for better availability.', + foundationModel: profile, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + FoundationModel: Match.anyValue(), + }); + }); + }); +}); diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.assets.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.assets.json new file mode 100644 index 0000000000000..c409e4835b0fc --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.assets.json @@ -0,0 +1,20 @@ +{ + "version": "45.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB Template", + "source": { + "path": "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.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/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.template.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.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/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.assets.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.assets.json new file mode 100644 index 0000000000000..2f833e5b5c911 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.assets.json @@ -0,0 +1,21 @@ +{ + "version": "45.0.0", + "files": { + "ea56341a7255336bb06c0d5f7a1610980c74cf1e0eaa7b0f9c8a66bc7e9e206e": { + "displayName": "aws-cdk-bedrock-inference-profiles-integ Template", + "source": { + "path": "aws-cdk-bedrock-inference-profiles-integ.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-us-east-1-34bebf73": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1", + "objectKey": "ea56341a7255336bb06c0d5f7a1610980c74cf1e0eaa7b0f9c8a66bc7e9e206e.json", + "region": "us-east-1", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-us-east-1" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.template.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.template.json new file mode 100644 index 0000000000000..f6248b976bb90 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/aws-cdk-bedrock-inference-profiles-integ.template.json @@ -0,0 +1,494 @@ +{ + "Resources": { + "AppProfileWithModel97F8407F": { + "Type": "AWS::Bedrock::ApplicationInferenceProfile", + "Properties": { + "Description": "Application inference profile with foundation model for cost tracking", + "InferenceProfileName": "test-app-profile-model", + "ModelSource": { + "CopyFrom": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + "::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0" + ] + ] + } + }, + "Tags": [ + { + "Key": "Environment", + "Value": "Integration" + }, + { + "Key": "Purpose", + "Value": "Testing" + } + ] + } + }, + "AppProfileWithCrossRegion6F6D2846": { + "Type": "AWS::Bedrock::ApplicationInferenceProfile", + "Properties": { + "Description": "Application inference profile with cross-region profile for multi-region cost tracking", + "InferenceProfileName": "test-app-profile-cross-region", + "ModelSource": { + "CopyFrom": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" + ] + ] + } + } + } + }, + "AgentWithAppProfileRole53411241": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "aws:SourceAccount": { + "Ref": "AWS::AccountId" + } + }, + "ArnLike": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:bedrock:us-east-1:", + { + "Ref": "AWS::AccountId" + }, + ":agent/*" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "bedrock.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "RoleName": "agent-awscdkbedrockinferentwithappprofile-cf17979c-bedrockagent" + } + }, + "AgentWithAppProfileRoleDefaultPolicyE0AED950": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "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-20240620-v1:0" + ] + ] + } + }, + { + "Action": [ + "bedrock:GetInferenceProfile", + "bedrock:InvokeModel" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "AppProfileWithModel97F8407F", + "InferenceProfileArn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AgentWithAppProfileRoleDefaultPolicyE0AED950", + "Roles": [ + { + "Ref": "AgentWithAppProfileRole53411241" + } + ] + } + }, + "AgentWithAppProfileCE45BEE6": { + "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": "test-agent-with-app-profile", + "AgentResourceRoleArn": { + "Fn::GetAtt": [ + "AgentWithAppProfileRole53411241", + "Arn" + ] + }, + "AutoPrepare": false, + "Description": "Agent using application inference profile", + "FoundationModel": { + "Fn::GetAtt": [ + "AppProfileWithModel97F8407F", + "InferenceProfileArn" + ] + }, + "IdleSessionTTLInSeconds": 600, + "Instruction": "You are a helpful assistant that uses an application inference profile for cost tracking and monitoring.", + "OrchestrationType": "DEFAULT", + "SkipResourceInUseCheckOnDelete": false + }, + "DependsOn": [ + "AgentWithAppProfileRoleDefaultPolicyE0AED950" + ] + }, + "AgentWithCrossRegionRoleFDE2C251": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Condition": { + "StringEquals": { + "aws:SourceAccount": { + "Ref": "AWS::AccountId" + } + }, + "ArnLike": { + "aws:SourceArn": { + "Fn::Join": [ + "", + [ + "arn:aws:bedrock:us-east-1:", + { + "Ref": "AWS::AccountId" + }, + ":agent/*" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": "bedrock.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "RoleName": "agent-awscdkbedrockinferntwithcrossregion-0e46d65a-bedrockagent" + } + }, + "AgentWithCrossRegionRoleDefaultPolicyC1384BE8": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "bedrock:GetFoundationModel", + "bedrock:InvokeModel*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:*::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0" + ] + ] + } + }, + { + "Action": [ + "bedrock:GetInferenceProfile", + "bedrock:InvokeModel*" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "AgentWithCrossRegionRoleDefaultPolicyC1384BE8", + "Roles": [ + { + "Ref": "AgentWithCrossRegionRoleFDE2C251" + } + ] + } + }, + "AgentWithCrossRegion6632862D": { + "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": "test-agent-with-cross-region", + "AgentResourceRoleArn": { + "Fn::GetAtt": [ + "AgentWithCrossRegionRoleFDE2C251", + "Arn" + ] + }, + "AutoPrepare": false, + "Description": "Agent using cross-region inference profile", + "FoundationModel": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0" + ] + ] + }, + "IdleSessionTTLInSeconds": 600, + "Instruction": "You are a helpful assistant that uses cross-region inference for better availability and resilience.", + "OrchestrationType": "DEFAULT", + "SkipResourceInUseCheckOnDelete": false + }, + "DependsOn": [ + "AgentWithCrossRegionRoleDefaultPolicyC1384BE8" + ] + }, + "PromptWithRouter4FEB811C": { + "Type": "AWS::Bedrock::Prompt", + "Properties": { + "Description": "Prompt using intelligent routing between models", + "Name": "test-prompt-with-router", + "Variants": [ + { + "ModelId": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:us-east-1:", + { + "Ref": "AWS::AccountId" + }, + ":default-prompt-router/anthropic.claude:1" + ] + ] + }, + "Name": "default-variant", + "TemplateConfiguration": { + "Text": { + "Text": "You are an AI assistant. Please help the user with their question: {{question}}" + } + }, + "TemplateType": "TEXT" + } + ] + } + } + }, + "Outputs": { + "AppProfileWithModelArn": { + "Description": "ARN of the application inference profile with foundation model", + "Value": { + "Fn::GetAtt": [ + "AppProfileWithModel97F8407F", + "InferenceProfileArn" + ] + } + }, + "AppProfileWithCrossRegionArn": { + "Description": "ARN of the application inference profile with cross-region profile", + "Value": { + "Fn::GetAtt": [ + "AppProfileWithCrossRegion6F6D2846", + "InferenceProfileArn" + ] + } + }, + "CrossRegionProfileId": { + "Description": "ID of the cross-region inference profile", + "Value": "us.anthropic.claude-3-5-sonnet-20240620-v1:0" + }, + "PromptRouterArn": { + "Description": "ARN of the prompt router", + "Value": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:us-east-1:", + { + "Ref": "AWS::AccountId" + }, + ":default-prompt-router/anthropic.claude:1" + ] + ] + } + }, + "AgentWithAppProfileArn": { + "Description": "ARN of the agent using application inference profile", + "Value": { + "Fn::GetAtt": [ + "AgentWithAppProfileCE45BEE6", + "AgentArn" + ] + } + }, + "AgentWithCrossRegionArn": { + "Description": "ARN of the agent using cross-region inference profile", + "Value": { + "Fn::GetAtt": [ + "AgentWithCrossRegion6632862D", + "AgentArn" + ] + } + }, + "PromptWithRouterArn": { + "Description": "ARN of the prompt using prompt router", + "Value": { + "Fn::GetAtt": [ + "PromptWithRouter4FEB811C", + "Arn" + ] + } + } + }, + "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/inference-profiles/integ.inference-profiles.js.snapshot/cdk.out b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/cdk.out new file mode 100644 index 0000000000000..3704a1b682acf --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"45.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/integ.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/integ.json new file mode 100644 index 0000000000000..a36aceaa09024 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/integ.json @@ -0,0 +1,20 @@ +{ + "version": "45.0.0", + "testCases": { + "BedrockInferenceProfilesTest/DefaultTest": { + "stacks": [ + "aws-cdk-bedrock-inference-profiles-integ" + ], + "cdkCommandOptions": { + "deploy": { + "args": { + "rollback": false + } + } + }, + "assertionStack": "BedrockInferenceProfilesTest/DefaultTest/DeployAssert", + "assertionStackName": "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB" + } + }, + "minimumCliVersion": "2.1020.2" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/manifest.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/manifest.json new file mode 100644 index 0000000000000..981de4dc6499b --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/manifest.json @@ -0,0 +1,872 @@ +{ + "version": "45.0.0", + "artifacts": { + "aws-cdk-bedrock-inference-profiles-integ.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-cdk-bedrock-inference-profiles-integ.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-cdk-bedrock-inference-profiles-integ": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/us-east-1", + "properties": { + "templateFile": "aws-cdk-bedrock-inference-profiles-integ.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-us-east-1", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-us-east-1", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1/ea56341a7255336bb06c0d5f7a1610980c74cf1e0eaa7b0f9c8a66bc7e9e206e.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-cdk-bedrock-inference-profiles-integ.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-us-east-1", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "aws-cdk-bedrock-inference-profiles-integ.assets" + ], + "metadata": { + "/aws-cdk-bedrock-inference-profiles-integ/AppProfileWithModel/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AppProfileWithModel97F8407F" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AppProfileWithCrossRegion/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AppProfileWithCrossRegion6F6D2846" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/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-inference-profiles-integ/AgentWithAppProfile/Role/ImportRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithAppProfileRole53411241" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/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-inference-profiles-integ/AgentWithAppProfile/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithAppProfileRoleDefaultPolicyE0AED950" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithAppProfileCE45BEE6" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + }, + { + "type": "aws:cdk:analytics:method", + "data": "*" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/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-inference-profiles-integ/AgentWithCrossRegion/Role/ImportRole": [ + { + "type": "aws:cdk:analytics:construct", + "data": "*" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithCrossRegionRoleFDE2C251" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/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-inference-profiles-integ/AgentWithCrossRegion/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithCrossRegionRoleDefaultPolicyC1384BE8" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithCrossRegion6632862D" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/PromptWithRouter/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PromptWithRouter4FEB811C" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AppProfileWithModelArn": [ + { + "type": "aws:cdk:logicalId", + "data": "AppProfileWithModelArn" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AppProfileWithCrossRegionArn": [ + { + "type": "aws:cdk:logicalId", + "data": "AppProfileWithCrossRegionArn" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/CrossRegionProfileId": [ + { + "type": "aws:cdk:logicalId", + "data": "CrossRegionProfileId" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/PromptRouterArn": [ + { + "type": "aws:cdk:logicalId", + "data": "PromptRouterArn" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfileArn": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithAppProfileArn" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegionArn": [ + { + "type": "aws:cdk:logicalId", + "data": "AgentWithCrossRegionArn" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/PromptWithRouterArn": [ + { + "type": "aws:cdk:logicalId", + "data": "PromptWithRouterArn" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-cdk-bedrock-inference-profiles-integ/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-cdk-bedrock-inference-profiles-integ" + }, + "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.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": [ + "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.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": [ + "BedrockInferenceProfilesTestDefaultTestDeployAssert4CC2EFAB.assets" + ], + "metadata": { + "/BedrockInferenceProfilesTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/BedrockInferenceProfilesTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "BedrockInferenceProfilesTest/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/core:enableStackNameDuplicates": { + "recommendedValue": true, + "explanation": "Allow multiple stacks with the same name" + }, + "aws-cdk:enableDiffNoFail": { + "recommendedValue": true, + "explanation": "Make `cdk diff` not fail when there are differences" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD" + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path" + }, + "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": { + "recommendedValue": true, + "explanation": "DockerImageAsset properly supports `.dockerignore` files by default" + }, + "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": { + "recommendedValue": true, + "explanation": "Fix the referencing of SecretsManager names from ARNs" + }, + "@aws-cdk/aws-kms:defaultKeyPolicies": { + "recommendedValue": true, + "explanation": "Tighten default KMS key policies" + }, + "@aws-cdk/aws-s3:grantWriteWithoutAcl": { + "recommendedValue": true, + "explanation": "Remove `PutObjectAcl` from Bucket.grantWrite" + }, + "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": { + "recommendedValue": true, + "explanation": "Do not specify a default DesiredCount for ECS services" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK" + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently" + }, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have elastic file systems encrypted at rest by default." + }, + "@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`." + }, + "@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." + }, + "@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" + }, + "@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." + }, + "@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." + }, + "@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" + }, + "@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" + }, + "@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.1020.2" +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/tree.json b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.js.snapshot/tree.json new file mode 100644 index 0000000000000..14bc0900b028c --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.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-inference-profiles-integ":{"id":"aws-cdk-bedrock-inference-profiles-integ","path":"aws-cdk-bedrock-inference-profiles-integ","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"AppProfileWithModel":{"id":"AppProfileWithModel","path":"aws-cdk-bedrock-inference-profiles-integ/AppProfileWithModel","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.ApplicationInferenceProfile","version":"0.0.0","metadata":[]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-bedrock-inference-profiles-integ/AppProfileWithModel/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrock.CfnApplicationInferenceProfile","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Bedrock::ApplicationInferenceProfile","aws:cdk:cloudformation:props":{"tags":[{"key":"Environment","value":"Integration"},{"key":"Purpose","value":"Testing"}],"description":"Application inference profile with foundation model for cost tracking","inferenceProfileName":"test-app-profile-model","modelSource":{"copyFrom":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},"::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"]]}}}}}}},"AppProfileWithCrossRegion":{"id":"AppProfileWithCrossRegion","path":"aws-cdk-bedrock-inference-profiles-integ/AppProfileWithCrossRegion","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.ApplicationInferenceProfile","version":"0.0.0","metadata":[]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-bedrock-inference-profiles-integ/AppProfileWithCrossRegion/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrock.CfnApplicationInferenceProfile","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Bedrock::ApplicationInferenceProfile","aws:cdk:cloudformation:props":{"description":"Application inference profile with cross-region profile for multi-region cost tracking","inferenceProfileName":"test-app-profile-cross-region","modelSource":{"copyFrom":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},":",{"Ref":"AWS::AccountId"},":inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"]]}}}}}}},"AgentWithAppProfile":{"id":"AgentWithAppProfile","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.Agent","version":"0.0.0","metadata":["*","*","*"]},"children":{"Role":{"id":"Role","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/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-inference-profiles-integ/AgentWithAppProfile/Role/ImportRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/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:aws:bedrock:us-east-1:",{"Ref":"AWS::AccountId"},":agent/*"]]}}},"Effect":"Allow","Principal":{"Service":"bedrock.amazonaws.com"}}],"Version":"2012-10-17"},"roleName":"agent-awscdkbedrockinferentwithappprofile-cf17979c-bedrockagent"}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/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-inference-profiles-integ/AgentWithAppProfile/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:GetFoundationModel","bedrock:InvokeModel*"],"Effect":"Allow","Resource":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},"::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"]]}},{"Action":["bedrock:GetInferenceProfile","bedrock:InvokeModel"],"Effect":"Allow","Resource":{"Fn::GetAtt":["AppProfileWithModel97F8407F","InferenceProfileArn"]}}],"Version":"2012-10-17"},"policyName":"AgentWithAppProfileRoleDefaultPolicyE0AED950","roles":[{"Ref":"AgentWithAppProfileRole53411241"}]}}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/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":"test-agent-with-app-profile","agentResourceRoleArn":{"Fn::GetAtt":["AgentWithAppProfileRole53411241","Arn"]},"autoPrepare":false,"description":"Agent using application inference profile","foundationModel":{"Fn::GetAtt":["AppProfileWithModel97F8407F","InferenceProfileArn"]},"idleSessionTtlInSeconds":600,"instruction":"You are a helpful assistant that uses an application inference profile for cost tracking and monitoring.","orchestrationType":"DEFAULT","skipResourceInUseCheckOnDelete":false}}},"DefaultAlias":{"id":"DefaultAlias","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfile/DefaultAlias","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.AgentAliasBase","version":"0.0.0","metadata":[]}}}},"AgentWithCrossRegion":{"id":"AgentWithCrossRegion","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.Agent","version":"0.0.0","metadata":["*","*","*"]},"children":{"Role":{"id":"Role","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/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-inference-profiles-integ/AgentWithCrossRegion/Role/ImportRole","constructInfo":{"fqn":"aws-cdk-lib.Resource","version":"0.0.0","metadata":["*"]}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/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:aws:bedrock:us-east-1:",{"Ref":"AWS::AccountId"},":agent/*"]]}}},"Effect":"Allow","Principal":{"Service":"bedrock.amazonaws.com"}}],"Version":"2012-10-17"},"roleName":"agent-awscdkbedrockinferntwithcrossregion-0e46d65a-bedrockagent"}}},"DefaultPolicy":{"id":"DefaultPolicy","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/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-inference-profiles-integ/AgentWithCrossRegion/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:GetFoundationModel","bedrock:InvokeModel*"],"Effect":"Allow","Resource":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:*::foundation-model/anthropic.claude-3-5-sonnet-20240620-v1:0"]]}},{"Action":["bedrock:GetInferenceProfile","bedrock:InvokeModel*"],"Effect":"Allow","Resource":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},":",{"Ref":"AWS::AccountId"},":inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"]]}}],"Version":"2012-10-17"},"policyName":"AgentWithCrossRegionRoleDefaultPolicyC1384BE8","roles":[{"Ref":"AgentWithCrossRegionRoleFDE2C251"}]}}}}}}},"Resource":{"id":"Resource","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/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":"test-agent-with-cross-region","agentResourceRoleArn":{"Fn::GetAtt":["AgentWithCrossRegionRoleFDE2C251","Arn"]},"autoPrepare":false,"description":"Agent using cross-region inference profile","foundationModel":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:",{"Ref":"AWS::Region"},":",{"Ref":"AWS::AccountId"},":inference-profile/us.anthropic.claude-3-5-sonnet-20240620-v1:0"]]},"idleSessionTtlInSeconds":600,"instruction":"You are a helpful assistant that uses cross-region inference for better availability and resilience.","orchestrationType":"DEFAULT","skipResourceInUseCheckOnDelete":false}}},"DefaultAlias":{"id":"DefaultAlias","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegion/DefaultAlias","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.AgentAliasBase","version":"0.0.0","metadata":[]}}}},"PromptWithRouter":{"id":"PromptWithRouter","path":"aws-cdk-bedrock-inference-profiles-integ/PromptWithRouter","constructInfo":{"fqn":"@aws-cdk/aws-bedrock-alpha.Prompt","version":"0.0.0","metadata":[]},"children":{"Resource":{"id":"Resource","path":"aws-cdk-bedrock-inference-profiles-integ/PromptWithRouter/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_bedrock.CfnPrompt","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::Bedrock::Prompt","aws:cdk:cloudformation:props":{"description":"Prompt using intelligent routing between models","name":"test-prompt-with-router","variants":[{"name":"default-variant","templateType":"TEXT","templateConfiguration":{"text":{"text":"You are an AI assistant. Please help the user with their question: {{question}}"}},"modelId":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":bedrock:us-east-1:",{"Ref":"AWS::AccountId"},":default-prompt-router/anthropic.claude:1"]]}}]}}}}},"AppProfileWithModelArn":{"id":"AppProfileWithModelArn","path":"aws-cdk-bedrock-inference-profiles-integ/AppProfileWithModelArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"AppProfileWithCrossRegionArn":{"id":"AppProfileWithCrossRegionArn","path":"aws-cdk-bedrock-inference-profiles-integ/AppProfileWithCrossRegionArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"CrossRegionProfileId":{"id":"CrossRegionProfileId","path":"aws-cdk-bedrock-inference-profiles-integ/CrossRegionProfileId","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"PromptRouterArn":{"id":"PromptRouterArn","path":"aws-cdk-bedrock-inference-profiles-integ/PromptRouterArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"AgentWithAppProfileArn":{"id":"AgentWithAppProfileArn","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithAppProfileArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"AgentWithCrossRegionArn":{"id":"AgentWithCrossRegionArn","path":"aws-cdk-bedrock-inference-profiles-integ/AgentWithCrossRegionArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"PromptWithRouterArn":{"id":"PromptWithRouterArn","path":"aws-cdk-bedrock-inference-profiles-integ/PromptWithRouterArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"BootstrapVersion":{"id":"BootstrapVersion","path":"aws-cdk-bedrock-inference-profiles-integ/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"aws-cdk-bedrock-inference-profiles-integ/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"BedrockInferenceProfilesTest":{"id":"BedrockInferenceProfilesTest","path":"BedrockInferenceProfilesTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"BedrockInferenceProfilesTest/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"BedrockInferenceProfilesTest/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"BedrockInferenceProfilesTest/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"BedrockInferenceProfilesTest/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"BedrockInferenceProfilesTest/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/inference-profiles/integ.inference-profiles.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.ts new file mode 100644 index 0000000000000..da3e12bb7af96 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/integ.inference-profiles.ts @@ -0,0 +1,117 @@ +import * as cdk from 'aws-cdk-lib'; +import { IntegTest } 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-inference-profiles-integ', { + env: { + region: 'us-east-1', + }, +}); + +// Create a cross-region inference profile +const crossRegionProfile = bedrock.CrossRegionInferenceProfile.fromConfig({ + geoRegion: bedrock.CrossRegionInferenceProfileRegion.US, + model: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, +}); + +// Create an application inference profile with a foundation model +const appProfileWithModel = new bedrock.ApplicationInferenceProfile(stack, 'AppProfileWithModel', { + applicationInferenceProfileName: 'test-app-profile-model', + description: 'Application inference profile with foundation model for cost tracking', + modelSource: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + tags: { + Environment: 'Integration', + Purpose: 'Testing', + }, +}); + +// Create an application inference profile with a cross-region profile +const appProfileWithCrossRegion = new bedrock.ApplicationInferenceProfile(stack, 'AppProfileWithCrossRegion', { + applicationInferenceProfileName: 'test-app-profile-cross-region', + description: 'Application inference profile with cross-region profile for multi-region cost tracking', + modelSource: crossRegionProfile, +}); + +// Create a prompt router +const promptRouter = bedrock.PromptRouter.fromDefaultId( + bedrock.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1', +); + +// Create an agent using an application inference profile +const agentWithAppProfile = new bedrock.Agent(stack, 'AgentWithAppProfile', { + agentName: 'test-agent-with-app-profile', + instruction: 'You are a helpful assistant that uses an application inference profile for cost tracking and monitoring.', + foundationModel: appProfileWithModel, + description: 'Agent using application inference profile', +}); + +// Create an agent using a cross-region inference profile +const agentWithCrossRegion = new bedrock.Agent(stack, 'AgentWithCrossRegion', { + agentName: 'test-agent-with-cross-region', + instruction: 'You are a helpful assistant that uses cross-region inference for better availability and resilience.', + foundationModel: crossRegionProfile, + description: 'Agent using cross-region inference profile', +}); + +// Create a prompt using a prompt router +const promptWithRouter = new bedrock.Prompt(stack, 'PromptWithRouter', { + promptName: 'test-prompt-with-router', + description: 'Prompt using intelligent routing between models', + variants: [ + bedrock.PromptVariant.text({ + variantName: 'default-variant', + promptText: 'You are an AI assistant. Please help the user with their question: {{question}}', + model: promptRouter, + }), + ], +}); + +// Output the ARNs for verification +new cdk.CfnOutput(stack, 'AppProfileWithModelArn', { + value: appProfileWithModel.inferenceProfileArn, + description: 'ARN of the application inference profile with foundation model', +}); + +new cdk.CfnOutput(stack, 'AppProfileWithCrossRegionArn', { + value: appProfileWithCrossRegion.inferenceProfileArn, + description: 'ARN of the application inference profile with cross-region profile', +}); + +new cdk.CfnOutput(stack, 'CrossRegionProfileId', { + value: crossRegionProfile.inferenceProfileId, + description: 'ID of the cross-region inference profile', +}); + +new cdk.CfnOutput(stack, 'PromptRouterArn', { + value: promptRouter.promptRouterArn, + description: 'ARN of the prompt router', +}); + +new cdk.CfnOutput(stack, 'AgentWithAppProfileArn', { + value: agentWithAppProfile.agentArn, + description: 'ARN of the agent using application inference profile', +}); + +new cdk.CfnOutput(stack, 'AgentWithCrossRegionArn', { + value: agentWithCrossRegion.agentArn, + description: 'ARN of the agent using cross-region inference profile', +}); + +new cdk.CfnOutput(stack, 'PromptWithRouterArn', { + value: promptWithRouter.promptArn, + description: 'ARN of the prompt using prompt router', +}); + +new IntegTest(app, 'BedrockInferenceProfilesTest', { + testCases: [stack], + cdkCommandOptions: { + deploy: { + args: { + rollback: false, + }, + }, + }, +}); diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/prompt-router.test.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/prompt-router.test.ts new file mode 100644 index 0000000000000..b18d4a3ea4dd1 --- /dev/null +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/prompt-router.test.ts @@ -0,0 +1,223 @@ +import { App } from 'aws-cdk-lib/core'; +import * as core from 'aws-cdk-lib/core'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import { Template, Match } from 'aws-cdk-lib/assertions'; +import * as bedrockAlpha from '../../../bedrock'; + +describe('PromptRouter', () => { + let stack: core.Stack; + + beforeEach(() => { + const app = new App(); + stack = new core.Stack(app, 'test-stack', { + env: { region: 'us-east-1' }, + }); + }); + + test('creates prompt router from default Anthropic Claude V1 identifier', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1', + ); + + expect(router.promptRouterId).toBe('anthropic.claude:1'); + // The ARN contains CloudFormation tokens, so we check the structure + expect(router.promptRouterArn).toContain('arn:'); + expect(router.promptRouterArn).toContain('bedrock'); + expect(router.promptRouterArn).toContain('us-east-1'); + expect(router.promptRouterArn).toContain('default-prompt-router/anthropic.claude:1'); + }); + + test('creates prompt router from default Meta Llama 3.1 identifier', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.META_LLAMA_3_1, + 'us-east-1', + ); + + expect(router.promptRouterId).toBe('meta.llama:1'); + expect(router.promptRouterArn).toContain('arn:'); + expect(router.promptRouterArn).toContain('bedrock'); + expect(router.promptRouterArn).toContain('us-east-1'); + expect(router.promptRouterArn).toContain('default-prompt-router/meta.llama:1'); + expect(router.routingEndpoints).toHaveLength(2); + }); + + test('creates routing endpoints as cross-region inference profiles', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1', + ); + + // Should create cross-region inference profiles for each routing model + router.routingEndpoints.forEach(endpoint => { + expect(endpoint).toBeInstanceOf(bedrockAlpha.CrossRegionInferenceProfile); + expect((endpoint as bedrockAlpha.CrossRegionInferenceProfile).type).toBe( + bedrockAlpha.InferenceProfileType.SYSTEM_DEFINED, + ); + }); + }); + + test('generates correct ARN format', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-west-2', + ); + + // The ARN contains CloudFormation tokens, so we check the structure + expect(router.promptRouterArn).toContain('arn:'); + expect(router.promptRouterArn).toContain('bedrock'); + expect(router.promptRouterArn).toContain('us-west-2'); + expect(router.promptRouterArn).toContain('default-prompt-router/anthropic.claude:1'); + }); + + test('grantInvoke adds correct permissions for router and all endpoints', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1', + ); + + const role = new iam.Role(stack, 'TestRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + + router.grantInvoke(role); + }); + + describe('DefaultPromptRouterIdentifier', () => { + test('ANTHROPIC_CLAUDE_V1 has correct configuration', () => { + const identifier = bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1; + + expect(identifier.promptRouterId).toBe('anthropic.claude:1'); + expect(identifier.routingModels).toHaveLength(2); + expect(identifier.routingModels).toContain(bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_HAIKU_V1_0); + expect(identifier.routingModels).toContain(bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0); + }); + + test('META_LLAMA_3_1 has correct configuration', () => { + const identifier = bedrockAlpha.DefaultPromptRouterIdentifier.META_LLAMA_3_1; + + expect(identifier.promptRouterId).toBe('meta.llama:1'); + expect(identifier.routingModels).toHaveLength(2); + expect(identifier.routingModels).toContain(bedrockAlpha.BedrockFoundationModel.META_LLAMA_3_1_8B_INSTRUCT_V1); + expect(identifier.routingModels).toContain(bedrockAlpha.BedrockFoundationModel.META_LLAMA_3_1_70B_INSTRUCT_V1); + }); + }); + + describe('integration with different regions', () => { + test('works with EU region', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'eu-west-1', + ); + + expect(router.promptRouterArn).toMatch(/eu-west-1/); + + // Routing endpoints should use EU geo region + router.routingEndpoints.forEach(endpoint => { + const crossRegionProfile = endpoint as bedrockAlpha.CrossRegionInferenceProfile; + expect(crossRegionProfile.inferenceProfileId).toMatch(/^eu\./); + }); + }); + + test('works with APAC region', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'ap-southeast-1', + ); + + expect(router.promptRouterArn).toMatch(/ap-southeast-1/); + + // Routing endpoints should use APAC geo region + router.routingEndpoints.forEach(endpoint => { + const crossRegionProfile = endpoint as bedrockAlpha.CrossRegionInferenceProfile; + expect(crossRegionProfile.inferenceProfileId).toMatch(/^apac\./); + }); + }); + }); + + describe('integration with prompts', () => { + test('can be used as model for prompt variant', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1', + ); + + const variant = bedrockAlpha.PromptVariant.text({ + variantName: 'test-variant', + promptText: 'What is the capital of France?', + model: router, + }); + + new bedrockAlpha.Prompt(stack, 'TestPrompt', { + promptName: 'test-prompt', + variants: [variant], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Prompt', { + Variants: Match.arrayWith([ + Match.objectLike({ + Name: 'test-variant', + ModelId: Match.anyValue(), + }), + ]), + }); + }); + }); + + describe('custom router configuration', () => { + test('can create router with custom configuration', () => { + const customRouter = new bedrockAlpha.PromptRouter( + { + promptRouterId: 'custom.router:1', + routingModels: [ + bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_HAIKU_V1_0, + bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0, + ], + }, + 'us-east-1', + ); + + expect(customRouter.promptRouterId).toBe('custom.router:1'); + expect(customRouter.routingEndpoints).toHaveLength(2); + }); + }); + + describe('error handling', () => { + test('handles region not in REGION_TO_GEO_AREA mapping', () => { + // This should not throw, but the cross-region profiles might not work as expected + // The router itself should still be created + expect(() => { + bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'unknown-region-1', + ); + }).not.toThrow(); + }); + }); + + describe('interface compliance', () => { + test('implements IBedrockInvokable interface', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1', + ); + + // Should have all required properties + expect(router.invokableArn).toBeDefined(); + expect(typeof router.grantInvoke).toBe('function'); + }); + + test('implements IPromptRouter interface', () => { + const router = bedrockAlpha.PromptRouter.fromDefaultId( + bedrockAlpha.DefaultPromptRouterIdentifier.ANTHROPIC_CLAUDE_V1, + 'us-east-1', + ); + + // Should have all required properties + expect(router.promptRouterArn).toBeDefined(); + expect(router.promptRouterId).toBeDefined(); + expect(router.routingEndpoints).toBeDefined(); + expect(Array.isArray(router.routingEndpoints)).toBe(true); + }); + }); +}); From 122a20c2b9dce6b0f70edb4a8fcd0fbd1b1a3711 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Wed, 23 Jul 2025 15:27:31 -0400 Subject: [PATCH 02/10] feat(inferenceprofiles): fix rosetta issue --- packages/@aws-cdk/aws-bedrock-alpha/README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/@aws-cdk/aws-bedrock-alpha/README.md b/packages/@aws-cdk/aws-bedrock-alpha/README.md index b4d336bdfcf14..7161daeb8b9b6 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/README.md +++ b/packages/@aws-cdk/aws-bedrock-alpha/README.md @@ -986,14 +986,4 @@ const importedProfile = bedrock.ApplicationInferenceProfile.fromApplicationInfer inferenceProfileIdentifier: 'my-profile-id', } ); - -// Import a Cfn L1 construct created application inference profile -const cfnProfile = new bedrock.CfnApplicationInferenceProfile(this, 'CfnProfile', { - inferenceProfileName: 'mytest', - modelSource: { - copyFrom: 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0', - }, -}); - -const importedFromCfn = bedrock.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); ``` From 0004b2604831c15f887e2ec0152788f44958ecc0 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Wed, 23 Jul 2025 15:39:20 -0400 Subject: [PATCH 03/10] feat(inferenceprofiles): updated export of iunference profiles --- .../aws-bedrock-alpha/bedrock/index.ts | 5 ++- .../bedrock/inference-profiles/index.ts | 32 ------------------- 2 files changed, 4 insertions(+), 33 deletions(-) delete mode 100644 packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts index de3ea73ce401c..89112e13b8afd 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/index.ts @@ -30,7 +30,10 @@ export * from './prompts/prompt-genai-resource'; // =================================== // Inference Profiles // =================================== -export * from './inference-profiles'; +export * from './inference-profiles/inference-profile'; +export * from './inference-profiles/application-inference-profile'; +export * from './inference-profiles/cross-region-inference-profile'; +export * from './inference-profiles/prompt-router'; // =================================== // Models diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts deleted file mode 100644 index cbaf69f4338d0..0000000000000 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - -// =================================== -// Common Interfaces and Base Classes -// =================================== -export * from './inference-profile'; - -// =================================== -// Application Inference Profiles -// =================================== -export * from './application-inference-profile'; - -// =================================== -// Cross-Region Inference Profiles -// =================================== -export * from './cross-region-inference-profile'; - -// =================================== -// Prompt Routers -// =================================== -export * from './prompt-router'; From 327cc092e0ed6bd0376dc30a2470c010fda5b818 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Wed, 23 Jul 2025 15:44:56 -0400 Subject: [PATCH 04/10] feat(inferenceprofiles): updated docs --- .../bedrock/inference-profiles/prompt-router.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts index 53109a83218a2..25f66bcf63370 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/prompt-router.ts @@ -1,16 +1,3 @@ -/** - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance - * with the License. A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES - * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions - * and limitations under the License. - */ - import { Arn, ArnFormat, Aws } from 'aws-cdk-lib'; import { IGrantable, Grant } from 'aws-cdk-lib/aws-iam'; import { BedrockFoundationModel, IBedrockInvokable } from '../models'; From 4ea1e2fe5405df9d45f8b3c2b7fc568c68c55151 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Mon, 28 Jul 2025 10:48:05 -0400 Subject: [PATCH 05/10] feat(inferenceprofiles): implemented code review comments --- .../application-inference-profile.ts | 71 ++++-- .../inference-profiles/inference-profile.ts | 19 +- .../application-inference-profile.test.ts | 221 +++++++++++++++++- 3 files changed, 282 insertions(+), 29 deletions(-) diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts index 333114fddfe29..a2e064b36f60d 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts @@ -107,6 +107,15 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements public readonly inferenceProfileId = Arn.split(attrs.inferenceProfileArn, ArnFormat.SLASH_RESOURCE_NAME) .resourceName!; public readonly type = InferenceProfileType.APPLICATION; + + public grantProfileUsage(grantee: IGrantable): Grant { + return Grant.addToPrincipal({ + grantee: grantee, + actions: ['bedrock:GetInferenceProfile', 'bedrock:InvokeModel'], + resourceArns: [this.inferenceProfileArn], + scope: this, + }); + } } return new Import(scope, id); @@ -125,6 +134,15 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements public readonly inferenceProfileArn = cfnApplicationInferenceProfile.attrInferenceProfileArn; public readonly inferenceProfileId = cfnApplicationInferenceProfile.attrInferenceProfileId; public readonly type = InferenceProfileType.APPLICATION; + + public grantProfileUsage(grantee: IGrantable): Grant { + return Grant.addToPrincipal({ + grantee: grantee, + actions: ['bedrock:GetInferenceProfile', 'bedrock:InvokeModel'], + resourceArns: [this.inferenceProfileArn], + scope: this, + }); + } })(cfnApplicationInferenceProfile, '@FromCfnApplicationInferenceProfile'); } @@ -200,21 +218,7 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements // ------------------------------------------------------ // Validate props // ------------------------------------------------------ - if (!props.applicationInferenceProfileName || props.applicationInferenceProfileName.trim() === '') { - throw new ValidationError('applicationInferenceProfileName is required and cannot be empty', this); - } - - if (props.applicationInferenceProfileName.length > 64) { - throw new ValidationError('applicationInferenceProfileName cannot exceed 64 characters', this); - } - - if (!props.modelSource) { - throw new ValidationError('modelSource is required', this); - } - - if (props.description !== undefined && props.description.length > 200) { - throw new ValidationError('description cannot exceed 200 characters', this); - } + this.validateProps(props); // ------------------------------------------------------ // Set properties @@ -250,6 +254,43 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements // METHODS // ------------------------------------------------------ + /** + * Validates the properties for creating an Application Inference Profile. + * + * @param props - The properties to validate + * @throws ValidationError if any validation fails + */ + private validateProps(props: ApplicationInferenceProfileProps): void { + // Validate applicationInferenceProfileName is provided and not empty + if (!props.applicationInferenceProfileName || props.applicationInferenceProfileName.trim() === '') { + throw new ValidationError('applicationInferenceProfileName is required and cannot be empty', this); + } + + // Validate applicationInferenceProfileName length + if (props.applicationInferenceProfileName.length > 64) { + throw new ValidationError('applicationInferenceProfileName cannot exceed 64 characters', this); + } + + // Validate applicationInferenceProfileName pattern + const namePattern = /^[0-9a-zA-Z]([0-9a-zA-Z]|[ _-][0-9a-zA-Z])*$/; + if (!namePattern.test(props.applicationInferenceProfileName)) { + throw new ValidationError( + 'applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$', + this, + ); + } + + // Validate modelSource is provided + if (!props.modelSource) { + throw new ValidationError('modelSource is required', this); + } + + // Validate description length if provided + if (props.description !== undefined && props.description.length > 200) { + throw new ValidationError('description cannot exceed 200 characters', this); + } + } + /** * Gives the appropriate policies to invoke and use the application inference profile. * This method ensures the appropriate permissions are given to use either the inference profile diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts index ef56d2fd71e83..237f38d74a451 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/inference-profile.ts @@ -41,7 +41,7 @@ export interface IInferenceProfile { /** * Grants appropriate permissions to use the inference profile. - * This includes permissions to call bedrock:GetInferenceProfile and bedrock:ListInferenceProfiles. + * Each profile type requires different permissions based on its usage pattern. * * @param grantee - The IAM principal to grant permissions to * @returns An IAM Grant object representing the granted permissions @@ -71,22 +71,15 @@ export abstract class InferenceProfileBase extends Resource implements IInferenc /** * Grants appropriate permissions to use the inference profile. - * This method adds the necessary IAM permissions to allow the grantee to: - * - Get inference profile details (bedrock:GetInferenceProfile) - * - List available inference profiles (bedrock:ListInferenceProfiles) + * Each profile type requires different permissions based on its usage pattern: + * - Application profiles need bedrock:InvokeModel for direct invocation + * - Cross-region profiles need bedrock:InvokeModel* for routing capabilities * * Note: This does not grant permissions to use the underlying model in the profile. - * For model invocation permissions, use the model's grantInvoke method separately. + * For comprehensive model invocation permissions, use the grantInvoke method instead. * * @param grantee - The IAM principal to grant permissions to * @returns An IAM Grant object representing the granted permissions */ - public grantProfileUsage(grantee: IGrantable): Grant { - return Grant.addToPrincipal({ - grantee: grantee, - actions: ['bedrock:GetInferenceProfile', 'bedrock:ListInferenceProfiles'], - resourceArns: [this.inferenceProfileArn], - scope: this, - }); - } + public abstract grantProfileUsage(grantee: IGrantable): Grant; } diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts index 365cb950b3fe1..ec17976483f1e 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts @@ -6,15 +6,22 @@ import { Template, Match } from 'aws-cdk-lib/assertions'; import * as bedrockAlpha from '../../../bedrock'; describe('ApplicationInferenceProfile', () => { + let app: App; let stack: core.Stack; let foundationModel: bedrockAlpha.IBedrockInvokable; beforeEach(() => { - const app = new App(); + app = new App(); stack = new core.Stack(app, 'test-stack'); foundationModel = bedrockAlpha.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0; }); + // Helper function to create a fresh stack for tests that need isolation + const createFreshStack = () => { + const freshApp = new App(); + return new core.Stack(freshApp, `teststack${Date.now()}${Math.floor(Math.random() * 1000)}`); + }; + test('creates application inference profile with foundation model', () => { new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { applicationInferenceProfileName: 'test-profile', @@ -116,6 +123,218 @@ describe('ApplicationInferenceProfile', () => { }).not.toThrow(); }); + describe('name pattern validation', () => { + test('accepts valid names with alphanumeric characters', () => { + const validNames = [ + 'test123', + 'MyProfile', + 'profile1', + 'A1B2C3', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfile${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + + test('accepts valid names with spaces', () => { + const validNames = [ + 'test profile', + 'My Profile 123', + 'profile 1', + 'A B C', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfile${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + + test('accepts valid names with underscores', () => { + const validNames = [ + 'test_profile', + 'My_Profile_123', + 'profile_1', + 'A_B_C', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfile${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + + test('accepts valid names with hyphens', () => { + const validNames = [ + 'test-profile', + 'My-Profile-123', + 'profile-1', + 'A-B-C', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfile${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + + test('accepts valid names with mixed separators', () => { + const validNames = [ + 'test_profile-123', + 'My Profile_123', + 'profile-1 test', + 'A_B-C 123', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfile${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + + test('throws error for names starting with space', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: ' test', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + + test('throws error for names starting with underscore', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: '_test', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + + test('throws error for names starting with hyphen', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { + applicationInferenceProfileName: '-test', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + + test('throws error for names with invalid characters', () => { + const invalidNames = [ + 'test@profile', + 'test#profile', + 'test$profile', + 'test%profile', + 'test&profile', + 'test*profile', + 'test+profile', + 'test=profile', + 'test!profile', + 'test?profile', + 'test.profile', + 'test,profile', + 'test;profile', + 'test:profile', + 'test|profile', + 'test\\profile', + 'test/profile', + 'testprofile', + 'test[profile', + 'test]profile', + 'test{profile', + 'test}profile', + 'test(profile', + 'test)profile', + 'test"profile', + "test'profile", + 'test`profile', + 'test~profile', + ]; + + invalidNames.forEach((name, index) => { + const freshStack = createFreshStack(); + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(freshStack, `TestProfile${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + }); + + test('throws error for names ending with space', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfileEndingSpace', { + applicationInferenceProfileName: 'test ', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + + test('throws error for names ending with underscore', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfileEndingUnderscore', { + applicationInferenceProfileName: 'test_', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + + test('throws error for names ending with hyphen', () => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfileEndingHyphen', { + applicationInferenceProfileName: 'test-', + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + + test('throws error for names with consecutive separators', () => { + const invalidNames = [ + 'test profile', + 'test__profile', + 'test--profile', + 'test_ profile', + 'test- profile', + 'test _profile', + 'test -profile', + ]; + + invalidNames.forEach((name, index) => { + const freshStack = createFreshStack(); + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(freshStack, `TestProfile${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }); + }); + }); + test('throws error when model source is not provided', () => { expect(() => { new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { From f3131646127b2acca0518552610b031b7a13ee1e Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Tue, 29 Jul 2025 13:50:11 -0400 Subject: [PATCH 06/10] feat(inferenceprofiles): implemented code review comments --- packages/@aws-cdk/aws-bedrock-alpha/README.md | 31 +++++ .../application-inference-profile.ts | 32 +++++- .../rosetta/default.ts-fixture | 1 + .../application-inference-profile.test.ts | 108 +++++++++++++++--- 4 files changed, 154 insertions(+), 18 deletions(-) diff --git a/packages/@aws-cdk/aws-bedrock-alpha/README.md b/packages/@aws-cdk/aws-bedrock-alpha/README.md index 7161daeb8b9b6..b21d79f2e883b 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/README.md +++ b/packages/@aws-cdk/aws-bedrock-alpha/README.md @@ -987,3 +987,34 @@ const importedProfile = bedrock.ApplicationInferenceProfile.fromApplicationInfer } ); ``` + +You can also import an application inference profile from an existing L1 CloudFormation construct: + +```ts fixture=default +// Create or reference an existing L1 CfnApplicationInferenceProfile +const cfnProfile = new aws_bedrock_cfn.CfnApplicationInferenceProfile(this, 'CfnProfile', { + inferenceProfileName: 'my-cfn-profile', + modelSource: { + copyFrom: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_3_5_SONNET_V1_0.invokableArn, + }, + description: 'Profile created via L1 construct', +}); + +// Import the L1 construct as an L2 ApplicationInferenceProfile +const importedFromCfn = bedrock.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); + +// The imported profile can now be used like any other ApplicationInferenceProfile +const agent = new bedrock.Agent(this, 'Agent', { + foundationModel: importedFromCfn, + instruction: 'You are a helpful assistant using an imported inference profile.', +}); + +// Grant permissions to use the imported profile +const lambdaFunction = new lambda.Function(this, 'MyFunction', { + runtime: lambda.Runtime.PYTHON_3_11, + handler: 'index.handler', + code: lambda.Code.fromInline('def handler(event, context): return "Hello"'), +}); + +importedFromCfn.grantProfileUsage(lambdaFunction); +``` diff --git a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts index a2e064b36f60d..85c4208bf062e 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/inference-profiles/application-inference-profile.ts @@ -15,9 +15,10 @@ export interface ApplicationInferenceProfileProps { /** * The name of the application inference profile. * This name will be used to identify the inference profile in the AWS console and APIs. - * If not provided, a name will be generated. + * - Required: Yes + * - Maximum length: 64 characters + * - Pattern: `^([0-9a-zA-Z:.][ _-]?)+$` * - * @default - A name will be generated by CloudFormation * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-bedrock-applicationinferenceprofile.html#cfn-bedrock-applicationinferenceprofile-inferenceprofilename */ readonly applicationInferenceProfileName: string; @@ -26,6 +27,8 @@ export interface ApplicationInferenceProfileProps { * Description of the inference profile. * Provides additional context about the purpose and usage of this inference profile. * + * - Maximum length: 200 characters when provided + * - Pattern: `^([0-9a-zA-Z:.][ _-]?)+$` * @default - No description is provided * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-bedrock-applicationinferenceprofile.html#cfn-bedrock-applicationinferenceprofile-description */ @@ -42,6 +45,7 @@ export interface ApplicationInferenceProfileProps { * The inference profile will route requests to the Regions defined in * the cross region (system-defined) inference profile that you choose. * Usage and costs for requests made to the Regions in the inference profile will be tracked. + * */ readonly modelSource: IBedrockInvokable; /** @@ -130,6 +134,13 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements public static fromCfnApplicationInferenceProfile( cfnApplicationInferenceProfile: bedrock.CfnApplicationInferenceProfile, ): IInferenceProfile { + // Singleton pattern that will prevent creating 2 or more Inference Profiles from the same L1 + const id = '@FromCfnApplicationInferenceProfile'; + const existing = cfnApplicationInferenceProfile.node.tryFindChild(id); + if (existing) { + return existing as unknown as IInferenceProfile; + } + return new (class extends InferenceProfileBase { public readonly inferenceProfileArn = cfnApplicationInferenceProfile.attrInferenceProfileArn; public readonly inferenceProfileId = cfnApplicationInferenceProfile.attrInferenceProfileId; @@ -143,7 +154,7 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements scope: this, }); } - })(cfnApplicationInferenceProfile, '@FromCfnApplicationInferenceProfile'); + })(cfnApplicationInferenceProfile, id); } // ------------------------------------------------------ @@ -272,10 +283,10 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements } // Validate applicationInferenceProfileName pattern - const namePattern = /^[0-9a-zA-Z]([0-9a-zA-Z]|[ _-][0-9a-zA-Z])*$/; + const namePattern = /^([0-9a-zA-Z:.][ _-]?)+$/; if (!namePattern.test(props.applicationInferenceProfileName)) { throw new ValidationError( - 'applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$', + 'applicationInferenceProfileName must match pattern ^([0-9a-zA-Z:.][ _-]?)+$', this, ); } @@ -289,6 +300,17 @@ export class ApplicationInferenceProfile extends InferenceProfileBase implements if (props.description !== undefined && props.description.length > 200) { throw new ValidationError('description cannot exceed 200 characters', this); } + + // Validate description pattern if provided + if (props.description !== undefined && props.description !== '') { + const descriptionPattern = /^([0-9a-zA-Z:.][ _-]?)+$/; + if (!descriptionPattern.test(props.description)) { + throw new ValidationError( + 'description must match pattern ^([0-9a-zA-Z:.][ _-]?)+$', + this, + ); + } + } } /** diff --git a/packages/@aws-cdk/aws-bedrock-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-bedrock-alpha/rosetta/default.ts-fixture index 197d5ef95fb72..0280ba65c7e86 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/rosetta/default.ts-fixture +++ b/packages/@aws-cdk/aws-bedrock-alpha/rosetta/default.ts-fixture @@ -2,6 +2,7 @@ import * as path from 'path'; import { Construct } from 'constructs'; import { Stack } from 'aws-cdk-lib'; +import { aws_bedrock as aws_bedrock_cfn } from 'aws-cdk-lib'; import * as bedrock from '@aws-cdk/aws-bedrock-alpha'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as s3 from 'aws-cdk-lib/aws-s3'; diff --git a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts index ec17976483f1e..f6d7e8dee0f81 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/test/bedrock/inference-profiles/application-inference-profile.test.ts @@ -214,13 +214,73 @@ describe('ApplicationInferenceProfile', () => { }); }); + test('accepts valid names with colons', () => { + const validNames = [ + 'test:profile', + 'My:Profile:123', + 'profile:1', + 'A:B:C', + 'test: profile', + 'profile :test', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfileColon${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + + test('accepts valid names with periods', () => { + const validNames = [ + 'test.profile', + 'My.Profile.123', + 'profile.1', + 'A.B.C', + 'test. profile', + 'profile .test', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfilePeriod${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + + test('accepts valid names with colons and periods mixed', () => { + const validNames = [ + 'test:profile.123', + 'My.Profile:123', + 'profile.1:test', + 'A:B.C', + 'test: profile.name', + 'profile .test:name', + ]; + + validNames.forEach((name, index) => { + expect(() => { + new bedrockAlpha.ApplicationInferenceProfile(stack, `TestProfileMixed${index}`, { + applicationInferenceProfileName: name, + modelSource: foundationModel, + }); + }).not.toThrow(); + }); + }); + test('throws error for names starting with space', () => { expect(() => { new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfile', { applicationInferenceProfileName: ' test', modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z:.][ _-]?)+$'); }); test('throws error for names starting with underscore', () => { @@ -229,7 +289,7 @@ describe('ApplicationInferenceProfile', () => { applicationInferenceProfileName: '_test', modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z:.][ _-]?)+$'); }); test('throws error for names starting with hyphen', () => { @@ -238,7 +298,7 @@ describe('ApplicationInferenceProfile', () => { applicationInferenceProfileName: '-test', modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z:.][ _-]?)+$'); }); test('throws error for names with invalid characters', () => { @@ -253,10 +313,8 @@ describe('ApplicationInferenceProfile', () => { 'test=profile', 'test!profile', 'test?profile', - 'test.profile', 'test,profile', 'test;profile', - 'test:profile', 'test|profile', 'test\\profile', 'test/profile', @@ -281,35 +339,35 @@ describe('ApplicationInferenceProfile', () => { applicationInferenceProfileName: name, modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z:.][ _-]?)+$'); }); }); - test('throws error for names ending with space', () => { + test('accepts names ending with space', () => { expect(() => { new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfileEndingSpace', { applicationInferenceProfileName: 'test ', modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).not.toThrow(); }); - test('throws error for names ending with underscore', () => { + test('accepts names ending with underscore', () => { expect(() => { new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfileEndingUnderscore', { applicationInferenceProfileName: 'test_', modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).not.toThrow(); }); - test('throws error for names ending with hyphen', () => { + test('accepts names ending with hyphen', () => { expect(() => { new bedrockAlpha.ApplicationInferenceProfile(stack, 'TestProfileEndingHyphen', { applicationInferenceProfileName: 'test-', modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).not.toThrow(); }); test('throws error for names with consecutive separators', () => { @@ -330,7 +388,7 @@ describe('ApplicationInferenceProfile', () => { applicationInferenceProfileName: name, modelSource: foundationModel, }); - }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z][ _-]?)+$'); + }).toThrow('applicationInferenceProfileName must match pattern ^([0-9a-zA-Z:.][ _-]?)+$'); }); }); }); @@ -494,6 +552,30 @@ describe('ApplicationInferenceProfile', () => { expect(importedProfile.inferenceProfileId).toBe(cfnProfile.attrInferenceProfileId); expect(importedProfile.type).toBe(bedrockAlpha.InferenceProfileType.APPLICATION); }); + + test('fromCfnApplicationInferenceProfile returns same instance when called multiple times (singleton pattern)', () => { + const cfnProfile = new bedrock.CfnApplicationInferenceProfile(stack, 'CfnProfile', { + inferenceProfileName: 'test-profile', + modelSource: { + copyFrom: foundationModel.invokableArn, + }, + }); + + // Call the method multiple times + const importedProfile1 = bedrockAlpha.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); + const importedProfile2 = bedrockAlpha.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); + const importedProfile3 = bedrockAlpha.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); + + // All calls should return the exact same instance (reference equality) + expect(importedProfile1).toBe(importedProfile2); + expect(importedProfile2).toBe(importedProfile3); + expect(importedProfile1).toBe(importedProfile3); + + // Verify the properties are still correct + expect(importedProfile1.inferenceProfileArn).toBe(cfnProfile.attrInferenceProfileArn); + expect(importedProfile1.inferenceProfileId).toBe(cfnProfile.attrInferenceProfileId); + expect(importedProfile1.type).toBe(bedrockAlpha.InferenceProfileType.APPLICATION); + }); }); describe('attributes', () => { From 645a0195b9d3999849e5fb3b30e6a483858e4d32 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Tue, 29 Jul 2025 15:26:24 -0400 Subject: [PATCH 07/10] feat(inferenceprofiles): added an example for fromCfnApplicationInferenceProfile --- packages/@aws-cdk/aws-bedrock-alpha/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/@aws-cdk/aws-bedrock-alpha/README.md b/packages/@aws-cdk/aws-bedrock-alpha/README.md index b21d79f2e883b..39916d00c732e 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/README.md +++ b/packages/@aws-cdk/aws-bedrock-alpha/README.md @@ -1003,12 +1003,6 @@ const cfnProfile = new aws_bedrock_cfn.CfnApplicationInferenceProfile(this, 'Cfn // Import the L1 construct as an L2 ApplicationInferenceProfile const importedFromCfn = bedrock.ApplicationInferenceProfile.fromCfnApplicationInferenceProfile(cfnProfile); -// The imported profile can now be used like any other ApplicationInferenceProfile -const agent = new bedrock.Agent(this, 'Agent', { - foundationModel: importedFromCfn, - instruction: 'You are a helpful assistant using an imported inference profile.', -}); - // Grant permissions to use the imported profile const lambdaFunction = new lambda.Function(this, 'MyFunction', { runtime: lambda.Runtime.PYTHON_3_11, From 23cdcb0e32be2f30cddcc196bae70981366e667e Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Wed, 30 Jul 2025 17:29:21 -0400 Subject: [PATCH 08/10] feat(agent): updating addAgentCollaborator in agent --- .../aws-bedrock-alpha/bedrock/agents/agent.ts | 10 +- .../test/bedrock/agents/agent.test.ts | 200 ++++++++++++++++++ 2 files changed, 206 insertions(+), 4 deletions(-) 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 b2673e27b85d7..f06d6d462d455 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts @@ -408,6 +408,7 @@ export class Agent extends AgentBase implements IAgent { * action groups associated with the ageny */ public readonly actionGroups: AgentActionGroup[] = []; + // ------------------------------------------------------ // CDK-only attributes // ------------------------------------------------------ @@ -428,6 +429,7 @@ export class Agent extends AgentBase implements IAgent { private readonly agentCollaboration?: AgentCollaboration; private readonly customOrchestrationExecutor?: CustomOrchestrationExecutor; private readonly promptOverrideConfiguration?: PromptOverrideConfiguration; + private readonly agentCollaborators: AgentCollaborator[] = []; private readonly __resource: bedrock.CfnAgent; // ------------------------------------------------------ @@ -638,9 +640,9 @@ export class Agent extends AgentBase implements IAgent { /** * Adds a collaborator to the agent and grants necessary permissions. * @param agentCollaborator - The collaborator to add - * @internal This method is used internally by the constructor and should not be called directly. */ - private addAgentCollaborator(agentCollaborator: AgentCollaborator) { + public addAgentCollaborator(agentCollaborator: AgentCollaborator) { + this.agentCollaborators.push(agentCollaborator); agentCollaborator.grant(this.role); } @@ -682,11 +684,11 @@ export class Agent extends AgentBase implements IAgent { * @internal This is an internal core function and should not be called directly. */ private renderAgentCollaborators(): bedrock.CfnAgent.AgentCollaboratorProperty[] | undefined { - if (!this.agentCollaboration) { + if (!this.agentCollaboration || this.agentCollaborators.length === 0) { return undefined; } - return this.agentCollaboration.collaborators.map(ac => ac._render()); + return this.agentCollaborators.map(ac => ac._render()); } /** 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 377d0ce51a839..5548d0d9ff69d 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 @@ -487,6 +487,206 @@ describe('Agent', () => { }); }); + describe('agent collaborators', () => { + test('addAgentCollaborator method adds collaborator and grants permissions', () => { + 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, + agentCollaboration: new bedrock.AgentCollaboration({ + type: bedrock.AgentCollaboratorType.SUPERVISOR, + collaborators: [], + }), + }); + + const collaboratorAlias = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias', { + aliasId: 'test-alias-id', + aliasName: 'TestAlias', + agentVersion: '1', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role', + }), + }); + + const collaborator = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias, + collaborationInstruction: 'Help with data analysis tasks', + collaboratorName: 'DataAnalyst', + relayConversationHistory: true, + }); + + // Add collaborator using the public method + agent.addAgentCollaborator(collaborator); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + AgentCollaboration: 'SUPERVISOR', + AgentCollaborators: [ + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Help with data analysis tasks', + CollaboratorName: 'DataAnalyst', + RelayConversationHistory: 'TO_COLLABORATOR', + }, + ], + }); + }); + + test('addAgentCollaborator method adds multiple collaborators', () => { + 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, + agentCollaboration: new bedrock.AgentCollaboration({ + type: bedrock.AgentCollaboratorType.SUPERVISOR, + collaborators: [], + }), + }); + + const collaboratorAlias1 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias1', { + aliasId: 'test-alias-id-1', + aliasName: 'TestAlias1', + agentVersion: '1', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent1', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-1', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-1', + }), + }); + + const collaboratorAlias2 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias2', { + aliasId: 'test-alias-id-2', + aliasName: 'TestAlias2', + agentVersion: '1', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent2', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-2', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-2', + }), + }); + + const collaborator1 = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias1, + collaborationInstruction: 'Help with data analysis tasks', + collaboratorName: 'DataAnalyst', + relayConversationHistory: true, + }); + + const collaborator2 = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias2, + collaborationInstruction: 'Help with research tasks', + collaboratorName: 'Researcher', + relayConversationHistory: false, + }); + + // Add multiple collaborators using the public method + agent.addAgentCollaborator(collaborator1); + agent.addAgentCollaborator(collaborator2); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + AgentCollaboration: 'SUPERVISOR', + AgentCollaborators: [ + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Help with data analysis tasks', + CollaboratorName: 'DataAnalyst', + RelayConversationHistory: 'TO_COLLABORATOR', + }, + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Help with research tasks', + CollaboratorName: 'Researcher', + RelayConversationHistory: 'DISABLED', + }, + ], + }); + }); + + test('addAgentCollaborator works with initial collaborators from props', () => { + const collaboratorAlias1 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias1', { + aliasId: 'test-alias-id-1', + aliasName: 'TestAlias1', + agentVersion: '1', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent1', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-1', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-1', + }), + }); + + const collaboratorAlias2 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias2', { + aliasId: 'test-alias-id-2', + aliasName: 'TestAlias2', + agentVersion: '1', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent2', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-2', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-2', + }), + }); + + const initialCollaborator = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias1, + collaborationInstruction: 'Initial collaborator from props', + collaboratorName: 'InitialCollaborator', + relayConversationHistory: true, + }); + + const agentCollaboration = new bedrock.AgentCollaboration({ + type: bedrock.AgentCollaboratorType.SUPERVISOR, + collaborators: [initialCollaborator], + }); + + 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, + agentCollaboration, + }); + + const dynamicCollaborator = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias2, + collaborationInstruction: 'Dynamic collaborator added later', + collaboratorName: 'DynamicCollaborator', + relayConversationHistory: false, + }); + + // Add collaborator dynamically using the public method + agent.addAgentCollaborator(dynamicCollaborator); + + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + AgentCollaboration: 'SUPERVISOR', + AgentCollaborators: [ + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Initial collaborator from props', + CollaboratorName: 'InitialCollaborator', + RelayConversationHistory: 'TO_COLLABORATOR', + }, + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Dynamic collaborator added later', + CollaboratorName: 'DynamicCollaborator', + RelayConversationHistory: 'DISABLED', + }, + ], + }); + }); + }); + describe('edge cases', () => { test('handles undefined agentCollaborators', () => { new bedrock.Agent(stack, 'TestAgent', { From 392bdbd716547053bc6620da6c1e4225da82bd23 Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Wed, 30 Jul 2025 17:51:10 -0400 Subject: [PATCH 09/10] feat(agent): updating addAgentCollaborator name to grantPermissionToAgent in agent --- .../aws-bedrock-alpha/bedrock/agents/agent.ts | 18 ++- .../test/bedrock/agents/agent.test.ts | 152 +++--------------- 2 files changed, 29 insertions(+), 141 deletions(-) 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 f06d6d462d455..3e9becd0f1879 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts @@ -429,7 +429,6 @@ export class Agent extends AgentBase implements IAgent { private readonly agentCollaboration?: AgentCollaboration; private readonly customOrchestrationExecutor?: CustomOrchestrationExecutor; private readonly promptOverrideConfiguration?: PromptOverrideConfiguration; - private readonly agentCollaborators: AgentCollaborator[] = []; private readonly __resource: bedrock.CfnAgent; // ------------------------------------------------------ @@ -516,7 +515,7 @@ export class Agent extends AgentBase implements IAgent { this.agentCollaboration = props.agentCollaboration; if (props.agentCollaboration) { props.agentCollaboration.collaborators.forEach(ac => { - this.addAgentCollaborator(ac); + this.grantPermissionToAgent(ac); }); } @@ -638,11 +637,14 @@ export class Agent extends AgentBase implements IAgent { } /** - * Adds a collaborator to the agent and grants necessary permissions. - * @param agentCollaborator - The collaborator to add + * Grants permissions for an agent collaborator to this agent's role. + * This method only grants IAM permissions and does not add the collaborator + * to the agent's collaboration configuration. To add collaborators to the + * agent configuration, include them in the AgentCollaboration when creating the agent. + * + * @param agentCollaborator - The collaborator to grant permissions for */ - public addAgentCollaborator(agentCollaborator: AgentCollaborator) { - this.agentCollaborators.push(agentCollaborator); + public grantPermissionToAgent(agentCollaborator: AgentCollaborator) { agentCollaborator.grant(this.role); } @@ -684,11 +686,11 @@ export class Agent extends AgentBase implements IAgent { * @internal This is an internal core function and should not be called directly. */ private renderAgentCollaborators(): bedrock.CfnAgent.AgentCollaboratorProperty[] | undefined { - if (!this.agentCollaboration || this.agentCollaborators.length === 0) { + if (!this.agentCollaboration || !this.agentCollaboration.collaborators || this.agentCollaboration.collaborators.length === 0) { return undefined; } - return this.agentCollaborators.map(ac => ac._render()); + return this.agentCollaboration.collaborators.map(ac => ac._render()); } /** 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 5548d0d9ff69d..6f751d1bbd975 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 @@ -488,7 +488,7 @@ describe('Agent', () => { }); describe('agent collaborators', () => { - test('addAgentCollaborator method adds collaborator and grants permissions', () => { + test('grantPermissionToAgent method grants permissions for collaborators', () => { 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, @@ -515,150 +515,46 @@ describe('Agent', () => { relayConversationHistory: true, }); - // Add collaborator using the public method - agent.addAgentCollaborator(collaborator); + // Grant permissions using the public method (this only grants IAM permissions) + agent.grantPermissionToAgent(collaborator); + // Since grantPermissionToAgent only grants permissions and doesn't add to collaboration, + // the agent should have no collaborators in the CloudFormation template Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { AgentCollaboration: 'SUPERVISOR', - AgentCollaborators: [ - { - AgentDescriptor: { - AliasArn: Match.objectLike({ - 'Fn::Join': Match.anyValue(), - }), - }, - CollaborationInstruction: 'Help with data analysis tasks', - CollaboratorName: 'DataAnalyst', - RelayConversationHistory: 'TO_COLLABORATOR', - }, - ], + AgentCollaborators: Match.absent(), }); }); - test('addAgentCollaborator method adds multiple collaborators', () => { - 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, - agentCollaboration: new bedrock.AgentCollaboration({ - type: bedrock.AgentCollaboratorType.SUPERVISOR, - collaborators: [], - }), - }); - - const collaboratorAlias1 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias1', { - aliasId: 'test-alias-id-1', - aliasName: 'TestAlias1', - agentVersion: '1', - agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent1', { - agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-1', - roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-1', - }), - }); - - const collaboratorAlias2 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias2', { - aliasId: 'test-alias-id-2', - aliasName: 'TestAlias2', + test('collaborators from AgentCollaboration props are rendered correctly', () => { + const collaboratorAlias = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias', { + aliasId: 'test-alias-id', + aliasName: 'TestAlias', agentVersion: '1', - agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent2', { - agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-2', - roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-2', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role', }), }); - const collaborator1 = new bedrock.AgentCollaborator({ - agentAlias: collaboratorAlias1, + const collaborator = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias, collaborationInstruction: 'Help with data analysis tasks', collaboratorName: 'DataAnalyst', relayConversationHistory: true, }); - const collaborator2 = new bedrock.AgentCollaborator({ - agentAlias: collaboratorAlias2, - collaborationInstruction: 'Help with research tasks', - collaboratorName: 'Researcher', - relayConversationHistory: false, - }); - - // Add multiple collaborators using the public method - agent.addAgentCollaborator(collaborator1); - agent.addAgentCollaborator(collaborator2); - - Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { - AgentCollaboration: 'SUPERVISOR', - AgentCollaborators: [ - { - AgentDescriptor: { - AliasArn: Match.objectLike({ - 'Fn::Join': Match.anyValue(), - }), - }, - CollaborationInstruction: 'Help with data analysis tasks', - CollaboratorName: 'DataAnalyst', - RelayConversationHistory: 'TO_COLLABORATOR', - }, - { - AgentDescriptor: { - AliasArn: Match.objectLike({ - 'Fn::Join': Match.anyValue(), - }), - }, - CollaborationInstruction: 'Help with research tasks', - CollaboratorName: 'Researcher', - RelayConversationHistory: 'DISABLED', - }, - ], - }); - }); - - test('addAgentCollaborator works with initial collaborators from props', () => { - const collaboratorAlias1 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias1', { - aliasId: 'test-alias-id-1', - aliasName: 'TestAlias1', - agentVersion: '1', - agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent1', { - agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-1', - roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-1', - }), - }); - - const collaboratorAlias2 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias2', { - aliasId: 'test-alias-id-2', - aliasName: 'TestAlias2', - agentVersion: '1', - agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent2', { - agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-2', - roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-2', - }), - }); - - const initialCollaborator = new bedrock.AgentCollaborator({ - agentAlias: collaboratorAlias1, - collaborationInstruction: 'Initial collaborator from props', - collaboratorName: 'InitialCollaborator', - relayConversationHistory: true, - }); - const agentCollaboration = new bedrock.AgentCollaboration({ type: bedrock.AgentCollaboratorType.SUPERVISOR, - collaborators: [initialCollaborator], + collaborators: [collaborator], }); - const agent = new bedrock.Agent(stack, 'TestAgent', { + new bedrock.Agent(stack, 'TestAgent', { instruction: 'This is a test instruction that must be at least 40 characters long to be valid', foundationModel, agentCollaboration, }); - const dynamicCollaborator = new bedrock.AgentCollaborator({ - agentAlias: collaboratorAlias2, - collaborationInstruction: 'Dynamic collaborator added later', - collaboratorName: 'DynamicCollaborator', - relayConversationHistory: false, - }); - - // Add collaborator dynamically using the public method - agent.addAgentCollaborator(dynamicCollaborator); - Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { AgentCollaboration: 'SUPERVISOR', AgentCollaborators: [ @@ -668,20 +564,10 @@ describe('Agent', () => { 'Fn::Join': Match.anyValue(), }), }, - CollaborationInstruction: 'Initial collaborator from props', - CollaboratorName: 'InitialCollaborator', + CollaborationInstruction: 'Help with data analysis tasks', + CollaboratorName: 'DataAnalyst', RelayConversationHistory: 'TO_COLLABORATOR', }, - { - AgentDescriptor: { - AliasArn: Match.objectLike({ - 'Fn::Join': Match.anyValue(), - }), - }, - CollaborationInstruction: 'Dynamic collaborator added later', - CollaboratorName: 'DynamicCollaborator', - RelayConversationHistory: 'DISABLED', - }, ], }); }); From b960d25ee4d79970b8f154a347b40a07e65735ca Mon Sep 17 00:00:00 2001 From: dinsajwa Date: Thu, 31 Jul 2025 10:28:37 -0400 Subject: [PATCH 10/10] feat(agent): updating grantPermissionToAgent to private --- .../aws-bedrock-alpha/bedrock/agents/agent.ts | 2 +- .../test/bedrock/agents/agent.test.ts | 113 +++++++++++++++--- 2 files changed, 99 insertions(+), 16 deletions(-) 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 3e9becd0f1879..607b4c4d8cdce 100644 --- a/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts +++ b/packages/@aws-cdk/aws-bedrock-alpha/bedrock/agents/agent.ts @@ -644,7 +644,7 @@ export class Agent extends AgentBase implements IAgent { * * @param agentCollaborator - The collaborator to grant permissions for */ - public grantPermissionToAgent(agentCollaborator: AgentCollaborator) { + private grantPermissionToAgent(agentCollaborator: AgentCollaborator) { agentCollaborator.grant(this.role); } 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 6f751d1bbd975..5ab6851b367b7 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 @@ -488,16 +488,7 @@ describe('Agent', () => { }); describe('agent collaborators', () => { - test('grantPermissionToAgent method grants permissions for collaborators', () => { - 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, - agentCollaboration: new bedrock.AgentCollaboration({ - type: bedrock.AgentCollaboratorType.SUPERVISOR, - collaborators: [], - }), - }); - + test('agent with collaborators via AgentCollaboration constructor renders correctly in CFN template', () => { const collaboratorAlias = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias', { aliasId: 'test-alias-id', aliasName: 'TestAlias', @@ -515,14 +506,106 @@ describe('Agent', () => { relayConversationHistory: true, }); - // Grant permissions using the public method (this only grants IAM permissions) - agent.grantPermissionToAgent(collaborator); + const agentCollaboration = new bedrock.AgentCollaboration({ + type: bedrock.AgentCollaboratorType.SUPERVISOR, + collaborators: [collaborator], + }); + + new bedrock.Agent(stack, 'TestAgent', { + instruction: 'This is a test instruction that must be at least 40 characters long to be valid', + foundationModel, + agentCollaboration, + }); - // Since grantPermissionToAgent only grants permissions and doesn't add to collaboration, - // the agent should have no collaborators in the CloudFormation template + // Verify that the agent has the correct collaboration configuration in the CFN template Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { AgentCollaboration: 'SUPERVISOR', - AgentCollaborators: Match.absent(), + AgentCollaborators: [ + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Help with data analysis tasks', + CollaboratorName: 'DataAnalyst', + RelayConversationHistory: 'TO_COLLABORATOR', + }, + ], + }); + }); + + test('agent with multiple collaborators via AgentCollaboration constructor', () => { + const collaboratorAlias1 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias1', { + aliasId: 'test-alias-id-1', + aliasName: 'TestAlias1', + agentVersion: '1', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent1', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-1', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-1', + }), + }); + + const collaboratorAlias2 = bedrock.AgentAlias.fromAttributes(stack, 'CollaboratorAlias2', { + aliasId: 'test-alias-id-2', + aliasName: 'TestAlias2', + agentVersion: '2', + agent: bedrock.Agent.fromAgentAttributes(stack, 'CollaboratorAgent2', { + agentArn: 'arn:aws:bedrock:us-east-1:123456789012:agent/collaborator-agent-id-2', + roleArn: 'arn:aws:iam::123456789012:role/collaborator-role-2', + }), + }); + + const collaborator1 = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias1, + collaborationInstruction: 'Help with data analysis tasks', + collaboratorName: 'DataAnalyst', + relayConversationHistory: true, + }); + + const collaborator2 = new bedrock.AgentCollaborator({ + agentAlias: collaboratorAlias2, + collaborationInstruction: 'Help with code generation tasks', + collaboratorName: 'CodeGenerator', + relayConversationHistory: false, + }); + + const agentCollaboration = new bedrock.AgentCollaboration({ + type: bedrock.AgentCollaboratorType.SUPERVISOR, + collaborators: [collaborator1, collaborator2], + }); + + new bedrock.Agent(stack, 'TestAgent', { + instruction: 'This is a test instruction that must be at least 40 characters long to be valid', + foundationModel, + agentCollaboration, + }); + + // Verify that the agent has multiple collaborators in the CFN template + Template.fromStack(stack).hasResourceProperties('AWS::Bedrock::Agent', { + AgentCollaboration: 'SUPERVISOR', + AgentCollaborators: [ + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Help with data analysis tasks', + CollaboratorName: 'DataAnalyst', + RelayConversationHistory: 'TO_COLLABORATOR', + }, + { + AgentDescriptor: { + AliasArn: Match.objectLike({ + 'Fn::Join': Match.anyValue(), + }), + }, + CollaborationInstruction: 'Help with code generation tasks', + CollaboratorName: 'CodeGenerator', + RelayConversationHistory: 'DISABLED', + }, + ], }); });