diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/InvokeModelDefaultTestDeployAssert9C0D2DFC.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/InvokeModelDefaultTestDeployAssert9C0D2DFC.assets.json index 62c1eb1585be8..81419156e1160 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/InvokeModelDefaultTestDeployAssert9C0D2DFC.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/InvokeModelDefaultTestDeployAssert9C0D2DFC.assets.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "36.0.5", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.assets.json index ea5387a06b82c..aab5d8193de11 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.assets.json @@ -1,7 +1,7 @@ { - "version": "36.0.0", + "version": "36.0.5", "files": { - "e9d946bbac52fb88d3fc7c9ea4f26da0e6a6965417c8ae9fb5464e5342269444": { + "b6d22af12502fb22f7fee80b3981c8767eb09e8e9f4ab927a380544cdcdc384b": { "source": { "path": "aws-stepfunctions-tasks-bedrock-invoke-model-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "e9d946bbac52fb88d3fc7c9ea4f26da0e6a6965417c8ae9fb5464e5342269444.json", + "objectKey": "b6d22af12502fb22f7fee80b3981c8767eb09e8e9f4ab927a380544cdcdc384b.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.template.json index a67f72f4ea158..ce041c6ffeaac 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/aws-stepfunctions-tasks-bedrock-invoke-model-integ.template.json @@ -103,7 +103,7 @@ { "Ref": "AWS::Region" }, - "::foundation-model/amazon.titan-text-express-v1\",\"Body\":{\"inputText.$\":\"States.Format('Alphabetize this list of first names:\\n{}', $.names)\",\"textGenerationConfig\":{\"maxTokenCount\":100,\"temperature\":1}}}},\"Prompt3\":{\"End\":true,\"Type\":\"Task\",\"InputPath\":\"$.names\",\"OutputPath\":\"$.names\",\"Resource\":\"arn:", + "::foundation-model/amazon.titan-text-express-v1\",\"Body\":{\"inputText.$\":\"States.Format('Alphabetize this list of first names:/n{}', $.names)\",\"textGenerationConfig\":{\"maxTokenCount\":100,\"temperature\":1}}}},\"Prompt3\":{\"Next\":\"Prompt4\",\"Type\":\"Task\",\"OutputPath\":\"$.Body.results[0].outputText\",\"Resource\":\"arn:", { "Ref": "AWS::Partition" }, @@ -115,7 +115,19 @@ { "Ref": "AWS::Region" }, - "::foundation-model/amazon.titan-text-express-v1\",\"Input\":{\"S3Uri.$\":\"$.names\"},\"Output\":{\"S3Uri.$\":\"$.names\"}}}},\"TimeoutSeconds\":30}" + "::foundation-model/amazon.titan-text-express-v1\",\"Body\":{\"inputText.$\":\"States.Format('Echo list of first names: {}', $.names)\",\"textGenerationConfig\":{\"maxTokenCount\":100,\"temperature\":1}}}},\"Prompt4\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::bedrock:invokeModel\",\"Parameters\":{\"ModelId\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + "::foundation-model/amazon.titan-text-express-v1\",\"Input\":{\"S3Uri\":\"$.names\"},\"Output\":{\"S3Uri\":\"$.names\"}}}},\"TimeoutSeconds\":30}" ] ] }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/cdk.out index 1f0068d32659a..bd5311dc372de 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"36.0.0"} \ No newline at end of file +{"version":"36.0.5"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/integ.json index 36143be21ab57..6e638249caa25 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "36.0.5", "testCases": { "InvokeModel/DefaultTest": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/manifest.json index 677092f63b132..085c560bba157 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "36.0.0", + "version": "36.0.5", "artifacts": { "aws-stepfunctions-tasks-bedrock-invoke-model-integ.assets": { "type": "cdk:asset-manifest", @@ -18,7 +18,7 @@ "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}/e9d946bbac52fb88d3fc7c9ea4f26da0e6a6965417c8ae9fb5464e5342269444.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b6d22af12502fb22f7fee80b3981c8767eb09e8e9f4ab927a380544cdcdc384b.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/tree.json index 17b23007671bd..217a79721df41 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.js.snapshot/tree.json @@ -32,6 +32,14 @@ "version": "0.0.0" } }, + "Prompt4": { + "id": "Prompt4", + "path": "aws-stepfunctions-tasks-bedrock-invoke-model-integ/Prompt4", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_stepfunctions_tasks.BedrockInvokeModel", + "version": "0.0.0" + } + }, "StateMachine": { "id": "StateMachine", "path": "aws-stepfunctions-tasks-bedrock-invoke-model-integ/StateMachine", @@ -186,7 +194,19 @@ { "Ref": "AWS::Region" }, - "::foundation-model/amazon.titan-text-express-v1\",\"Body\":{\"inputText.$\":\"States.Format('Alphabetize this list of first names:\\n{}', $.names)\",\"textGenerationConfig\":{\"maxTokenCount\":100,\"temperature\":1}}}},\"Prompt3\":{\"End\":true,\"Type\":\"Task\",\"InputPath\":\"$.names\",\"OutputPath\":\"$.names\",\"Resource\":\"arn:", + "::foundation-model/amazon.titan-text-express-v1\",\"Body\":{\"inputText.$\":\"States.Format('Alphabetize this list of first names:/n{}', $.names)\",\"textGenerationConfig\":{\"maxTokenCount\":100,\"temperature\":1}}}},\"Prompt3\":{\"Next\":\"Prompt4\",\"Type\":\"Task\",\"OutputPath\":\"$.Body.results[0].outputText\",\"Resource\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":states:::bedrock:invokeModel\",\"Parameters\":{\"ModelId\":\"arn:", + { + "Ref": "AWS::Partition" + }, + ":bedrock:", + { + "Ref": "AWS::Region" + }, + "::foundation-model/amazon.titan-text-express-v1\",\"Body\":{\"inputText.$\":\"States.Format('Echo list of first names: {}', $.names)\",\"textGenerationConfig\":{\"maxTokenCount\":100,\"temperature\":1}}}},\"Prompt4\":{\"End\":true,\"Type\":\"Task\",\"Resource\":\"arn:", { "Ref": "AWS::Partition" }, @@ -198,7 +218,7 @@ { "Ref": "AWS::Region" }, - "::foundation-model/amazon.titan-text-express-v1\",\"Input\":{\"S3Uri.$\":\"$.names\"},\"Output\":{\"S3Uri.$\":\"$.names\"}}}},\"TimeoutSeconds\":30}" + "::foundation-model/amazon.titan-text-express-v1\",\"Input\":{\"S3Uri\":\"$.names\"},\"Output\":{\"S3Uri\":\"$.names\"}}}},\"TimeoutSeconds\":30}" ] ] }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.ts index 2d98f804b4475..7ae58db6d8692 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/bedrock/integ.invoke-model.ts @@ -37,7 +37,7 @@ const prompt2 = new BedrockInvokeModel(stack, 'Prompt2', { body: sfn.TaskInput.fromObject( { inputText: sfn.JsonPath.format( - 'Alphabetize this list of first names:\n{}', + 'Alphabetize this list of first names:/n{}', sfn.JsonPath.stringAt('$.names'), ), textGenerationConfig: { @@ -52,13 +52,33 @@ const prompt2 = new BedrockInvokeModel(stack, 'Prompt2', { resultPath: '$', }); +/** Test for Bedrock Output Path */ const prompt3 = new BedrockInvokeModel(stack, 'Prompt3', { model, - inputPath: sfn.JsonPath.stringAt('$.names'), - outputPath: sfn.JsonPath.stringAt('$.names'), + body: sfn.TaskInput.fromObject( + { + inputText: sfn.JsonPath.format( + 'Echo list of first names: {}', + sfn.JsonPath.stringAt('$.names'), + ), + textGenerationConfig: { + maxTokenCount: 100, + temperature: 1, + }, + }, + ), + outputPath: '$.Body.results[0].outputText', +}); + +/** Test for Bedrock s3 URI Path */ +//State Machine Execution will fail for the following input as it expects a valid s3 URI from previous prompt +const prompt4 = new BedrockInvokeModel(stack, 'Prompt4', { + model, + input: { s3InputUri: '$.names' }, + output: { s3OutputUri: '$.names' }, }); -const chain = sfn.Chain.start(prompt1).next(prompt2).next(prompt3); +const chain = sfn.Chain.start(prompt1).next(prompt2).next(prompt3).next(prompt4); new sfn.StateMachine(stack, 'StateMachine', { definitionBody: sfn.DefinitionBody.fromChainable(chain), diff --git a/packages/@aws-cdk/cx-api/FEATURE_FLAGS.md b/packages/@aws-cdk/cx-api/FEATURE_FLAGS.md index 9e980cc71faae..fdc3e50d23051 100644 --- a/packages/@aws-cdk/cx-api/FEATURE_FLAGS.md +++ b/packages/@aws-cdk/cx-api/FEATURE_FLAGS.md @@ -71,6 +71,8 @@ Flags come in three types: | [@aws-cdk/pipelines:reduceAssetRoleTrustScope](#aws-cdkpipelinesreduceassetroletrustscope) | Remove the root account principal from PipelineAssetsFileRole trust policy | 2.141.0 | (default) | | [@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm](#aws-cdkaws-ecsremovedefaultdeploymentalarm) | When enabled, remove default deployment alarm settings | 2.143.0 | (default) | | [@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault](#aws-cdkcustom-resourceslogapiresponsedatapropertytruedefault) | When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default | 2.145.0 | (fix) | +| [@aws-cdk/aws-s3:keepNotificationInImportedBucket](#aws-cdkaws-s3keepnotificationinimportedbucket) | When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack. | 2.155.0 | (fix) | +| [@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask](#aws-cdkaws-stepfunctions-tasksusenews3uriparametersforbedrockinvokemodeltask) | When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model. | V2NEXT | (fix) | @@ -131,7 +133,9 @@ The following json shows the current recommended set of flags, as `cdk init` wou "@aws-cdk/aws-eks:nodegroupNameAttribute": true, "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, - "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": true } } ``` @@ -1338,4 +1342,38 @@ property from the event object. | 2.145.0 | `false` | `false` | +### @aws-cdk/aws-s3:keepNotificationInImportedBucket + +*When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack.* (fix) + +Currently, adding notifications to a bucket where it was created by ourselves will override notification added where it is imported. + +When this feature flag is enabled, adding notifications to a bucket in the current stack will only update notification defined in this stack. +Other notifications that are not managed by this stack will be kept. + + +| Since | Default | Recommended | +| ----- | ----- | ----- | +| (not in v1) | | | +| 2.155.0 | `false` | `false` | + + +### @aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask + +*When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.* (fix) + +Currently, 'inputPath' and 'outputPath' from the TaskStateBase Props is being used under BedrockInvokeModelProps to define S3URI under 'input' and 'output' fields +of State Machine Task definition. + +When this feature flag is enabled, specify newly introduced props 's3InputUri' and +'s3OutputUri' to populate S3 uri under input and output fields in state machine task definition for Bedrock invoke model. + + +| Since | Default | Recommended | +| ----- | ----- | ----- | +| (not in v1) | | | +| V2NEXT | `true` | `true` | + +**Compatibility with old behavior:** Disable the feature flag to use input and output path fields for s3 URI + diff --git a/packages/aws-cdk-lib/aws-stepfunctions-tasks/README.md b/packages/aws-cdk-lib/aws-stepfunctions-tasks/README.md index a8175c94cc8ff..2067a10332668 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions-tasks/README.md +++ b/packages/aws-cdk-lib/aws-stepfunctions-tasks/README.md @@ -398,11 +398,48 @@ const task = new tasks.BedrockInvokeModel(this, 'Prompt Model', { names: sfn.JsonPath.stringAt('$.Body.results[0].outputText'), }, }); + +``` +### Using Input Path for S3 URI + +Provide S3 URI as an input or output path to invoke a model + +To specify the S3 URI as JSON path to your input or output fields, use props `s3InputUri` and `s3OutputUri` under BedrockInvokeModelProps and set +feature flag `@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask` to true. + + +If this flag is not enabled, the code will populate the S3Uri using `InputPath` and `OutputPath` fields, which is not recommended. + +```ts + +import * as bedrock from 'aws-cdk-lib/aws-bedrock'; + +const model = bedrock.FoundationModel.fromFoundationModelId( + this, + 'Model', + bedrock.FoundationModelIdentifier.AMAZON_TITAN_TEXT_G1_EXPRESS_V1, +); + +const task = new tasks.BedrockInvokeModel(this, 'Prompt Model', { + model, + input : { s3InputUri: sfn.JsonPath.stringAt('$.prompt') }, + output: { s3OutputUri: sfn.JsonPath.stringAt('$.prompt') }, +}); + ``` + ### Using Input Path Provide S3 URI as an input or output path to invoke a model +Currently, input and output Path provided in the BedrockInvokeModelProps input is defined as S3URI field under task definition of state machine. +To modify the existing behaviour, set `@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask` to true. + +If this feature flag is enabled, S3URI fields will be generated from other Props(`s3InputUri` and `s3OutputUri`), and the given inputPath, OutputPath will be rendered as +it is in the JSON task definition. + +If the feature flag is set to `false`, the behavior will be to populate the S3Uri using the `InputPath` and `OutputPath` fields, which is not recommended. + ```ts import * as bedrock from 'aws-cdk-lib/aws-bedrock'; diff --git a/packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/invoke-model.ts b/packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/invoke-model.ts index 3abc7338cab8c..6d789ff85a356 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/invoke-model.ts +++ b/packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/invoke-model.ts @@ -4,7 +4,8 @@ import * as bedrock from '../../../aws-bedrock'; import * as iam from '../../../aws-iam'; import * as s3 from '../../../aws-s3'; import * as sfn from '../../../aws-stepfunctions'; -import { Stack } from '../../../core'; +import { Annotations, Stack, FeatureFlags } from '../../../core'; +import * as cxapi from '../../../cx-api'; import { integrationResourceArn, validatePatternSupported } from '../private/task-utils'; /** @@ -22,6 +23,15 @@ export interface BedrockInvokeModelInputProps { * @default - Input data is retrieved from the `body` field */ readonly s3Location?: s3.Location; + + /** + * The source location where the API response is written. + * + * This field can be used to specify s3 URI in the form of token + * + * @default - The API response body is returned in the result. + */ + readonly s3InputUri?: string; } /** @@ -40,6 +50,15 @@ export interface BedrockInvokeModelOutputProps { * @default - Response body is returned in the task result */ readonly s3Location?: s3.Location; + + /** + * The destination location where the API response is written. + * + * This field can be used to specify s3 URI in the form of token + * + * @default - The API response body is returned in the result. + */ + readonly s3OutputUri?: string; } /** @@ -145,9 +164,17 @@ export class BedrockInvokeModel extends sfn.TaskStateBase { validatePatternSupported(this.integrationPattern, BedrockInvokeModel.SUPPORTED_INTEGRATION_PATTERNS); + const useNewS3UriParamsForTask = FeatureFlags.of(this).isEnabled(cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK); + const isBodySpecified = props.body !== undefined; - //Either specific props.input with bucket name and object key or input s3 path - const isInputSpecified = (props.input !== undefined && props.input.s3Location !== undefined) || (props.inputPath !== undefined); + + let isInputSpecified: boolean; + if (!useNewS3UriParamsForTask) { + isInputSpecified = (props.input !== undefined && props.input.s3Location !== undefined) || (props.inputPath !== undefined); + } else { + //Either specific props.input with bucket name and object key or input s3 path + isInputSpecified = props.input!==undefined ? props.input?.s3Location !== undefined || props.input?.s3InputUri !== undefined : false; + } if (isBodySpecified && isInputSpecified) { throw new Error('Either `body` or `input` must be specified, but not both.'); @@ -161,11 +188,24 @@ export class BedrockInvokeModel extends sfn.TaskStateBase { if (props.output?.s3Location?.objectVersion !== undefined) { throw new Error('Output S3 object version is not supported.'); } + if (props.input?.s3InputUri && props.input.s3Location || props.output?.s3OutputUri && props.output.s3Location) { + throw new Error('Either specify S3 Uri or S3 location, but not both.'); + } + if (useNewS3UriParamsForTask && (props.input?.s3InputUri === '' || props.output?.s3OutputUri === '')) { + throw new Error('S3 Uri cannot be an empty string'); + } + + //Warning to let users know about the newly introduced props + if (props.inputPath || props.outputPath && !useNewS3UriParamsForTask) { + Annotations.of(scope).addWarningV2('aws-cdk-lib/aws-stepfunctions-taks', + 'These props will set the value of inputPath/outputPath as s3 URI under input/output field in state machine JSON definition. To modify the behaviour set feature flag `@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": true` and use props input.s3InputUri/output.s3OutputUri'); + } this.taskPolicies = this.renderPolicyStatements(); } private renderPolicyStatements(): iam.PolicyStatement[] { + const useNewS3UriParamsForTask = FeatureFlags.of(this).isEnabled(cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK); const policyStatements = [ new iam.PolicyStatement({ actions: ['bedrock:InvokeModel'], @@ -173,7 +213,8 @@ export class BedrockInvokeModel extends sfn.TaskStateBase { }), ]; - if (this.props.inputPath !== undefined) { + //For Compatibility with existing behaviour of input path + if (this.props.input?.s3InputUri !== undefined || (!useNewS3UriParamsForTask && this.props.inputPath !== undefined)) { policyStatements.push( new iam.PolicyStatement({ actions: ['s3:GetObject'], @@ -204,7 +245,8 @@ export class BedrockInvokeModel extends sfn.TaskStateBase { ); } - if (this.props.outputPath !== undefined) { + //For Compatibility with existing behaviour of output path + if (this.props.output?.s3OutputUri !== undefined || (!useNewS3UriParamsForTask && this.props.outputPath !== undefined)) { policyStatements.push( new iam.PolicyStatement({ actions: ['s3:PutObject'], @@ -262,6 +304,10 @@ export class BedrockInvokeModel extends sfn.TaskStateBase { * @internal */ protected _renderTask(): any { + + const useNewS3UriParamsForTask = FeatureFlags.of(this).isEnabled(cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK); + const inputSource = this.getInputSource(this.props.input, this.props.inputPath, useNewS3UriParamsForTask); + const outputSource = this.getOutputSource(this.props.output, this.props.outputPath, useNewS3UriParamsForTask); return { Resource: integrationResourceArn('bedrock', 'invokeModel'), Parameters: sfn.FieldUtils.renderObject({ @@ -269,12 +315,8 @@ export class BedrockInvokeModel extends sfn.TaskStateBase { Accept: this.props.accept, ContentType: this.props.contentType, Body: this.props.body?.value, - Input: this.props.input?.s3Location ? { - S3Uri: `s3://${this.props.input.s3Location.bucketName}/${this.props.input.s3Location.objectKey}`, - } : this.props.inputPath ? { S3Uri: this.props.inputPath } : undefined, - Output: this.props.output?.s3Location ? { - S3Uri: `s3://${this.props.output.s3Location.bucketName}/${this.props.output.s3Location.objectKey}`, - } : this.props.outputPath ? { S3Uri: this.props.outputPath }: undefined, + Input: inputSource ? { S3Uri: inputSource } : undefined, + Output: outputSource ? { S3Uri: outputSource } : undefined, GuardrailIdentifier: this.props.guardrail?.guardrailIdentifier, GuardrailVersion: this.props.guardrail?.guardrailVersion, Trace: this.props.traceEnabled === undefined @@ -285,5 +327,27 @@ export class BedrockInvokeModel extends sfn.TaskStateBase { }), }; }; + + private getInputSource(props?: BedrockInvokeModelInputProps, inputPath?: string, useNewS3UriParamsForTask?: boolean): string | undefined { + if (props?.s3Location) { + return `s3://${props.s3Location.bucketName}/${props.s3Location.objectKey}`; + } else if (useNewS3UriParamsForTask && props?.s3InputUri) { + return props.s3InputUri; + } else if (!useNewS3UriParamsForTask && inputPath) { + return inputPath; + } + return undefined; + } + + private getOutputSource(props?: BedrockInvokeModelOutputProps, outputPath?: string, useNewS3UriParamsForTask?: boolean): string | undefined { + if (props?.s3Location) { + return `s3://${props.s3Location.bucketName}/${props.s3Location.objectKey}`; + } else if (useNewS3UriParamsForTask && props?.s3OutputUri) { + return props.s3OutputUri; + } else if (!useNewS3UriParamsForTask && outputPath) { + return outputPath; + } + return undefined; + } } diff --git a/packages/aws-cdk-lib/aws-stepfunctions-tasks/test/bedrock/invoke-model.test.ts b/packages/aws-cdk-lib/aws-stepfunctions-tasks/test/bedrock/invoke-model.test.ts index 02fa2737bb2b8..869be41943937 100644 --- a/packages/aws-cdk-lib/aws-stepfunctions-tasks/test/bedrock/invoke-model.test.ts +++ b/packages/aws-cdk-lib/aws-stepfunctions-tasks/test/bedrock/invoke-model.test.ts @@ -2,6 +2,7 @@ import { Template, Match } from '../../../assertions'; import * as bedrock from '../../../aws-bedrock'; import * as sfn from '../../../aws-stepfunctions'; import * as cdk from '../../../core'; +import * as cxapi from '../../../cx-api'; import { Guardrail } from '../../lib/bedrock/guardrail'; import { BedrockInvokeModel } from '../../lib/bedrock/invoke-model'; @@ -204,8 +205,151 @@ describe('Invoke Model', () => { }); }); - test('invoke model allows input and output json path', () => { - const stack = new cdk.Stack(); + test('invoke model allows input and output as s3 Uri with feature flag set to true', () => { + const app = new cdk.App({ context: { [cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: true } }); + const stack = new cdk.Stack(app); + const model = bedrock.ProvisionedModel.fromProvisionedModelArn(stack, 'Imported', 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123'); + + const task = new BedrockInvokeModel(stack, 'Invoke', { + model, + input: { s3InputUri: sfn.JsonPath.stringAt('$.prompt') }, + output: { s3OutputUri: sfn.JsonPath.stringAt('$.prompt') }, + }); + + new sfn.StateMachine(stack, 'StateMachine', { + definitionBody: sfn.DefinitionBody.fromChainable(task), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::bedrock:invokeModel', + ], + ], + }, + End: true, + Parameters: { + ModelId: 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123', + Input: { + //Expected key modified from S3Uri to S3Uri.$ as per the State Machine context key field transformation + //Reference: https://docs.aws.amazon.com/step-functions/latest/dg/input-output-example.html + 'S3Uri.$': '$.prompt', + }, + Output: { + 'S3Uri.$': '$.prompt', + }, + }, + }); + }); + + test('invoke model renderes input and output path correctly with feature flag set to true', () => { + const app = new cdk.App({ context: { [cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: true } }); + const stack = new cdk.Stack(app); + const model = bedrock.ProvisionedModel.fromProvisionedModelArn(stack, 'Imported', 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123'); + + const task = new BedrockInvokeModel(stack, 'Invoke', { + model, + input: { s3InputUri: sfn.JsonPath.stringAt('$.prompt') }, + output: { s3OutputUri: sfn.JsonPath.stringAt('$.prompt') }, + inputPath: sfn.JsonPath.stringAt('$.names'), + outputPath: sfn.JsonPath.stringAt('$.names'), + }); + + new sfn.StateMachine(stack, 'StateMachine', { + definitionBody: sfn.DefinitionBody.fromChainable(task), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::bedrock:invokeModel', + ], + ], + }, + End: true, + InputPath: '$.names', + OutputPath: '$.names', + Parameters: { + ModelId: 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123', + Input: { + //Expected key modified from S3Uri to S3Uri.$ as per the State Machine context key field transformation + //Reference: https://docs.aws.amazon.com/step-functions/latest/dg/input-output-example.html + 'S3Uri.$': '$.prompt', + }, + Output: { + 'S3Uri.$': '$.prompt', + }, + }, + }); + }); + + //Should not throw an error for input path and body if feature flag is set to true + test('validation for input and body correctly with feature flag set to true', () => { + const app = new cdk.App({ context: { [cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: true } }); + const stack = new cdk.Stack(app); + const model = bedrock.ProvisionedModel.fromProvisionedModelArn(stack, 'Imported', 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123'); + + const task = new BedrockInvokeModel(stack, 'Invoke', { + model, + body: sfn.TaskInput.fromObject( + { + prompt: 'Hello world', + }, + ), + inputPath: sfn.JsonPath.stringAt('$.names'), + outputPath: sfn.JsonPath.stringAt('$.names'), + }); + + new sfn.StateMachine(stack, 'StateMachine', { + definitionBody: sfn.DefinitionBody.fromChainable(task), + }); + + // THEN + expect(stack.resolve(task.toStateJson())).toEqual({ + Type: 'Task', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':states:::bedrock:invokeModel', + ], + ], + }, + End: true, + InputPath: '$.names', + OutputPath: '$.names', + Parameters: { + ModelId: 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123', + Body: { + prompt: 'Hello world', + }, + }, + }); + }); + + test('invoke model renders input and output JSON Path as s3 URI with feature flag set to false', () => { + const app = new cdk.App({ context: { [cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: false } }); + const stack = new cdk.Stack(app); const model = bedrock.ProvisionedModel.fromProvisionedModelArn(stack, 'Imported', 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123'); const task = new BedrockInvokeModel(stack, 'Invoke', { @@ -250,6 +394,50 @@ describe('Invoke Model', () => { }); }); + test('throws error S3 input Uri is specified as an empty string', () => { + const app = new cdk.App({ context: { [cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: true } }); + const stack = new cdk.Stack(app); + const model = bedrock.ProvisionedModel.fromProvisionedModelArn(stack, 'Imported', 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123'); + + expect(() => { + new BedrockInvokeModel(stack, 'Invoke', { + model, + input: { + s3InputUri: '', + }, + output: { + s3OutputUri: '', + }, + }); + }).toThrow('S3 Uri cannot be an empty string'); + }); + + test('cannot specify both s3 uri and s3 bucket', () => { + const app = new cdk.App({ context: { [cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: true } }); + const stack = new cdk.Stack(app); + const model = bedrock.ProvisionedModel.fromProvisionedModelArn(stack, 'Imported', 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123'); + + expect(() => { + new BedrockInvokeModel(stack, 'Invoke', { + model, + input: { + s3Location: { + bucketName: 'test-bucket', + objectKey: 'input-key', + }, + s3InputUri: sfn.JsonPath.stringAt('$.prompt'), + }, + output: { + s3Location: { + bucketName: 'test-bucket', + objectKey: 'output-key', + }, + s3OutputUri: sfn.JsonPath.stringAt('$.prompt'), + }, + }); + }).toThrow('Either specify S3 Uri or S3 location, but not both.'); + }); + test('S3 permissions are created in generated policy when input and output locations are specified', () => { // GIVEN const stack = new cdk.Stack(); @@ -322,16 +510,17 @@ describe('Invoke Model', () => { }); }); - test('S3 permissions are created in generated policy when input and output path are specified', () => { + test('S3 permissions are created in generated policy when input/output s3uri is specified', () => { // GIVEN - const stack = new cdk.Stack(); + const app = new cdk.App({ context: { [cxapi.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: true } }); + const stack = new cdk.Stack(app); const model = bedrock.ProvisionedModel.fromProvisionedModelArn(stack, 'Imported', 'arn:aws:bedrock:us-turbo-2:123456789012:provisioned-model/abc-123'); // WHEN const task = new BedrockInvokeModel(stack, 'Invoke', { model, - inputPath: sfn.JsonPath.stringAt('$.prompt'), - outputPath: sfn.JsonPath.stringAt('$.prompt'), + input: { s3InputUri: 's3://input-bucket/input-key' }, + output: { s3OutputUri: 's3://input-bucket/output-key' }, }); new sfn.StateMachine(stack, 'StateMachine', { diff --git a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md index d911731f3b9f9..5d2b104c1f8bf 100644 --- a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md +++ b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md @@ -72,6 +72,7 @@ Flags come in three types: | [@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm](#aws-cdkaws-ecsremovedefaultdeploymentalarm) | When enabled, remove default deployment alarm settings | 2.143.0 | (default) | | [@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault](#aws-cdkcustom-resourceslogapiresponsedatapropertytruedefault) | When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default | 2.145.0 | (fix) | | [@aws-cdk/aws-s3:keepNotificationInImportedBucket](#aws-cdkaws-s3keepnotificationinimportedbucket) | When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack. | 2.155.0 | (fix) | +| [@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask](#aws-cdkaws-stepfunctions-tasksusenews3uriparametersforbedrockinvokemodeltask) | When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model. | V2NEXT | (fix) | @@ -177,6 +178,7 @@ are migrating a v1 CDK project to v2, explicitly set any of these flags which do | [@aws-cdk/aws-lambda:recognizeVersionProps](#aws-cdkaws-lambdarecognizeversionprops) | Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`. | (fix) | 1.106.0 | `false` | `true` | | [@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2\_2021](#aws-cdkaws-cloudfrontdefaultsecuritypolicytlsv12_2021) | Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default. | (fix) | 1.117.0 | `false` | `true` | | [@aws-cdk/pipelines:reduceAssetRoleTrustScope](#aws-cdkpipelinesreduceassetroletrustscope) | Remove the root account principal from PipelineAssetsFileRole trust policy | (default) | | `false` | `true` | +| [@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask](#aws-cdkaws-stepfunctions-tasksusenews3uriparametersforbedrockinvokemodeltask) | When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model. | (fix) | | `false` | `true` | @@ -192,7 +194,8 @@ Here is an example of a `cdk.json` file that restores v1 behavior for these flag "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": false, "@aws-cdk/aws-lambda:recognizeVersionProps": false, "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": false, - "@aws-cdk/pipelines:reduceAssetRoleTrustScope": false + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": false, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": false } } ``` @@ -1356,4 +1359,23 @@ Other notifications that are not managed by this stack will be kept. | 2.155.0 | `false` | `false` | +### @aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask + +*When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.* (fix) + +Currently, 'inputPath' and 'outputPath' from the TaskStateBase Props is being used under BedrockInvokeModelProps to define S3URI under 'input' and 'output' fields +of State Machine Task definition. + +When this feature flag is enabled, specify newly introduced props 's3InputUri' and +'s3OutputUri' to populate S3 uri under input and output fields in state machine task definition for Bedrock invoke model. + + +| Since | Default | Recommended | +| ----- | ----- | ----- | +| (not in v1) | | | +| V2NEXT | `true` | `true` | + +**Compatibility with old behavior:** Disable the feature flag to use input and output path fields for s3 URI + + diff --git a/packages/aws-cdk-lib/cx-api/README.md b/packages/aws-cdk-lib/cx-api/README.md index 06f364f9922f5..d094fd410d235 100644 --- a/packages/aws-cdk-lib/cx-api/README.md +++ b/packages/aws-cdk-lib/cx-api/README.md @@ -375,4 +375,20 @@ _cdk.json_ "@aws-cdk/aws-stepfunctions-tasks:ecsReduceRunTaskPermissions": true } } +``` + +* `@aws-cdk/aws-stepfunctions-taks:useNewS3UriParametersForBedrockInvokeModelTask` + +When enabled, use new props for S3 URI under `input` and `output` fields in task definition of state machine for bedrock invoke model. + +When this feature flag is enabled, use newly introduced props `s3InputUri` and `s3OutputUri` to populate S3 uri under input and output fields in state machine task definition for Bedrock invoke model. + +_cdk.json_ + +```json +{ + "context": { + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": true + } +} ``` \ No newline at end of file diff --git a/packages/aws-cdk-lib/cx-api/lib/features.ts b/packages/aws-cdk-lib/cx-api/lib/features.ts index 5ce4c237986d9..c4c5f13a0ee28 100644 --- a/packages/aws-cdk-lib/cx-api/lib/features.ts +++ b/packages/aws-cdk-lib/cx-api/lib/features.ts @@ -106,6 +106,7 @@ export const EBS_DEFAULT_GP3 = '@aws-cdk/aws-ec2:ebsDefaultGp3Volume'; export const ECS_REMOVE_DEFAULT_DEPLOYMENT_ALARM = '@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm'; export const LOG_API_RESPONSE_DATA_PROPERTY_TRUE_DEFAULT = '@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault'; export const S3_KEEP_NOTIFICATION_IN_IMPORTED_BUCKET = '@aws-cdk/aws-s3:keepNotificationInImportedBucket'; +export const USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK = '@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask'; export const FLAGS: Record = { ////////////////////////////////////////////////////////////////////// @@ -1107,6 +1108,24 @@ export const FLAGS: Record = { introducedIn: { v2: '2.155.0' }, recommendedValue: false, }, + + ////////////////////////////////////////////////////////////////////// + [USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: { + type: FlagType.BugFix, + summary: 'When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.', + detailsMd: ` + Currently, 'inputPath' and 'outputPath' from the TaskStateBase Props is being used under BedrockInvokeModelProps to define S3URI under 'input' and 'output' fields + of State Machine Task definition. + + When this feature flag is enabled, specify newly introduced props 's3InputUri' and + 's3OutputUri' to populate S3 uri under input and output fields in state machine task definition for Bedrock invoke model. + + `, + introducedIn: { v2: 'V2NEXT' }, + defaults: { v2: true }, + recommendedValue: true, + compatibilityWithOldBehaviorMd: 'Disable the feature flag to use input and output path fields for s3 URI', + }, }; const CURRENT_MV = 'v2'; diff --git a/packages/aws-cdk-lib/cx-api/test/features.test.ts b/packages/aws-cdk-lib/cx-api/test/features.test.ts index c1aeb3b80c0d1..470b2644ba2a6 100644 --- a/packages/aws-cdk-lib/cx-api/test/features.test.ts +++ b/packages/aws-cdk-lib/cx-api/test/features.test.ts @@ -38,6 +38,7 @@ test('feature flag defaults may not be changed anymore', () => { [feats.LAMBDA_RECOGNIZE_VERSION_PROPS]: true, [feats.CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021]: true, [feats.PIPELINE_REDUCE_ASSET_ROLE_TRUST_SCOPE]: true, + [feats.USE_NEW_S3URI_PARAMETERS_FOR_BEDROCK_INVOKE_MODEL_TASK]: true, // Add new disabling feature flags below this line }); });