Skip to content

Commit 9919273

Browse files
committed
Merge branch 'feature/enhancea2i' into 'develop'
Enhance A2I template and handle A2I task failures. See merge request genaiic-reusable-assets/engagement-artifacts/genaiic-idp-accelerator!251
2 parents 6388a86 + e6c75f4 commit 9919273

File tree

11 files changed

+907
-409
lines changed

11 files changed

+907
-409
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ SPDX-License-Identifier: MIT-0
88

99
### Added
1010

11+
- **Enhanced A2I Template and Workflow Management**
12+
- Enhanced A2I template with improved user interface and clearer instructions for reviewers
13+
- Added comprehensive instructions for reviewers in A2I template to guide the review process
14+
- Implemented capture of failed review tasks with proper error handling and logging
15+
- Added workflow orchestration control to stop processing when reviewer rejects A2I task
16+
- Removed automatic A2I task creation when Pattern-1 Bedrock Data Automation (BDA) fails to classify document to appropriate Blueprint
17+
1118
- **Dynamic Cost Calculation for Metering Data**
1219
- Added automated unit cost and estimated cost calculation to metering table with new `unit_cost` and `estimated_cost` columns
1320
- Dynamic pricing configuration loading from configuration

patterns/pattern-1/src/hitl-process-function/index.py

Lines changed: 237 additions & 196 deletions
Large diffs are not rendered by default.

