Skip to content

Commit 136d369

Browse files
dineshSajwanDinesh Sajwangithub-actions
authored
feat(construct): fixed issues in image summarization (#483)
* feat(construct): fixed issues in image summarization * chore: self mutation Signed-off-by: github-actions <[email protected]> --------- Signed-off-by: github-actions <[email protected]> Co-authored-by: Dinesh Sajwan <[email protected]> Co-authored-by: github-actions <[email protected]>
1 parent e7c30ac commit 136d369

File tree

5 files changed

+94
-34
lines changed

5 files changed

+94
-34
lines changed

apidocs/classes/SummarizationAppsyncStepfn.md

+18
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
- [eventBridgeBus](SummarizationAppsyncStepfn.md#eventbridgebus)
2323
- [fieldLogLevel](SummarizationAppsyncStepfn.md#fieldloglevel)
2424
- [graphqlApi](SummarizationAppsyncStepfn.md#graphqlapi)
25+
- [graphqlApiId](SummarizationAppsyncStepfn.md#graphqlapiid)
26+
- [graphqlUrl](SummarizationAppsyncStepfn.md#graphqlurl)
2527
- [inputAssetBucket](SummarizationAppsyncStepfn.md#inputassetbucket)
2628
- [inputValidationLambdaFunction](SummarizationAppsyncStepfn.md#inputvalidationlambdafunction)
2729
- [lambdaTracing](SummarizationAppsyncStepfn.md#lambdatracing)
@@ -145,6 +147,22 @@ Returns an instance of appsync.CfnGraphQLApi for summary created by the construc
145147

146148
___
147149

150+
### graphqlApiId
151+
152+
`Readonly` **graphqlApiId**: `string`
153+
154+
Graphql Api Id value
155+
156+
___
157+
158+
### graphqlUrl
159+
160+
`Readonly` **graphqlUrl**: `string`
161+
162+
Graphql Url value
163+
164+
___
165+
148166
### inputAssetBucket
149167

150168
`Readonly` **inputAssetBucket**: `IBucket`

lambda/aws-summarization-appsync-stepfn/document_reader/helper.py

+26-5
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
# and limitations under the License.
1212
#
1313
from typing import Dict
14+
from aiohttp import ClientError
1415
import boto3
1516
from PyPDF2 import PdfReader
16-
17+
import tempfile
18+
import os
1719
from aws_lambda_powertools import Logger, Tracer
1820
from s3inmemoryloader import S3FileLoaderInMemory
1921

@@ -23,6 +25,8 @@
2325
s3 = boto3.resource('s3')
2426
rekognition_client=boto3.client('rekognition')
2527

28+
s3_client = boto3.client('s3')
29+
2630
@tracer.capture_method
2731
def read_file_from_s3(bucket, key):
2832
logger.info(f"Fetching file from S3: bucket: {bucket}, key: {key}")
@@ -42,10 +46,10 @@ def check_file_exists(bucket,key):
4246
return True
4347
except s3_client.exceptions.ClientError as exp:
4448
if exp.response['Error']['Code'] == '404':
45-
logger.exception('Object doesn\'t exist')
49+
logger.info('Object doesn\'t already exist in tranformed bucket, processing it now.. ')
4650
return False
4751
else:
48-
logger.exception('An error occured')
52+
logger.exception('Object doesn\'t already exist in tranformed bucket, processing it now..')
4953
return False
5054

5155

@@ -73,11 +77,15 @@ def get_file_transformation(transformed_asset_bucket,transformed_file_name,
7377
response['name'] = transformed_file_name
7478
response['summary']=''
7579
else:
76-
with open(original_file_name, "rb") as img_file:
80+
input_file= download_file(input_asset_bucket,original_file_name)
81+
with open(input_file, "rb") as img_file:
7782
image_bytes = {"Bytes": img_file.read()}
7883
if(moderate_image(image_bytes) is False):
7984
logger.info("Upload image to processed assets bucket")
80-
s3.Bucket(transformed_asset_bucket).put_object(Key=original_file_name, Body=image_bytes)
85+
s3_client.copy_object(Bucket=transformed_asset_bucket,
86+
Key=original_file_name,
87+
CopySource=input_asset_bucket+'/'+original_file_name
88+
)
8189
response['status'] = 'File transformed'
8290
response['name'] = original_file_name
8391
response['summary']=''
@@ -86,6 +94,19 @@ def get_file_transformation(transformed_asset_bucket,transformed_file_name,
8694
logger.info("File already exists,skip transformation.")
8795
return response
8896

97+
def download_file(bucket,key )-> str:
98+
try:
99+
file_path = os.path.join(tempfile.gettempdir(), os.path.basename(key))
100+
s3_client.download_file(bucket, key,file_path)
101+
logger.info(f"file downloaded {file_path}")
102+
return file_path
103+
except ClientError as client_err:
104+
logger.error(f"Couldn\'t download file {key}/{file_path} from {bucket}: {client_err.response['Error']['Message']}")
105+
106+
except Exception as exp:
107+
logger.error(f"Couldn\'t download file {key}/{file_path} from {bucket}: {exp}")
108+
109+
89110
def moderate_image(image_bytes)-> str:
90111
isToxicImage = False
91112
try:

lambda/aws-summarization-appsync-stepfn/summary_generator/lambda.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@
4848

4949
transformed_bucket_name = os.environ["ASSET_BUCKET_NAME"]
5050
chain_type = os.environ["SUMMARY_LLM_CHAIN_TYPE"]
51-
5251
bedrock_client = boto3.client('bedrock-runtime')
5352

53+
5454
@logger.inject_lambda_context(log_event=True)
5555
@tracer.capture_lambda_handler
5656
@metrics.log_metrics(capture_cold_start_metric=True)
@@ -142,9 +142,9 @@ def generate_summary(_summary_llm,inputFile,language)-> str:
142142
def generate_summary_for_image(_summary_llm,base64_images,language)-> str:
143143

144144
logger.info(f" generating image description...")
145-
prompt ="Describe this image."
146-
147-
system_message = "Respond only in {language}."
145+
prompt =f"Think step by step and then describe this image in {language} language."
146+
147+
system_message = f"Respond only in {language}."
148148
human_message=[
149149

150150
{
@@ -161,8 +161,7 @@ def generate_summary_for_image(_summary_llm,base64_images,language)-> str:
161161

162162
}
163163
]
164-
#ai_message = "Here is my response after thinking step by step ."
165-
ai_message = "Here is my response in 50 words."
164+
ai_message = "Here is my response after thinking step by step ."
166165
prompt = ChatPromptTemplate.from_messages(
167166
[
168167
SystemMessage(content=system_message),
@@ -172,4 +171,4 @@ def generate_summary_for_image(_summary_llm,base64_images,language)-> str:
172171
)
173172

174173
chain = prompt | _summary_llm | StrOutputParser()
175-
chain.invoke({"question": "Describe the image"})
174+
chain.invoke({"question": prompt})

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

+41-19
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, Stack } from 'aws-cdk-lib';
14+
import { Duration, Aws } 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';
@@ -31,7 +31,7 @@ import { ConstructName } from '../../../common/base-class/construct-name-enum';
3131
import * as eventBridge from '../../../common/helpers/eventbridge-helper';
3232
import { buildDockerLambdaFunction } from '../../../common/helpers/lambda-builder-helper';
3333
import * as s3BucketHelper from '../../../common/helpers/s3-bucket-helper';
34-
import { generatePhysicalName, lambdaMemorySizeLimiter } from '../../../common/helpers/utils';
34+
import { lambdaMemorySizeLimiter, generatePhysicalNameV2 } from '../../../common/helpers/utils';
3535
import { DockerLambdaCustomProps } from '../../../common/props/DockerLambdaCustomProps';
3636

3737
export interface SummarizationAppsyncStepfnProps {
@@ -197,6 +197,16 @@ export class SummarizationAppsyncStepfn extends BaseClass {
197197
*/
198198
public readonly graphqlApi: appsync.IGraphqlApi ;
199199

200+
/**
201+
* Graphql Api Id value
202+
*/
203+
public readonly graphqlApiId: string ;
204+
205+
/**
206+
* Graphql Url value
207+
*/
208+
public readonly graphqlUrl: string ;
209+
200210
/**
201211
* Returns the instance of ec2.IVpc used by the construct
202212
*/
@@ -313,8 +323,10 @@ export class SummarizationAppsyncStepfn extends BaseClass {
313323
this.inputAssetBucket = new s3.Bucket(this,
314324
'inputAssetsSummaryBucket'+this.stage, props.bucketInputsAssetsProps);
315325
} else {
316-
const bucketName= 'input-assets-summary-bucket'+this.stage+'-'+Aws.ACCOUNT_ID;
317-
this.inputAssetBucket = new s3.Bucket(this, 'inputAssetsSummaryBucket'+this.stage,
326+
const bucketName= generatePhysicalNameV2(this,
327+
'input-assets-bucket'+this.stage,
328+
{ maxLength: 63, lower: true });
329+
this.inputAssetBucket = new s3.Bucket(this, bucketName,
318330
{
319331
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
320332
encryption: s3.BucketEncryption.S3_MANAGED,
@@ -340,9 +352,11 @@ export class SummarizationAppsyncStepfn extends BaseClass {
340352
this.processedAssetBucket = new s3.Bucket(this,
341353
'processedAssetsSummaryBucket'+this.stage, props.bucketProcessedAssetsProps);
342354
} else {
343-
const bucketName= 'processed-assets-summary-bucket'+this.stage+'-'+Aws.ACCOUNT_ID;
355+
const bucketName= generatePhysicalNameV2(this,
356+
'processed-assets-bucket'+this.stage,
357+
{ maxLength: 63, lower: true });
344358

345-
this.processedAssetBucket = new s3.Bucket(this, 'processedAssetsSummaryBucket'+this.stage,
359+
this.processedAssetBucket = new s3.Bucket(this, bucketName,
346360
{
347361
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
348362
encryption: s3.BucketEncryption.S3_MANAGED,
@@ -376,10 +390,12 @@ export class SummarizationAppsyncStepfn extends BaseClass {
376390
],
377391
};
378392

379-
const apiName = props.summaryApiName || 'summaryApi';
393+
const apiName = props.summaryApiName || generatePhysicalNameV2(this,
394+
'summaryApi'+this.stage,
395+
{ maxLength: 32, lower: true });
380396

381397
// graphql api for summary. client invoke this api with given schema and cognito user pool auth.
382-
const summarizationGraphqlApi = new appsync.GraphqlApi(this, 'summarizationGraphqlApi'+this.stage,
398+
const summarizationGraphqlApi = new appsync.GraphqlApi(this, apiName,
383399
{
384400
name: apiName+this.stage,
385401
logConfig: {
@@ -393,6 +409,8 @@ export class SummarizationAppsyncStepfn extends BaseClass {
393409
xrayEnabled: this.enablexray,
394410
});
395411
this.graphqlApi= summarizationGraphqlApi;
412+
this.graphqlApiId = summarizationGraphqlApi.apiId;
413+
this.graphqlUrl= summarizationGraphqlApi.graphqlUrl;
396414

397415
// If the user provides a mergedApi endpoint, the lambda
398416
// functions will use this endpoint to send their status updates
@@ -473,7 +491,9 @@ export class SummarizationAppsyncStepfn extends BaseClass {
473491

474492
const construct_input_validation_lambda_props = {
475493
code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../../../../lambda/aws-summarization-appsync-stepfn/input_validator')),
476-
functionName: 'summary_input_validator'+this.stage,
494+
functionName: generatePhysicalNameV2(this,
495+
'summary_input_validator'+this.stage,
496+
{ maxLength: 63, lower: true }),
477497
description: 'Lambda function to validate input for summary api',
478498
vpc: this.vpc,
479499
tracing: this.lambdaTracing,
@@ -669,8 +689,11 @@ export class SummarizationAppsyncStepfn extends BaseClass {
669689
true,
670690
);
671691

692+
const functionName = generatePhysicalNameV2(this,
693+
'summary_generator'+this.stage,
694+
{ maxLength: 32, lower: true });
672695
const construct_generate_summary_lambda_props = {
673-
functionName: 'summary_generator'+this.stage,
696+
functionName: functionName,
674697
description: 'Lambda function to generate the summary',
675698
code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../../../../lambda/aws-summarization-appsync-stepfn/summary_generator')),
676699
vpc: this.vpc,
@@ -689,7 +712,7 @@ export class SummarizationAppsyncStepfn extends BaseClass {
689712

690713
// Lambda function used to generate the summary in the step function
691714
const generateSummarylambda = buildDockerLambdaFunction(this,
692-
'generateSummarylambda' + this.stage,
715+
functionName,
693716
construct_generate_summary_lambda_props,
694717
props.customSummaryGeneratorDockerLambdaProps,
695718
);
@@ -729,7 +752,7 @@ export class SummarizationAppsyncStepfn extends BaseClass {
729752
resultPath: '$.validation_result',
730753
});
731754

732-
const documentReaderTask = new sfnTask.LambdaInvoke(this, 'Read document and check summary in cache ', {
755+
const documentReaderTask = new sfnTask.LambdaInvoke(this, 'Read document.', {
733756
lambdaFunction: documentReaderLambda,
734757
resultPath: '$.document_result',
735758
});
@@ -741,7 +764,9 @@ export class SummarizationAppsyncStepfn extends BaseClass {
741764
});
742765

743766
const dlq: sqs.Queue = new sqs.Queue(this, 'dlq', {
744-
queueName: 'summarydlq'+this.stage,
767+
queueName: generatePhysicalNameV2(this,
768+
'summarydlq'+this.stage,
769+
{ maxLength: 32, lower: true }),
745770
retentionPeriod: Duration.days(7),
746771
enforceSSL: true,
747772
});
@@ -783,12 +808,9 @@ export class SummarizationAppsyncStepfn extends BaseClass {
783808
const maxLogGroupNameLength = 255;
784809
const logGroupPrefix = '/aws/vendedlogs/states/constructs/';
785810
const maxGeneratedNameLength = maxLogGroupNameLength - logGroupPrefix.length;
786-
const nameParts: string[] = [
787-
Stack.of(scope).stackName, // Name of the stack
788-
scope.node.id, // Construct ID
789-
'StateMachineLogSummarization', // Literal string for log group name portion
790-
];
791-
const logGroupName = generatePhysicalName(logGroupPrefix, nameParts, maxGeneratedNameLength);
811+
812+
const logGroupName = generatePhysicalNameV2(this, logGroupPrefix,
813+
{ maxLength: maxGeneratedNameLength, lower: true });
792814
const summarizationLogGroup = new logs.LogGroup(this, 'summarizationLogGroup', {
793815
logGroupName: logGroupName,
794816
});

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,9 @@ describe('Summarization Appsync Stepfn construct', () => {
126126
'GraphQLUrl',
127127
],
128128
},
129-
INPUT_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testinputAssetsSummaryBucketdev') },
129+
INPUT_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testinputassetsbucket') },
130130
IS_FILE_TRANSFORMED: 'false',
131-
TRANSFORMED_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testprocessedAssetsSummaryBucket') },
131+
TRANSFORMED_ASSET_BUCKET: { Ref: Match.stringLikeRegexp('testprocessedassetsbucket') },
132132
},
133133
},
134134
});
@@ -139,7 +139,7 @@ describe('Summarization Appsync Stepfn construct', () => {
139139
Variables: {
140140
ASSET_BUCKET_NAME: {
141141
Ref: Match.stringLikeRegexp
142-
('testprocessedAssetsSummaryBucket'),
142+
('testprocessedassetsbucket'),
143143
},
144144
GRAPHQL_URL: {
145145
'Fn::GetAtt': [

0 commit comments

Comments
 (0)