Skip to content

Commit 9b1b838

Browse files
dineshSajwanDinesh Sajwankrokoko
authored
feat(construct): Bug fix for aws-contentgen-appsync-lambda and aws-summarization-appsync-stepfn construct (#722)
* feat(content-gen): update modelid in content gen construct --------- Co-authored-by: Dinesh Sajwan <[email protected]> Co-authored-by: Alain Krok <[email protected]>
1 parent 82f48c1 commit 9b1b838

File tree

8 files changed

+75
-54
lines changed

8 files changed

+75
-54
lines changed

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ The following constructs are available in the library:
136136
| [SageMaker model deployment (JumpStart)](./src/patterns/gen-ai/aws-model-deployment-sagemaker/README_jumpstart.md) | Deploy a foundation model from Amazon SageMaker JumpStart to an Amazon SageMaker endpoint. | Amazon SageMaker |
137137
| [SageMaker model deployment (Hugging Face)](./src/patterns/gen-ai/aws-model-deployment-sagemaker/README_hugging_face.md) | Deploy a foundation model from Hugging Face to an Amazon SageMaker endpoint. | Amazon SageMaker |
138138
| [SageMaker model deployment (Custom)](./src/patterns/gen-ai/aws-model-deployment-sagemaker/README_custom_sagemaker_endpoint.md) | Deploy a foundation model from an S3 location to an Amazon SageMaker endpoint. | Amazon SageMaker |
139-
| [Content Generation](./src/patterns/gen-ai/aws-contentgen-appsync-lambda/README.md) | Generate images from text using Amazon titan-image-generator-v1 or stability.stable-diffusion-xl model. | AWS Lambda, Amazon Bedrock, AWS AppSync |
139+
| [Content Generation](./src/patterns/gen-ai/aws-contentgen-appsync-lambda/README.md) | Generate images from text using Amazon titan-image-generator-v1 or stability.stable-diffusion-xl-v1 model. | AWS Lambda, Amazon Bedrock, AWS AppSync |
140140
| [Web crawler](./src/patterns/gen-ai/aws-web-crawler/README.md) | Crawl websites and RSS feeds on a schedule and store changeset data in an Amazon Simple Storage Service bucket. | AWS Lambda, AWS Batch, AWS Fargate, Amazon DynamoDB |
141141
| [Amazon Bedrock Monitoring (Amazon CloudWatch Dashboard)](./src/patterns/gen-ai/aws-bedrock-cw-dashboard/README.md) | Amazon CloudWatch dashboard to monitor model usage from Amazon Bedrock. | Amazon CloudWatch |
142142

Diff for: lambda/aws-contentgen-appsync-lambda/src/image_generator.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from datetime import datetime
1616
from requests_aws4auth import AWS4Auth
1717
from aws_lambda_powertools import Logger, Tracer, Metrics
18+
from util import MODEL_NAME
19+
1820

1921
logger = Logger(service="CONTENT_GENERATION")
2022
tracer = Tracer(service="CONTENT_GENERATION")
@@ -49,7 +51,6 @@ def __init__(self,input_text, rekognition_client,comprehend_client,bedrock_clien
4951

5052

5153

52-
@tracer.capture_method
5354
def upload_file_to_s3(self,imgbase64encoded,file_name):
5455

5556
"""Upload generated file to S3 bucket"""
@@ -68,7 +69,6 @@ def upload_file_to_s3(self,imgbase64encoded,file_name):
6869
"bucket_name":self.bucket,
6970
}
7071

71-
@tracer.capture_method
7272
def text_moderation(self):
7373

7474
"""Check input text has any toxicity or not. The comprehend is trained
@@ -96,7 +96,6 @@ def text_moderation(self):
9696

9797
return response
9898

99-
@tracer.capture_method
10099
def image_moderation(self,file_name):
101100

102101
"""Detect image moderation on the generated image to avoid any toxicity/nudity"""
@@ -197,12 +196,12 @@ def send_job_status(self,variables):
197196
auth=aws_auth_appsync,
198197
timeout=10
199198
)
200-
logger.info('res :: {}',responseJobstatus)
199+
logger.info(f"sending response :: {responseJobstatus}")
201200

202201
def get_model_payload(modelid,params,input_text,negative_prompts):
203-
202+
204203
body=''
205-
if modelid=='stability.stable-diffusion-xl' :
204+
if modelid==MODEL_NAME.STABILITY_DIFFUSION :
206205
body = json.dumps({
207206
"text_prompts": (
208207
[{"text": input_text, "weight": 1.0}]
@@ -218,7 +217,7 @@ def get_model_payload(modelid,params,input_text,negative_prompts):
218217
"height": params['height']
219218
})
220219
return body
221-
if modelid=='amazon.titan-image-generator-v1' :
220+
if modelid==MODEL_NAME.TITAN_IMAGE :
222221

223222
body = json.dumps({
224223
"taskType": "TEXT_IMAGE",

Diff for: lambda/aws-contentgen-appsync-lambda/src/lambda.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from aws_lambda_powertools.utilities.typing import LambdaContext
2121
from aws_lambda_powertools.metrics import MetricUnit
2222
from aws_lambda_powertools.utilities.validation import validate, SchemaValidationError
23-
23+
from util import MODEL_NAME
2424

2525

2626
logger = Logger(service="CONTENT_GENERATION")
@@ -88,9 +88,9 @@ def handler(event, context: LambdaContext) -> dict:
8888
num_of_images=0 #if multiple image geneated iterate through all
8989
for image in parsed_reponse['image_generated']:
9090
logger.info(f'num_of_images {num_of_images}')
91-
if model_id=='stability.stable-diffusion-xl' :
91+
if model_id==MODEL_NAME.STABILITY_DIFFUSION :
9292
imgbase64encoded= parsed_reponse['image_generated'][num_of_images]["base64"]
93-
if model_id=='amazon.titan-image-generator-v1' :
93+
if model_id==MODEL_NAME.TITAN_IMAGE :
9494
imgbase64encoded= parsed_reponse['image_generated'][num_of_images]
9595
imageGenerated=img.upload_file_to_s3(imgbase64encoded,file_name)
9696
num_of_images=+1
@@ -127,14 +127,14 @@ def parse_response(query_response,model_id):
127127
else:
128128
response_dict = json.loads(query_response["body"].read())
129129

130-
if model_id=='stability.stable-diffusion-xl' :
130+
if model_id==MODEL_NAME.STABILITY_DIFFUSION :
131131

132132
if(response_dict['artifacts'] is None):
133133
parsed_reponse['image_generated_status']='Failed'
134134
else:
135135
parsed_reponse['image_generated']=response_dict['artifacts']
136136

137-
if model_id=='amazon.titan-image-generator-v1' :
137+
if model_id==MODEL_NAME.TITAN_IMAGE :
138138
if(response_dict['images'] is None):
139139
parsed_reponse['image_generated_status']='Failed'
140140
else:
@@ -143,4 +143,3 @@ def parse_response(query_response,model_id):
143143

144144
return parsed_reponse
145145

146-

Diff for: lambda/aws-contentgen-appsync-lambda/src/util.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#
2+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
5+
# with the License. A copy of the License is located at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
10+
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
11+
# and limitations under the License.
12+
13+
from enum import StrEnum
14+
15+
class MODEL_NAME(StrEnum):
16+
STABILITY_DIFFUSION = 'stability.stable-diffusion-xl-v1',
17+
TITAN_IMAGE='amazon.titan-image-generator-v1'

Diff for: src/patterns/gen-ai/aws-contentgen-appsync-lambda/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ The workflow is as follows:
4343

4444
3. Lambda function first implement text moderation using Amazon Comprehend to check for inappropriate content.
4545

46-
4. The functions then generate an image from the text using Amazon Bedrock with the stability.stable-diffusion-xl/amazon.titan-image-generator-v1 model.
46+
4. The functions then generate an image from the text using Amazon Bedrock with the stability.stable-diffusion-xl-v1/amazon.titan-image-generator-v1 model.
4747

4848
5. Next, image moderation is performed using Amazon Rekognition to further ensure appropriateness.
4949

@@ -52,7 +52,7 @@ The workflow is as follows:
5252

5353
This construct builds a Lambda function from a Docker image, thus you need [Docker desktop](https://www.docker.com/products/docker-desktop/) running on your machine.
5454

55-
Make sure the model (stability.stable-diffusion-xl/amazon.titan-image-generator-v1) is enabled in your account. Please follow the [Amazon Bedrock User Guide](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) for steps related to enabling model access.
55+
Make sure the model (stability.stable-diffusion-xl-v1/amazon.titan-image-generator-v1) is enabled in your account. Please follow the [Amazon Bedrock User Guide](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) for steps related to enabling model access.
5656

5757
AWS Lambda functions provisioned in this construct use [Powertools for AWS Lambda (Python)](https://github.com/aws-powertools/powertools-lambda-python) for tracing, structured logging and custom metrics creation.
5858

@@ -214,7 +214,7 @@ Expected response: It invoke an asynchronous summarization process thus the resp
214214
Where:
215215
- job_id: id which can be used to filter subscriptions on client side.
216216
- status: this field will be used by the subscription to update the status of the image generation process.
217-
- model_config: configure model id amazon.titan-image-generator-v1/stability.stable-diffusion-xl.
217+
- model_config: configure model id amazon.titan-image-generator-v1/stability.stable-diffusion-xl-v1.
218218
- model_kwargs: Image generation model driver for Stable Diffusion models and Amazon Titan generator on Amazon Bedrock.
219219

220220

Diff for: src/patterns/gen-ai/aws-contentgen-appsync-lambda/index.ts

+5-6
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,11 @@ export class ContentGenerationAppSyncLambda extends BaseClass {
177177
if (props?.existingVpc) {
178178
this.vpc = props.existingVpc;
179179
} else {
180-
this.vpc = vpc_helper.buildVpc(scope, {
181-
defaultVpcProps: props?.vpcProps,
182-
vpcName: 'cgAppSyncLambdaVpc',
183-
});
180+
this.vpc = new ec2.Vpc(this, 'Vpc', props.vpcProps);
184181
// vpc endpoints
185182
vpc_helper.AddAwsServiceEndpoint(scope, this.vpc, [vpc_helper.ServiceEndpointTypeEnum.S3,
186-
vpc_helper.ServiceEndpointTypeEnum.BEDROCK_RUNTIME, vpc_helper.ServiceEndpointTypeEnum.REKOGNITION]);
183+
vpc_helper.ServiceEndpointTypeEnum.BEDROCK_RUNTIME, vpc_helper.ServiceEndpointTypeEnum.REKOGNITION,
184+
vpc_helper.ServiceEndpointTypeEnum.COMPREHEND]);
187185
}
188186

189187
// Security group
@@ -285,6 +283,7 @@ export class ContentGenerationAppSyncLambda extends BaseClass {
285283
],
286284
},
287285
xrayEnabled: this.enablexray,
286+
visibility: appsync.Visibility.GLOBAL,
288287
logConfig: {
289288
fieldLogLevel: this.fieldLogLevel,
290289
retention: this.retention,
@@ -471,7 +470,7 @@ export class ContentGenerationAppSyncLambda extends BaseClass {
471470
description: 'Lambda function for generating image',
472471
vpc: this.vpc,
473472
tracing: this.lambdaTracing,
474-
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
473+
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
475474
securityGroups: [this.securityGroup],
476475
memorySize: lambdaMemorySizeLimiter(this, 1_769 * 4),
477476
timeout: Duration.minutes(15),

Diff for: src/patterns/gen-ai/aws-summarization-appsync-stepfn/index.ts

+28-27
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* and limitations under the License.
1212
*/
1313
import * as path from 'path';
14-
import { Duration, Aws } from 'aws-cdk-lib';
14+
import { Duration, Aws, RemovalPolicy } from 'aws-cdk-lib';
1515
import * as appsync from 'aws-cdk-lib/aws-appsync';
1616
import * as cognito from 'aws-cdk-lib/aws-cognito';
1717
import * as ec2 from 'aws-cdk-lib/aws-ec2';
@@ -258,15 +258,10 @@ export class SummarizationAppsyncStepfn extends BaseClass {
258258
if (props?.existingVpc) {
259259
this.vpc = props.existingVpc;
260260
} else {
261-
this.vpc = vpc_helper.buildVpc(scope, {
262-
defaultVpcProps: props?.vpcProps,
263-
vpcName: 'sumAppSyncStepFnVpc',
264-
});
265-
261+
this.vpc = new ec2.Vpc(this, 'Vpc', props.vpcProps);
266262
// vpc endpoints
267263
vpc_helper.AddAwsServiceEndpoint(scope, this.vpc, [vpc_helper.ServiceEndpointTypeEnum.S3,
268-
vpc_helper.ServiceEndpointTypeEnum.BEDROCK_RUNTIME, vpc_helper.ServiceEndpointTypeEnum.REKOGNITION,
269-
vpc_helper.ServiceEndpointTypeEnum.APP_SYNC]);
264+
vpc_helper.ServiceEndpointTypeEnum.BEDROCK_RUNTIME, vpc_helper.ServiceEndpointTypeEnum.REKOGNITION]);
270265
}
271266

272267
// Security group
@@ -303,6 +298,7 @@ export class SummarizationAppsyncStepfn extends BaseClass {
303298
encryption: s3.BucketEncryption.S3_MANAGED,
304299
enforceSSL: true,
305300
versioned: true,
301+
removalPolicy: RemovalPolicy.DESTROY,
306302
lifecycleRules: [{
307303
expiration: Duration.days(90),
308304
}],
@@ -321,17 +317,15 @@ export class SummarizationAppsyncStepfn extends BaseClass {
321317
this.inputAssetBucket = new s3.Bucket(this,
322318
'inputAssetsSummaryBucket' + this.stage, props.bucketInputsAssetsProps);
323319
} else {
324-
const bucketName = generatePhysicalNameV2(this,
325-
'input-assets-bucket' + this.stage,
326-
{ maxLength: 63, lower: true });
327-
this.inputAssetBucket = new s3.Bucket(this, bucketName,
320+
321+
this.inputAssetBucket = new s3.Bucket(this, 'inputAssetsSummaryBucket' + this.stage,
328322
{
329323
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
330324
encryption: s3.BucketEncryption.S3_MANAGED,
331-
bucketName: bucketName,
332325
serverAccessLogsBucket: serverAccessLogBucket,
333326
enforceSSL: true,
334327
versioned: true,
328+
removalPolicy: RemovalPolicy.DESTROY,
335329
lifecycleRules: [{
336330
expiration: Duration.days(90),
337331
}],
@@ -350,18 +344,14 @@ export class SummarizationAppsyncStepfn extends BaseClass {
350344
this.processedAssetBucket = new s3.Bucket(this,
351345
'processedAssetsSummaryBucket' + this.stage, props.bucketProcessedAssetsProps);
352346
} else {
353-
const bucketName = generatePhysicalNameV2(this,
354-
'processed-assets-bucket' + this.stage,
355-
{ maxLength: 63, lower: true });
356-
357-
this.processedAssetBucket = new s3.Bucket(this, bucketName,
347+
this.processedAssetBucket = new s3.Bucket(this, 'processedAssetsSummaryBucket' + this.stage,
358348
{
359349
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
360350
encryption: s3.BucketEncryption.S3_MANAGED,
361-
bucketName: bucketName,
362351
serverAccessLogsBucket: serverAccessLogBucket,
363352
enforceSSL: true,
364353
versioned: true,
354+
removalPolicy: RemovalPolicy.DESTROY,
365355
lifecycleRules: [{
366356
expiration: Duration.days(90),
367357
}],
@@ -495,7 +485,7 @@ export class SummarizationAppsyncStepfn extends BaseClass {
495485
description: 'Lambda function to validate input for summary api',
496486
vpc: this.vpc,
497487
tracing: this.lambdaTracing,
498-
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
488+
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
499489
securityGroups: [this.securityGroup],
500490
memorySize: lambdaMemorySizeLimiter(this, 1_769 * 1),
501491
timeout: Duration.minutes(5),
@@ -592,7 +582,7 @@ export class SummarizationAppsyncStepfn extends BaseClass {
592582
functionName: 'summary_document_reader' + this.stage,
593583
description: 'Lambda function to read the input transformed document',
594584
vpc: this.vpc,
595-
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
585+
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
596586
securityGroups: [this.securityGroup],
597587
memorySize: lambdaMemorySizeLimiter(this, 1_769 * 1),
598588
tracing: this.lambdaTracing,
@@ -695,7 +685,7 @@ export class SummarizationAppsyncStepfn extends BaseClass {
695685
description: 'Lambda function to generate the summary',
696686
code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../../../../lambda/aws-summarization-appsync-stepfn/summary_generator')),
697687
vpc: this.vpc,
698-
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
688+
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
699689
securityGroups: [this.securityGroup],
700690
memorySize: lambdaMemorySizeLimiter(this, 1_769 * 4),
701691
timeout: Duration.minutes(10),
@@ -809,9 +799,7 @@ export class SummarizationAppsyncStepfn extends BaseClass {
809799

810800
const logGroupName = generatePhysicalNameV2(this, logGroupPrefix,
811801
{ maxLength: maxGeneratedNameLength, lower: true });
812-
const summarizationLogGroup = new logs.LogGroup(this, 'summarizationLogGroup', {
813-
logGroupName: logGroupName,
814-
});
802+
815803

816804
// step function definition
817805
const definition = inputValidationTask.next(
@@ -824,12 +812,11 @@ export class SummarizationAppsyncStepfn extends BaseClass {
824812
);
825813

826814
// step function
827-
828815
const summarizationStepFunction = new sfn.StateMachine(this, 'summarizationStepFunction', {
829816
definitionBody: sfn.DefinitionBody.fromChainable(definition),
830817
timeout: Duration.minutes(15),
831818
logs: {
832-
destination: summarizationLogGroup,
819+
destination: getLoggroup(this, logGroupName),
833820
level: sfn.LogLevel.ALL,
834821
},
835822
tracingEnabled: this.enablexray,
@@ -888,3 +875,17 @@ export class SummarizationAppsyncStepfn extends BaseClass {
888875
}
889876
}
890877

878+
function getLoggroup(stack: Construct, logGroupName: string) {
879+
const existingLogGroup = logs.LogGroup.fromLogGroupName(
880+
stack, 'ExistingSummarizationLogGroup', logGroupName);
881+
882+
if (existingLogGroup.logGroupName) {
883+
return existingLogGroup;
884+
} else {
885+
return new logs.LogGroup(stack, 'SummarizationLogGroup', {
886+
logGroupName: logGroupName,
887+
retention: logs.RetentionDays.ONE_MONTH,
888+
removalPolicy: RemovalPolicy.DESTROY,
889+
});
890+
}
891+
}

Diff for: test/patterns/gen-ai/aws-summarization-appsync-stepfn/aws-summarization-appsync-stepfn.test.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,17 @@ describe('Summarization Appsync Stepfn construct', () => {
5656
cidrMask: 24,
5757
},
5858
{
59-
name: 'private',
59+
name: 'isolated',
6060
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
6161
cidrMask: 24,
6262
},
63+
{
64+
name: 'private',
65+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
66+
cidrMask: 24,
67+
},
6368
],
69+
natGateways: 1,
6470
},
6571
);
6672
const mergedapiRole = new iam.Role(
@@ -129,9 +135,9 @@ describe('Summarization Appsync Stepfn construct', () => {
129135
'GraphQLUrl',
130136
],
131137
},
132-
INPUT_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testinputassetsbucket') },
138+
INPUT_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testinputAssetsSummaryBucket') },
133139
IS_FILE_TRANSFORMED: 'false',
134-
TRANSFORMED_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testprocessedassetsbucket') },
140+
TRANSFORMED_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testprocessedAssetsSummaryBucket') },
135141
},
136142
},
137143
});
@@ -142,7 +148,7 @@ describe('Summarization Appsync Stepfn construct', () => {
142148
Variables: {
143149
ASSET_BUCKET_NAME: {
144150
Ref: Match.stringLikeRegexp
145-
('testprocessedassetsbucket'),
151+
('testprocessedAssetsSummaryBucket'),
146152
},
147153
GRAPHQL_URL: {
148154
'Fn::GetAtt': [

0 commit comments

Comments
 (0)