patterns/pattern-1/src/processresults_function/index.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ def start_human_loop(
673673
FlowDefinitionArn = ssm.get_parameter(Name=f"/{os.environ.get('METRIC_NAMESPACE', 'IDP')}/FlowDefinitionArn")['Parameter']['Value']
674674
human_review_id = generate_random_string(2)
675675
response = a2i_runtime_client.start_human_loop(
676-
HumanLoopName=f"review-bda-{execution_id}-{human_review_id}",
676+
HumanLoopName=f"review-bda-{human_review_id}-{execution_id}-{record_number}-{page_id_num}",
677677
FlowDefinitionArn=FlowDefinitionArn,
678678
HumanLoopInput={"InputContent": json.dumps(human_loop_input)}
679679
)
@@ -825,6 +825,7 @@ def process_segments(
825825

826826
now = datetime.datetime.now().isoformat()
827827
hitl_triggered = False
828+
overall_hitl_triggered = False
828829

829830
for record_number, segment in enumerate(segment_metadata, start=1):
830831
logger.info(f"Processing segment for execution id: {execution_id}")
@@ -901,6 +902,7 @@ def process_segments(
901902
if low_confidence:
902903
hitl_triggered = low_confidence
903904
metrics.put_metric('HITLTriggered', 1)
905+
overall_hitl_triggered = True
904906
for page_number in page_indices:
905907
page_str = str(page_number)
906908
key_values = pagespecific_details['key_value_details'].get(page_str, [])
@@ -929,6 +931,7 @@ def process_segments(
929931
else:
930932
if enable_hitl == 'true':
931933
std_hitl = 'true'
934+
# std_hitl = None # HITL for standard output blueprint match is disabled until we have option to choose Blueprint in A2I
932935
else:
933936
std_hitl = None
934937
# Process standard output if no custom output match
@@ -940,7 +943,8 @@ def process_segments(
940943
page_array = list(range(start_page, end_page + 1))
941944
item.update({
942945
"page_array": page_array,
943-
"hitl_triggered": std_hitl,
946+
# "hitl_triggered": std_hitl,
947+
"hitl_triggered": None,
944948
"extraction_bp_name": "None",
945949
"extracted_result": std_output
946950
})
@@ -950,31 +954,34 @@ def process_segments(
950954
record_number=record_number,
951955
bp_match=segment.get('custom_output_status'),
952956
extraction_bp_name="None",
953-
hitl_triggered=std_hitl,
957+
# hitl_triggered=std_hitl,
958+
hitl_triggered=None,
954959
page_array=page_array,
955960
review_portal_url=SAGEMAKER_A2I_REVIEW_PORTAL_URL
956961
)
957962

958-
hitl_triggered = std_hitl
959-
if enable_hitl == 'true':
960-
for page_number in range(start_page, end_page + 1):
961-
ImageUri = f"s3://{output_bucket}/{object_key}/pages/{page_number}/image.jpg"
962-
try:
963-
human_loop_response = start_human_loop(
964-
execution_id=execution_id,
965-
kv_pairs=[],
966-
source_image_uri=ImageUri,
967-
bounding_boxes=[],
968-
blueprintName="",
969-
bp_confidence=0.00,
970-
confidenceThreshold=confidence_threshold,
971-
page_id=page_number,
972-
page_indices=page_array,
973-
record_number=record_number
974-
)
975-
logger.info(f"Triggered human loop for page {page_number}: {human_loop_response}")
976-
except Exception as e:
977-
logger.error(f"Failed to start human loop for page {page_number}: {str(e)}")
963+
# hitl_triggered = std_hitl
964+
hitl_triggered = None
965+
# if enable_hitl == 'true':
966+
# # if std_hitl: # HITL for standard output blueprint match is disabled until we have option to choose Blueprint in A2I
967+
# for page_number in range(start_page, end_page + 1):
968+
# ImageUri = f"s3://{output_bucket}/{object_key}/pages/{page_number}/image.jpg"
969+
# try:
970+
# human_loop_response = start_human_loop(
971+
# execution_id=execution_id,
972+
# kv_pairs=[],
973+
# source_image_uri=ImageUri,
974+
# bounding_boxes=[],
975+
# blueprintName="",
976+
# bp_confidence=0.00,
977+
# confidenceThreshold=confidence_threshold,
978+
# page_id=page_number,
979+
# page_indices=page_array,
980+
# record_number=record_number
981+
# )
982+
# logger.info(f"Triggered human loop for page {page_number}: {human_loop_response}")
983+
# except Exception as e:
984+
# logger.error(f"Failed to start human loop for page {page_number}: {str(e)}")
978985

979986
document.hitl_metadata.append(hitl_metadata)
980987

@@ -985,7 +992,7 @@ def process_segments(
985992
except Exception as e:
986993
logger.error(f"Error saving to DynamoDB: {str(e)}")
987994

988-
return document, hitl_triggered
995+
return document, overall_hitl_triggered
989996

990997
def handler(event, context):
991998
"""
@@ -1136,7 +1143,7 @@ def handler(event, context):
11361143
execution_id,
11371144
document
11381145
)
1139-
if hitl_result:
1146+
if hitl_result or hitl_triggered== "true":
11401147
hitl_triggered = "true"
11411148
elif isinstance(output_metadata, dict):
11421149
for asset_id, asset in output_metadata.items():
@@ -1149,7 +1156,7 @@ def handler(event, context):
11491156
execution_id,
11501157
document
11511158
)
1152-
if hitl_result:
1159+
if hitl_result or hitl_triggered== "true":
11531160
hitl_triggered = "true"
11541161
else:
11551162
logger.error("Unexpected output_metadata format in job_metadata.json")

patterns/pattern-1/statemachine/workflow.asl.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@
108108
"BackoffRate": 2
109109
}
110110
],
111+
"Catch": [
112+
{
113+
"ErrorEquals": ["HITLFailedException"],
114+
"Next": "HITLFailState"
115+
}
116+
],
111117
"Next": "HITLStatusUpdate"
112118
},
113119
"HITLStatusUpdate": {
@@ -171,6 +177,11 @@
171177
"Type": "Fail",
172178
"Cause": "Data Automation Job Failed",
173179
"Error": "JobFailedException"
180+
},
181+
"HITLFailState": {
182+
"Type": "Fail",
183+
"Cause": "Human In The Loop Review Failed",
184+
"Error": "HITLFailedException"
174185
}
175186
}
176187
}

patterns/pattern-1/template.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,8 @@ Resources:
878878
detail:
879879
humanLoopStatus:
880880
- 'Completed'
881+
- 'Failed'
882+
- 'Stopped'
881883
State: 'ENABLED'
882884
Targets:
883885
- Arn: !GetAtt HITLProcessLambdaFunction.Arn

patterns/pattern-2/template.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@ Parameters:
8181
Default: ""
8282
Description: "If you provided a Bedrock Guardrail Id above, provide the corresponding Guardrail version here"
8383

84+
EnableHITL:
85+
Type: String
86+
Default: "false"
87+
AllowedValues:
88+
- "true"
89+
- "false"
90+
Description: "Enable Human In The Loop (A2I) for document review"
91+
92+
SageMakerA2IReviewPortalURL:
93+
Type: String
94+
Default: ""
95+
Description: "SageMaker A2I Review Portal URL for HITL workflows"
96+
8497
ConfigurationDefaultS3Uri:
8598
Type: String
8699
Description: "S3 URI (s3://bucket/path/config.json) to import default configuration from S3"
@@ -105,6 +118,7 @@ Conditions:
105118
HasCustomClassificationModelARN : !Not [!Equals [!Ref CustomClassificationModelARN , ""]]
106119
HasCustomExtractionModelARN : !Not [!Equals [!Ref CustomExtractionModelARN , ""]]
107120
HasPermissionsBoundary: !Not [!Equals [!Ref PermissionsBoundaryArn, ""]]
121+
IsHITLEnabled: !Equals [!Ref EnableHITL, "true"]
108122

109123

110124
Resources:

patterns/pattern-3/template.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,23 @@ Parameters:
9595
AllowedPattern: "^(|arn:aws:iam::[0-9]{12}:policy/.+)$"
9696
ConstraintDescription: Must be empty or a valid IAM policy ARN
9797

98+
EnableHITL:
99+
Type: String
100+
Default: "false"
101+
AllowedValues:
102+
- "true"
103+
- "false"
104+
Description: "Enable Human In The Loop (A2I) for document review"
105+
106+
SageMakerA2IReviewPortalURL:
107+
Type: String
108+
Default: ""
109+
Description: "SageMaker A2I Review Portal URL for HITL workflows"
110+
98111
Conditions:
99112
HasGuardrailConfig: !And [!Not [!Equals [!Ref BedrockGuardrailId, ""]], !Not [!Equals [!Ref BedrockGuardrailVersion, ""]]]
100113
HasPermissionsBoundary: !Not [!Equals [!Ref PermissionsBoundaryArn, ""]]
114+
IsHITLEnabled: !Equals [!Ref EnableHITL, "true"]
101115

102116
Resources:
103117

samples/lending_package.pdf

-98.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)