|
| 1 | +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 2 | +// SPDX-License-Identifier: MIT-0 |
| 3 | + |
| 4 | +import { Construct } from "constructs"; |
| 5 | +import * as cdk from "aws-cdk-lib"; |
| 6 | +import { NagSuppressions } from "cdk-nag"; |
| 7 | + |
| 8 | +import { |
| 9 | + aws_stepfunctions as sfn, |
| 10 | + aws_stepfunctions_tasks as tasks, |
| 11 | + aws_iam as iam, |
| 12 | + aws_s3 as s3, |
| 13 | +} from "aws-cdk-lib"; |
| 14 | + |
| 15 | +import { Bucket } from "../enum"; |
| 16 | +import { dt_lambda } from "../../../components/lambda"; |
| 17 | +import { dt_stepfunction } from "../../../components/stepfunction"; |
| 18 | + |
| 19 | +export interface props { |
| 20 | + bedrockRegion: string; |
| 21 | + contentBucket: s3.Bucket; |
| 22 | + removalPolicy: cdk.RemovalPolicy; |
| 23 | +} |
| 24 | + |
| 25 | +enum Strings { |
| 26 | + modelVendor = "stability", |
| 27 | + modelNamePrefix = "sd3-", |
| 28 | +} |
| 29 | + |
| 30 | +const id = "v3"; |
| 31 | + |
| 32 | +export class dt_readableWorkflow extends Construct { |
| 33 | + public readonly invokeModel: tasks.StepFunctionsStartExecution; |
| 34 | + public readonly modelChoiceCondition: sfn.Condition; |
| 35 | + public readonly sfnMain: sfn.StateMachine; |
| 36 | + |
| 37 | + constructor(scope: Construct, id: string, props: props) { |
| 38 | + super(scope, id); |
| 39 | + |
| 40 | + // LAMBDA |
| 41 | + // LAMBDA | INVOKE BEDROCK |
| 42 | + // LAMBDA | INVOKE BEDROCK | ROLE |
| 43 | + const invokeBedrockLambdaRole = new iam.Role( |
| 44 | + this, |
| 45 | + "invokeBedrockSaveToS3", |
| 46 | + { |
| 47 | + // ASM-L6 // ASM-L8 |
| 48 | + assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), |
| 49 | + description: "Lambda Role (Invoke Bedrock API & save file to S3)", |
| 50 | + }, |
| 51 | + ); |
| 52 | + |
| 53 | + // LAMBDA | INVOKE BEDROCK | POLICY |
| 54 | + const permitInvokeBedrockModel = new iam.Policy( |
| 55 | + this, |
| 56 | + "permitInvokeModels", |
| 57 | + { |
| 58 | + policyName: `Invoke-${Strings.modelVendor}-${Strings.modelNamePrefix}-models`, |
| 59 | + statements: [ |
| 60 | + new iam.PolicyStatement({ |
| 61 | + // ASM-IAM |
| 62 | + actions: ["bedrock:InvokeModel"], |
| 63 | + resources: [ |
| 64 | + `arn:aws:bedrock:${props.bedrockRegion}::foundation-model/${Strings.modelVendor}.${Strings.modelNamePrefix}*`, // Foundational Models |
| 65 | + ], |
| 66 | + }), |
| 67 | + ], |
| 68 | + }, |
| 69 | + ); |
| 70 | + invokeBedrockLambdaRole.attachInlinePolicy(permitInvokeBedrockModel); |
| 71 | + NagSuppressions.addResourceSuppressions( |
| 72 | + permitInvokeBedrockModel, |
| 73 | + [ |
| 74 | + { |
| 75 | + id: "AwsSolutions-IAM5", |
| 76 | + reason: "Preferred model for prompt is unknown at deploy time", |
| 77 | + appliesTo: [ |
| 78 | + `Resource::arn:aws:bedrock:${props.bedrockRegion}::foundation-model/${Strings.modelVendor}.${Strings.modelNamePrefix}*`, |
| 79 | + ], |
| 80 | + }, |
| 81 | + ], |
| 82 | + true, |
| 83 | + ); |
| 84 | + const permitPutS3 = new iam.Policy(this, "permitPutS3", { |
| 85 | + policyName: "S3-PutObject", |
| 86 | + statements: [ |
| 87 | + new iam.PolicyStatement({ |
| 88 | + // ASM-IAM |
| 89 | + actions: ["s3:PutObject"], |
| 90 | + resources: [ |
| 91 | + `${props.contentBucket.bucketArn}/${Bucket.PREFIX_PRIVATE}/*`, |
| 92 | + ], |
| 93 | + }), |
| 94 | + ], |
| 95 | + }); |
| 96 | + invokeBedrockLambdaRole.attachInlinePolicy(permitPutS3); |
| 97 | + NagSuppressions.addResourceSuppressions( |
| 98 | + permitPutS3, |
| 99 | + [ |
| 100 | + { |
| 101 | + id: "AwsSolutions-IAM5", |
| 102 | + reason: |
| 103 | + "Scoped to project specific resources. Unknown filenames at deploy time.", |
| 104 | + appliesTo: [ |
| 105 | + "Resource::<basereadablecontentBucketbucket6155EB26.Arn>/private/*", |
| 106 | + ], |
| 107 | + }, |
| 108 | + ], |
| 109 | + true, |
| 110 | + ); |
| 111 | + |
| 112 | + // LAMBDA | INVOKE BEDROCK | FUNCTION |
| 113 | + const invokeBedrockLambda = new dt_lambda(this, "invokeBedrockLambda", { |
| 114 | + role: invokeBedrockLambdaRole, |
| 115 | + path: "lambda/invokeBedrockSaveToS3", |
| 116 | + description: "Invoke Bedrock API & save file to S3", |
| 117 | + environment: { |
| 118 | + BEDROCK_REGION: props.bedrockRegion, |
| 119 | + }, |
| 120 | + bundlingNodeModules: [ |
| 121 | + "@aws-sdk/client-bedrock-runtime", |
| 122 | + "@aws-sdk/client-s3", |
| 123 | + ], |
| 124 | + timeout: cdk.Duration.seconds(60), |
| 125 | + }); |
| 126 | + |
| 127 | + // |
| 128 | + // STATE MACHINE |
| 129 | + // STATE MACHINE | TASKS |
| 130 | + // STATE MACHINE | TASKS | createPrompt |
| 131 | + const createPrompt = new sfn.Pass(this, "createPrompt", { |
| 132 | + resultPath: "$.prompt", |
| 133 | + parameters: { |
| 134 | + Payload: { |
| 135 | + prompt: sfn.JsonPath.stringAt("$.jobDetails.prePrompt"), |
| 136 | + }, |
| 137 | + }, |
| 138 | + }); |
| 139 | + // STATE MACHINE | TASKS | createBody |
| 140 | + const createBody = new sfn.Pass(this, "createBody", { |
| 141 | + resultPath: "$.body", |
| 142 | + parameters: { |
| 143 | + Payload: sfn.JsonPath.jsonMerge( |
| 144 | + sfn.JsonPath.objectAt("$.prompt.Payload"), |
| 145 | + sfn.JsonPath.objectAt("$.jobDetails.parameters"), |
| 146 | + ), |
| 147 | + }, |
| 148 | + }); |
| 149 | + |
| 150 | + // STATE MACHINE | TASKS | invokeBedrock |
| 151 | + const invokeBedrock = new tasks.LambdaInvoke(this, "invokeBedrock", { |
| 152 | + lambdaFunction: invokeBedrockLambda.lambdaFunction, |
| 153 | + resultPath: "$.invokeBedrock", |
| 154 | + resultSelector: { |
| 155 | + "Payload.$": "$.Payload", |
| 156 | + }, |
| 157 | + payload: sfn.TaskInput.fromObject({ |
| 158 | + ModelId: sfn.JsonPath.objectAt("$.jobDetails.modelId"), |
| 159 | + Body: sfn.JsonPath.stringAt("$.body.Payload"), |
| 160 | + PathToResult: "images.0", |
| 161 | + ResultS3Bucket: props.contentBucket.bucketName, |
| 162 | + ResultS3Key: sfn.JsonPath.format( |
| 163 | + `${Bucket.PREFIX_PRIVATE}/{}/{}/{}_{}.png`, |
| 164 | + sfn.JsonPath.stringAt("$.jobDetails.identity"), |
| 165 | + sfn.JsonPath.stringAt("$.jobDetails.id"), |
| 166 | + sfn.JsonPath.stringAt("$.jobDetails.itemId"), |
| 167 | + sfn.JsonPath.stringAt("$$.Execution.StartTime"), |
| 168 | + ), |
| 169 | + ItemId: sfn.JsonPath.stringAt("$.jobDetails.itemId"), |
| 170 | + }), |
| 171 | + }); |
| 172 | + |
| 173 | + // STATE MACHINE | TASKS | filterOutput |
| 174 | + const filterOutput = new sfn.Pass(this, "filterOutput", { |
| 175 | + parameters: { |
| 176 | + payload: sfn.JsonPath.stringAt("$.invokeBedrock.Payload.key"), |
| 177 | + }, |
| 178 | + }); |
| 179 | + |
| 180 | + // STATE MACHINE | DEF |
| 181 | + this.sfnMain = new dt_stepfunction( |
| 182 | + this, |
| 183 | + `${cdk.Stack.of(this).stackName}_Readable_${Strings.modelVendor}_${id}`, |
| 184 | + { |
| 185 | + nameSuffix: `Readable_${Strings.modelVendor}_${id}`, |
| 186 | + removalPolicy: props.removalPolicy, |
| 187 | + definition: createPrompt |
| 188 | + .next(createBody) |
| 189 | + .next(invokeBedrock) |
| 190 | + .next(filterOutput), |
| 191 | + }, |
| 192 | + ).StateMachine; |
| 193 | + NagSuppressions.addResourceSuppressions( |
| 194 | + this.sfnMain, |
| 195 | + [ |
| 196 | + { |
| 197 | + id: "AwsSolutions-IAM5", |
| 198 | + reason: "Permissions scoped to dedicated resources.", |
| 199 | + appliesTo: [ |
| 200 | + `Resource::<${cdk.Stack.of(this).getLogicalId( |
| 201 | + invokeBedrockLambda.lambdaFunction.node |
| 202 | + .defaultChild as cdk.CfnElement, |
| 203 | + )}.Arn>:*`, |
| 204 | + ], |
| 205 | + }, |
| 206 | + ], |
| 207 | + true, |
| 208 | + ); |
| 209 | + |
| 210 | + // PARENT |
| 211 | + // PARENT | CHOICE FILTER |
| 212 | + this.modelChoiceCondition = sfn.Condition.stringMatches( |
| 213 | + "$.jobDetails.modelId", |
| 214 | + `${Strings.modelVendor}.${Strings.modelNamePrefix}*`, |
| 215 | + ); |
| 216 | + // PARENT | TASK |
| 217 | + this.invokeModel = new tasks.StepFunctionsStartExecution( |
| 218 | + this, |
| 219 | + `invokeModel_${Strings.modelVendor}_${id}`, |
| 220 | + { |
| 221 | + stateMachine: this.sfnMain, |
| 222 | + resultSelector: { |
| 223 | + "Payload.$": "$.Output.payload", |
| 224 | + }, |
| 225 | + resultPath: "$.invokeModel", |
| 226 | + integrationPattern: sfn.IntegrationPattern.RUN_JOB, |
| 227 | + input: sfn.TaskInput.fromObject({ |
| 228 | + jobDetails: sfn.JsonPath.objectAt("$.jobDetails"), |
| 229 | + }), |
| 230 | + }, |
| 231 | + ); |
| 232 | + |
| 233 | + // END |
| 234 | + } |
| 235 | +} |
0 commit comments